yaml-flow 5.2.6 → 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 +16 -0
- package/examples/example-board/scripts/copilot_wrapper_helper.ps1 +19 -10
- 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/browser/card-compute.js
CHANGED
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
|
|
127
127
|
var VALID_ELEMENT_KINDS = ['metric','table','chart','form','filter','list','notes','todo','alert','narrative','badge','text','markdown','custom'];
|
|
128
128
|
var VALID_STATUSES = ['fresh','stale','loading','error'];
|
|
129
|
-
var ALLOWED_KEYS = ['id','meta','requires','provides','view','card_data','compute','
|
|
129
|
+
var ALLOWED_KEYS = ['id','meta','requires','provides','view','card_data','compute','source_defs'];
|
|
130
130
|
|
|
131
131
|
function validateNode(node) {
|
|
132
132
|
var errors = [];
|
|
@@ -191,17 +191,17 @@
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
//
|
|
195
|
-
if (node.
|
|
196
|
-
if (!Array.isArray(node.
|
|
197
|
-
errors.push('
|
|
194
|
+
// source_defs
|
|
195
|
+
if (node.source_defs != null) {
|
|
196
|
+
if (!Array.isArray(node.source_defs)) {
|
|
197
|
+
errors.push('source_defs: must be an array');
|
|
198
198
|
} else {
|
|
199
|
-
node.
|
|
200
|
-
if (!src || typeof src !== 'object' || Array.isArray(src)) errors.push('
|
|
201
|
-
else if (typeof src.bindTo !== 'string' || !src.bindTo) errors.push('
|
|
199
|
+
node.source_defs.forEach(function (src, i) {
|
|
200
|
+
if (!src || typeof src !== 'object' || Array.isArray(src)) errors.push('source_defs[' + i + ']: must be an object');
|
|
201
|
+
else if (typeof src.bindTo !== 'string' || !src.bindTo) errors.push('source_defs[' + i + ']: missing required "bindTo" property');
|
|
202
202
|
else {
|
|
203
|
-
if (src.outputFile != null && typeof src.outputFile !== 'string') errors.push('
|
|
204
|
-
if (src.optional != null && typeof src.optional !== 'boolean') errors.push('
|
|
203
|
+
if (src.outputFile != null && typeof src.outputFile !== 'string') errors.push('source_defs[' + i + ']: outputFile must be a string');
|
|
204
|
+
if (src.optional != null && typeof src.optional !== 'boolean') errors.push('source_defs[' + i + ']: optional must be a boolean');
|
|
205
205
|
}
|
|
206
206
|
});
|
|
207
207
|
}
|
|
@@ -231,18 +231,18 @@
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
* Enrich
|
|
235
|
-
* Pure function: no side effects, returns new enriched
|
|
234
|
+
* Enrich source_defs with execution context for template interpolation and prompt rendering.
|
|
235
|
+
* Pure function: no side effects, returns new enriched source_defs array.
|
|
236
236
|
*
|
|
237
|
-
* @param {Array}
|
|
237
|
+
* @param {Array} source_defs - Array of source definitions
|
|
238
238
|
* @param {Object} context - Execution context containing requires, sourcesData, computed_values
|
|
239
|
-
* @returns {Array} New array of
|
|
239
|
+
* @returns {Array} New array of source_defs with _requires, _sourcesData, _computed_values attached
|
|
240
240
|
*/
|
|
241
|
-
function enrichSources(
|
|
242
|
-
if (!
|
|
241
|
+
function enrichSources(source_defs, context) {
|
|
242
|
+
if (!source_defs || source_defs.length === 0) return [];
|
|
243
243
|
context = context || {};
|
|
244
244
|
|
|
245
|
-
return
|
|
245
|
+
return source_defs.map(function (src) {
|
|
246
246
|
return Object.assign({}, src, {
|
|
247
247
|
_requires: context.requires || {},
|
|
248
248
|
_sourcesData: context.sourcesData || {},
|
package/browser/live-cards.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// live-cards.js — LiveCards v3: Node-based Board/Canvas engine
|
|
2
2
|
//
|
|
3
3
|
// Schema: Each node has { id } required; all else optional.
|
|
4
|
-
// id, meta, card_data, requires, provides,
|
|
5
|
-
// Nodes with view render as cards; nodes with
|
|
4
|
+
// id, meta, card_data, requires, provides, source_defs, compute, view
|
|
5
|
+
// Nodes with view render as cards; nodes with source_defs but no view render as source pills in canvas.
|
|
6
6
|
// compute[] — ordered array of { bindTo, expr } JSONata steps → writes to node.computed_values (ephemeral)
|
|
7
|
-
//
|
|
7
|
+
// source_defs[] — open objects: only bindTo + outputFile matter to the engine; all other fields are
|
|
8
8
|
// passed verbatim to the board's task-executor (--in JSON). Users define their own
|
|
9
9
|
// shape (kind, url, mailbox, channel, model, ...) per executor.
|
|
10
10
|
// requires[] — upstream node IDs; engine subscribes automatically
|
|
@@ -89,6 +89,15 @@ var LiveCard = (function () {
|
|
|
89
89
|
.lc-files-modal-backdrop .modal-dialog { max-height:90vh; }
|
|
90
90
|
.lc-files-modal-backdrop .modal-content { display:flex; flex-direction:column; max-height:90vh; }
|
|
91
91
|
.lc-files-modal-backdrop .modal-body { overflow-y:auto; flex:1; min-height:200px; padding:1rem; }
|
|
92
|
+
.lc-simulation-card { background:#fdf6ec; border-color:#e0c97f !important; }
|
|
93
|
+
.lc-simulation-card .card-header { background:#faecc8; border-bottom-color:#e0c97f; }
|
|
94
|
+
.lc-gandalf-card { background:#eef4ff; border-color:#6ea4e0 !important; }
|
|
95
|
+
.lc-gandalf-card .card-header { background:#d7e8fa; border-bottom-color:#6ea4e0; cursor:pointer; user-select:none; }
|
|
96
|
+
.lc-gandalf-card .card-header:hover { background:#c8dcf5; }
|
|
97
|
+
.lc-gandalf-caret { transition:transform .2s; display:inline-flex; align-items:center; margin-left:auto; opacity:.6; flex-shrink:0; cursor:pointer; padding:2px; }
|
|
98
|
+
.lc-gandalf-caret:hover { opacity:1; }
|
|
99
|
+
.lc-gandalf-card.lc-collapsed .lc-gandalf-caret { transform:rotate(-90deg); }
|
|
100
|
+
.lc-gandalf-card.lc-collapsed .card-body { display:none !important; }
|
|
92
101
|
@media (max-width:576px) {
|
|
93
102
|
.lc-metric-value { font-size:1.5rem; }
|
|
94
103
|
.lc-chart-wrap { min-height:150px; }
|
|
@@ -1847,6 +1856,68 @@ var LiveCard = (function () {
|
|
|
1847
1856
|
};
|
|
1848
1857
|
}
|
|
1849
1858
|
|
|
1859
|
+
// ---- ref ----
|
|
1860
|
+
// Indirection element: resolves a bind path to get the view definition,
|
|
1861
|
+
// then dispatches to the real renderer. The resolved value may be:
|
|
1862
|
+
// - a string → treated directly as the element kind ("table", "chart", etc.)
|
|
1863
|
+
// - an object → { kind, label, data: { columns, chartType, chartOptions, writeTo } }
|
|
1864
|
+
// merged with static elemDef (static fields win for protection)
|
|
1865
|
+
// - null/undefined → falls back to elemDef.data.fallbackKind or shape-inferred kind
|
|
1866
|
+
//
|
|
1867
|
+
// Allowed kinds from resolved value (whitelist, unknown → "table"):
|
|
1868
|
+
// table, editable-table, chart, metric, list, badge, text, narrative, markdown
|
|
1869
|
+
//
|
|
1870
|
+
// Usage:
|
|
1871
|
+
// { "kind": "ref",
|
|
1872
|
+
// "data": { "bind": "fetched_sources.rebalance.proposed_trades",
|
|
1873
|
+
// "viewBind": "card_data.display_mode",
|
|
1874
|
+
// "fallbackKind": "table" } }
|
|
1875
|
+
//
|
|
1876
|
+
// viewBind can point to any namespace: card_data, fetched_sources, requires, computed_values.
|
|
1877
|
+
// If the resolved view object contains a "bind" sub-path, that overrides data.bind.
|
|
1878
|
+
const _REF_KIND_WHITELIST = new Set([
|
|
1879
|
+
'table','editable-table','chart','metric','list','badge',
|
|
1880
|
+
'text','narrative','markdown','form','filter','todo','alert',
|
|
1881
|
+
]);
|
|
1882
|
+
function _renderRef(data, el, elemDef, node) {
|
|
1883
|
+
const ed = elemDef.data || {};
|
|
1884
|
+
|
|
1885
|
+
// Resolve the view hint
|
|
1886
|
+
const viewRaw = ed.viewBind ? _resolveBind(node, ed.viewBind) : undefined;
|
|
1887
|
+
|
|
1888
|
+
let resolvedKind, resolvedExtra;
|
|
1889
|
+
if (typeof viewRaw === 'string' && viewRaw) {
|
|
1890
|
+
resolvedKind = viewRaw;
|
|
1891
|
+
resolvedExtra = {};
|
|
1892
|
+
} else if (viewRaw && typeof viewRaw === 'object' && !Array.isArray(viewRaw)) {
|
|
1893
|
+
resolvedKind = typeof viewRaw.kind === 'string' ? viewRaw.kind : undefined;
|
|
1894
|
+
resolvedExtra = viewRaw.data && typeof viewRaw.data === 'object' ? viewRaw.data : {};
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// Validate kind against whitelist; fall back to shape inference
|
|
1898
|
+
if (!resolvedKind || !_REF_KIND_WHITELIST.has(resolvedKind)) {
|
|
1899
|
+
resolvedKind = ed.fallbackKind && _REF_KIND_WHITELIST.has(ed.fallbackKind)
|
|
1900
|
+
? ed.fallbackKind
|
|
1901
|
+
: (Array.isArray(data) ? 'table' : typeof data === 'string' ? 'text' : 'narrative');
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// Build effective elemDef: resolved hints first, static elemDef fields override (card author wins)
|
|
1905
|
+
const mergedData = Object.assign({}, resolvedExtra, ed);
|
|
1906
|
+
delete mergedData.viewBind;
|
|
1907
|
+
delete mergedData.fallbackKind;
|
|
1908
|
+
|
|
1909
|
+
// If the resolved hint provided its own bind path, honour it (but static ed.bind still wins)
|
|
1910
|
+
if (!mergedData.bind && resolvedExtra.bind) mergedData.bind = resolvedExtra.bind;
|
|
1911
|
+
|
|
1912
|
+
const effectiveElemDef = Object.assign({}, elemDef, { kind: resolvedKind }, { data: mergedData });
|
|
1913
|
+
|
|
1914
|
+
// Re-resolve data using effective bind (may have changed)
|
|
1915
|
+
const effectiveData = mergedData.bind ? _resolveBind(node, mergedData.bind) : data;
|
|
1916
|
+
|
|
1917
|
+
const renderer = _renderers[resolvedKind] || _renderers.table;
|
|
1918
|
+
renderer(effectiveData, el, effectiveElemDef, node);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1850
1921
|
// ---- Register built-in renderers ----
|
|
1851
1922
|
|
|
1852
1923
|
_renderers.table = _renderTable;
|
|
@@ -1867,6 +1938,7 @@ var LiveCard = (function () {
|
|
|
1867
1938
|
_renderers['file-upload'] = _renderFileUpload;
|
|
1868
1939
|
_renderers['chat'] = _renderChatEl;
|
|
1869
1940
|
_renderers.actions = _renderActions;
|
|
1941
|
+
_renderers.ref = _renderRef;
|
|
1870
1942
|
|
|
1871
1943
|
// ===========================================================================
|
|
1872
1944
|
// _renderElements — render all view.elements for a card node
|
|
@@ -2453,26 +2525,75 @@ var LiveCard = (function () {
|
|
|
2453
2525
|
|
|
2454
2526
|
function _buildCardWrapper(node) {
|
|
2455
2527
|
const wrap = document.createElement('div');
|
|
2456
|
-
|
|
2528
|
+
const card = node && node.card ? node.card : {};
|
|
2529
|
+
const isSimulation = card.meta && card.meta.simulation === true;
|
|
2530
|
+
const isGandalfCard = card.meta && card.meta._gandalfCard === true;
|
|
2531
|
+
const extraClass = isSimulation ? ' lc-simulation-card' : (isGandalfCard ? ' lc-gandalf-card' : '');
|
|
2532
|
+
wrap.className = 'card shadow-sm h-100' + extraClass;
|
|
2457
2533
|
const header = document.createElement('div');
|
|
2458
2534
|
header.className = 'card-header d-flex align-items-center gap-2 py-2';
|
|
2459
|
-
const card = node && node.card ? node.card : {};
|
|
2460
2535
|
const title = (card.meta && card.meta.title) || node.id;
|
|
2461
2536
|
const tags = (card.meta && card.meta.tags) || [];
|
|
2462
2537
|
let badgeHtml = '';
|
|
2463
|
-
if ((card.
|
|
2464
|
-
var src = card.
|
|
2538
|
+
if ((card.source_defs && card.source_defs.length) && !card.view) {
|
|
2539
|
+
var src = card.source_defs[0] || {};
|
|
2465
2540
|
badgeHtml = '<span class="badge bg-info text-dark ms-auto">' + _esc(src.kind || 'source') + '</span>';
|
|
2466
2541
|
} else if (tags.length) {
|
|
2467
2542
|
badgeHtml = tags.map(t => '<span class="badge bg-secondary ms-1">' + _esc(t) + '</span>').join('');
|
|
2468
2543
|
}
|
|
2469
2544
|
header.innerHTML = '<strong class="small">' + _esc(title) + '</strong>' + badgeHtml;
|
|
2470
|
-
|
|
2545
|
+
|
|
2546
|
+
// Gandalf cards: collapsible via caret — caret gets its own click listener,
|
|
2547
|
+
// header is left alone for dragging in canvas mode.
|
|
2548
|
+
if (isGandalfCard) {
|
|
2549
|
+
const caret = document.createElement('span');
|
|
2550
|
+
caret.className = 'lc-gandalf-caret';
|
|
2551
|
+
caret.title = 'Collapse / expand';
|
|
2552
|
+
caret.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>';
|
|
2553
|
+
header.appendChild(caret);
|
|
2554
|
+
|
|
2555
|
+
const storageKey = 'lc-gandalf-collapsed:' + (node.id || title);
|
|
2556
|
+
if (sessionStorage.getItem(storageKey) === '1') {
|
|
2557
|
+
wrap.classList.add('lc-collapsed');
|
|
2558
|
+
header.dataset.gandalfCollapsed = '1';
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
caret.addEventListener('click', function(e) {
|
|
2562
|
+
e.stopPropagation();
|
|
2563
|
+
const cardEl = caret.closest('.lc-gandalf-card') || wrap;
|
|
2564
|
+
cardEl.classList.toggle('lc-collapsed');
|
|
2565
|
+
sessionStorage.setItem(storageKey, cardEl.classList.contains('lc-collapsed') ? '1' : '0');
|
|
2566
|
+
});
|
|
2567
|
+
caret.addEventListener('pointerdown', e => e.stopPropagation()); // prevent drag start
|
|
2568
|
+
}
|
|
2569
|
+
if (isSimulation) {
|
|
2570
|
+
const simBtns = document.createElement('span');
|
|
2571
|
+
simBtns.className = 'd-inline-flex align-items-center gap-1 ms-auto';
|
|
2572
|
+
|
|
2573
|
+
const pinBtn = document.createElement('button');
|
|
2574
|
+
pinBtn.className = 'btn btn-sm btn-outline-success lc-sim-pin';
|
|
2575
|
+
pinBtn.style.cssText = 'padding: 2px 6px;';
|
|
2576
|
+
pinBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 2h6l-1 7h-4L9 2z"/><path d="M6 17h12l-2-4H8L6 17z"/></svg>';
|
|
2577
|
+
pinBtn.title = 'Pin this simulation card';
|
|
2578
|
+
pinBtn.dataset.nodeId = node.id;
|
|
2579
|
+
|
|
2580
|
+
const discardBtn = document.createElement('button');
|
|
2581
|
+
discardBtn.className = 'btn btn-sm btn-outline-danger lc-sim-discard';
|
|
2582
|
+
discardBtn.style.cssText = 'padding: 2px 6px;';
|
|
2583
|
+
discardBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
2584
|
+
discardBtn.title = 'Discard this simulation card';
|
|
2585
|
+
discardBtn.dataset.nodeId = node.id;
|
|
2586
|
+
|
|
2587
|
+
simBtns.appendChild(pinBtn);
|
|
2588
|
+
simBtns.appendChild(discardBtn);
|
|
2589
|
+
header.appendChild(simBtns);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2471
2592
|
// Add dev mode code icon button if devMode is enabled
|
|
2472
2593
|
if (devMode.current) {
|
|
2473
2594
|
const codeBtn = document.createElement('button');
|
|
2474
2595
|
codeBtn.className = 'btn btn-sm btn-outline-secondary';
|
|
2475
|
-
codeBtn.style.cssText = 'padding: 2px 6px; margin-left: auto;';
|
|
2596
|
+
codeBtn.style.cssText = 'padding: 2px 6px;' + (isSimulation ? '' : ' margin-left: auto;');
|
|
2476
2597
|
codeBtn.innerHTML = '</>';
|
|
2477
2598
|
codeBtn.title = 'Inspect card data';
|
|
2478
2599
|
codeBtn.addEventListener('click', function(e) {
|
|
@@ -2495,7 +2616,7 @@ var LiveCard = (function () {
|
|
|
2495
2616
|
const status = (node.card_data && node.card_data.status) || 'fresh';
|
|
2496
2617
|
const card = node && node.card ? node.card : {};
|
|
2497
2618
|
const title = (card.meta && card.meta.title) || node.id;
|
|
2498
|
-
const kind = (card.
|
|
2619
|
+
const kind = (card.source_defs && card.source_defs[0] && card.source_defs[0].kind) || 'source';
|
|
2499
2620
|
el.innerHTML = `<div class="lc-source-pill shadow-sm">
|
|
2500
2621
|
${_statusDot(status)}
|
|
2501
2622
|
<span class="fw-medium">${_esc(title)}</span>
|
|
@@ -2615,7 +2736,7 @@ var LiveCard = (function () {
|
|
|
2615
2736
|
nodeList.forEach(node => {
|
|
2616
2737
|
const pos = _positions[node.id] || { x: 0, y: 0 };
|
|
2617
2738
|
|
|
2618
|
-
if ((!node.card || !node.card.view) && (node.card && node.card.
|
|
2739
|
+
if ((!node.card || !node.card.view) && (node.card && node.card.source_defs && node.card.source_defs.length)) {
|
|
2619
2740
|
const el = _buildSourcePill(node);
|
|
2620
2741
|
el.dataset.nodeId = node.id;
|
|
2621
2742
|
el.style.left = pos.x + 'px';
|
|
@@ -2625,7 +2746,10 @@ var LiveCard = (function () {
|
|
|
2625
2746
|
_makeDraggable(el, node);
|
|
2626
2747
|
} else {
|
|
2627
2748
|
const el = document.createElement('div');
|
|
2628
|
-
|
|
2749
|
+
const isSimCanvas = node.card && node.card.meta && node.card.meta.simulation === true;
|
|
2750
|
+
const isGandalfCanvas = node.card && node.card.meta && node.card.meta._gandalfCard === true;
|
|
2751
|
+
const canvasExtra = isSimCanvas ? ' lc-simulation-card' : (isGandalfCanvas ? ' lc-gandalf-card' : '');
|
|
2752
|
+
el.className = 'lc-canvas-card card shadow-sm' + canvasExtra;
|
|
2629
2753
|
el.dataset.nodeId = node.id;
|
|
2630
2754
|
el.style.left = pos.x + 'px';
|
|
2631
2755
|
el.style.top = pos.y + 'px';
|
|
@@ -2633,6 +2757,9 @@ var LiveCard = (function () {
|
|
|
2633
2757
|
|
|
2634
2758
|
const { wrap, body } = _buildCardWrapper(node);
|
|
2635
2759
|
while (wrap.firstChild) el.appendChild(wrap.firstChild);
|
|
2760
|
+
// Re-apply collapsed state: in canvas mode el is the card container, not wrap
|
|
2761
|
+
const movedHeader = el.querySelector('.card-header');
|
|
2762
|
+
if (movedHeader && movedHeader.dataset.gandalfCollapsed === '1') el.classList.add('lc-collapsed');
|
|
2636
2763
|
canvasInner.appendChild(el);
|
|
2637
2764
|
nodeMap[node.id] = { node, colEl: el, bodyEl: body };
|
|
2638
2765
|
engine.render(node, body, { showNotes: false, showChat: false });
|
|
@@ -80,10 +80,10 @@
|
|
|
80
80
|
"type": "array",
|
|
81
81
|
"items": {
|
|
82
82
|
"type": "object",
|
|
83
|
-
"required": ["bindTo", "
|
|
83
|
+
"required": ["bindTo", "ref"],
|
|
84
84
|
"properties": {
|
|
85
85
|
"bindTo": { "type": "string", "description": "Token name published downstream" },
|
|
86
|
-
"
|
|
86
|
+
"ref": { "type": "string", "description": "Path to read value from (card_data.*, requires.*, fetched_sources.*, computed_values.*)" }
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
"description": "Explicit bindings exposing computed or card_data values downstream as named tokens"
|
|
@@ -100,14 +100,19 @@
|
|
|
100
100
|
},
|
|
101
101
|
|
|
102
102
|
"source_def": {
|
|
103
|
-
"description": "One source entry. The engine requires 'bindTo' (compute namespace key) and 'outputFile' (delivery signal path). bindTo and outputFile must be unique across all
|
|
103
|
+
"description": "One source entry. The engine requires 'bindTo' (compute namespace key) and 'outputFile' (delivery signal path). bindTo and outputFile must be unique across all source_defs in a card. Every other property is yours — add whatever your task-executor needs: kind, url, headers, mailbox, channel, model, query, etc. The full object is passed verbatim as the --in JSON to the executor.",
|
|
104
104
|
"type": "object",
|
|
105
105
|
"required": ["bindTo", "outputFile"],
|
|
106
106
|
"additionalProperties": true,
|
|
107
107
|
"properties": {
|
|
108
108
|
"bindTo": { "type": "string", "description": "Key under fetched_sources.* available in compute expressions" },
|
|
109
109
|
"outputFile": { "type": "string", "description": "Board-relative path the executor writes its JSON result to. Presence of this file signals delivery." },
|
|
110
|
-
"
|
|
110
|
+
"projections": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"description": "Named data projections from card_data or requires, evaluated before the executor is called. Each key is a ref name; each value is a JSONata expression rooted at card_data or requires. The resolved values are passed to the executor as _projections. fetched_sources, computed_values, and source_defs are not accessible here — sources run before those exist.",
|
|
113
|
+
"additionalProperties": { "type": "string" }
|
|
114
|
+
},
|
|
115
|
+
"optionalForCompletionGating": { "type": "boolean", "default": false, "description": "When true this source does not gate card completion. Default false when absent, so source_defs are completion-gating by default." },
|
|
111
116
|
"timeout": { "type": "integer", "minimum": 0, "default": 120000, "description": "Executor/script timeout in ms. Default: 120 000 (2 min)." },
|
|
112
117
|
"script": { "type": "string", "description": "Legacy direct-run: shell command executed when no .task-executor is registered. stdout is captured as the result." }
|
|
113
118
|
}
|
|
@@ -215,7 +220,7 @@
|
|
|
215
220
|
},
|
|
216
221
|
|
|
217
222
|
"title": "LiveCard",
|
|
218
|
-
"description": "A unified card node. Behavior depends on which sections are present (
|
|
223
|
+
"description": "A unified card node. Behavior depends on which sections are present (source_defs, compute, view, etc.)",
|
|
219
224
|
"type": "object",
|
|
220
225
|
"required": ["id"],
|
|
221
226
|
"additionalProperties": false,
|
|
@@ -258,22 +263,22 @@
|
|
|
258
263
|
},
|
|
259
264
|
"llm_task_completion_inference": {
|
|
260
265
|
"type": "object",
|
|
261
|
-
"description": "Runtime state written by the inference adapter (advanced/undocumented). Prefer the standard
|
|
266
|
+
"description": "Runtime state written by the inference adapter (advanced/undocumented). Prefer the standard source_defs → compute → provides pattern for LLM-based signals.",
|
|
262
267
|
"properties": {
|
|
263
268
|
"inferenceRequested": { "type": "string", "format": "date-time", "description": "Timestamp when the latest inference request was initiated" },
|
|
264
269
|
"inferenceCompletedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the latest inference request completed" },
|
|
265
270
|
"isTaskCompleted": { "type": "boolean", "description": "Whether the task is considered complete by the adapter" },
|
|
266
271
|
"reasoning": { "type": "string", "description": "Explanation of completion decision" },
|
|
267
|
-
"evidence": { "type": "array", "description": "Supporting evidence from
|
|
272
|
+
"evidence": { "type": "array", "description": "Supporting evidence from source_defs/compute" }
|
|
268
273
|
},
|
|
269
274
|
"additionalProperties": true
|
|
270
275
|
}
|
|
271
276
|
},
|
|
272
277
|
"additionalProperties": true
|
|
273
278
|
},
|
|
274
|
-
"
|
|
279
|
+
"source_defs": {
|
|
275
280
|
"type": "array",
|
|
276
|
-
"description": "Source entries. Each entry is passed verbatim to the board's .task-executor (registered via init --task-executor) as the --in JSON file. The executor fetches/generates the data and writes JSON to --out. If no executor is registered, the built-in executor runs the entry's 'cli' command directly. Sources gate completion by default. Set optionalForCompletionGating: true for enrichment-only
|
|
281
|
+
"description": "Source entries. Each entry is passed verbatim to the board's .task-executor (registered via init --task-executor) as the --in JSON file. The executor fetches/generates the data and writes JSON to --out. If no executor is registered, the built-in executor runs the entry's 'cli' command directly. Sources gate completion by default. Set optionalForCompletionGating: true for enrichment-only source_defs that should not block task-completed.",
|
|
277
282
|
"items": { "$ref": "#/definitions/source_def" }
|
|
278
283
|
},
|
|
279
284
|
"compute": {
|
|
@@ -83,7 +83,7 @@ var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
|
|
|
83
83
|
"markdown",
|
|
84
84
|
"custom"
|
|
85
85
|
]);
|
|
86
|
-
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "
|
|
86
|
+
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "source_defs"]);
|
|
87
87
|
function validateNode(node) {
|
|
88
88
|
const errors = [];
|
|
89
89
|
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
@@ -109,15 +109,15 @@ function validateNode(node) {
|
|
|
109
109
|
if (n.requires != null && !Array.isArray(n.requires)) errors.push("requires: must be an array of strings");
|
|
110
110
|
if (n.provides != null) {
|
|
111
111
|
if (!Array.isArray(n.provides)) {
|
|
112
|
-
errors.push("provides: must be an array of { bindTo,
|
|
112
|
+
errors.push("provides: must be an array of { bindTo, ref } bindings");
|
|
113
113
|
} else {
|
|
114
114
|
n.provides.forEach((p, i) => {
|
|
115
115
|
if (!p || typeof p !== "object" || Array.isArray(p)) {
|
|
116
|
-
errors.push(`provides[${i}]: must be an object with bindTo and
|
|
116
|
+
errors.push(`provides[${i}]: must be an object with bindTo and ref`);
|
|
117
117
|
} else {
|
|
118
118
|
const b = p;
|
|
119
119
|
if (typeof b.bindTo !== "string" || !b.bindTo) errors.push(`provides[${i}]: missing required "bindTo" string`);
|
|
120
|
-
if (typeof b.
|
|
120
|
+
if (typeof b.ref !== "string" || !b.ref) errors.push(`provides[${i}]: missing required "ref" string`);
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
123
|
}
|
|
@@ -137,35 +137,35 @@ function validateNode(node) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
if (n.
|
|
141
|
-
if (!Array.isArray(n.
|
|
142
|
-
errors.push("
|
|
140
|
+
if (n.source_defs != null) {
|
|
141
|
+
if (!Array.isArray(n.source_defs)) {
|
|
142
|
+
errors.push("source_defs: must be an array");
|
|
143
143
|
} else {
|
|
144
144
|
const bindTos = /* @__PURE__ */ new Set();
|
|
145
145
|
const outputFiles = /* @__PURE__ */ new Set();
|
|
146
|
-
n.
|
|
146
|
+
n.source_defs.forEach((src, i) => {
|
|
147
147
|
if (!src || typeof src !== "object" || Array.isArray(src)) {
|
|
148
|
-
errors.push(`
|
|
148
|
+
errors.push(`source_defs[${i}]: must be an object`);
|
|
149
149
|
} else {
|
|
150
150
|
const s = src;
|
|
151
151
|
if (typeof s.bindTo !== "string" || !s.bindTo) {
|
|
152
|
-
errors.push(`
|
|
152
|
+
errors.push(`source_defs[${i}]: missing required "bindTo" property`);
|
|
153
153
|
} else {
|
|
154
154
|
if (bindTos.has(s.bindTo)) {
|
|
155
|
-
errors.push(`
|
|
155
|
+
errors.push(`source_defs[${i}]: bindTo "${s.bindTo}" is not unique across source_defs`);
|
|
156
156
|
}
|
|
157
157
|
bindTos.add(s.bindTo);
|
|
158
158
|
}
|
|
159
159
|
if (typeof s.outputFile !== "string" || !s.outputFile) {
|
|
160
|
-
errors.push(`
|
|
160
|
+
errors.push(`source_defs[${i}]: missing required "outputFile" property`);
|
|
161
161
|
} else {
|
|
162
162
|
if (outputFiles.has(s.outputFile)) {
|
|
163
|
-
errors.push(`
|
|
163
|
+
errors.push(`source_defs[${i}]: outputFile "${s.outputFile}" is not unique across source_defs`);
|
|
164
164
|
}
|
|
165
165
|
outputFiles.add(s.outputFile);
|
|
166
166
|
}
|
|
167
167
|
if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
|
|
168
|
-
errors.push(`
|
|
168
|
+
errors.push(`source_defs[${i}]: optionalForCompletionGating must be a boolean`);
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
});
|
|
@@ -200,14 +200,29 @@ function validateNode(node) {
|
|
|
200
200
|
}
|
|
201
201
|
return { ok: errors.length === 0, errors };
|
|
202
202
|
}
|
|
203
|
-
function enrichSources(
|
|
204
|
-
if (!
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
203
|
+
async function enrichSources(source_defs, context) {
|
|
204
|
+
if (!source_defs || source_defs.length === 0) return [];
|
|
205
|
+
const evalCtx = {
|
|
206
|
+
card_data: context.card_data ?? {},
|
|
207
|
+
requires: context.requires ?? {}
|
|
208
|
+
};
|
|
209
|
+
return Promise.all(
|
|
210
|
+
source_defs.map(async (src) => {
|
|
211
|
+
const _projections = {};
|
|
212
|
+
if (src.projections && typeof src.projections === "object" && !Array.isArray(src.projections)) {
|
|
213
|
+
for (const [key, expr] of Object.entries(src.projections)) {
|
|
214
|
+
if (typeof expr === "string" && expr.trim().length > 0) {
|
|
215
|
+
try {
|
|
216
|
+
_projections[key] = await jsonata2__default.default(expr).evaluate(evalCtx);
|
|
217
|
+
} catch {
|
|
218
|
+
_projections[key] = void 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { ...src, _projections };
|
|
224
|
+
})
|
|
225
|
+
);
|
|
211
226
|
}
|
|
212
227
|
var CardCompute = {
|
|
213
228
|
run,
|
|
@@ -908,6 +923,7 @@ function createReactiveGraph(configOrLive, options, executionId) {
|
|
|
908
923
|
const inputQueue = new MemoryJournal();
|
|
909
924
|
let live = "state" in configOrLive && "config" in configOrLive ? configOrLive : createLiveGraph(configOrLive, executionId);
|
|
910
925
|
let disposed = false;
|
|
926
|
+
const pendingHandlers = /* @__PURE__ */ new Set();
|
|
911
927
|
const handlers = new Map(Object.entries(initialHandlers));
|
|
912
928
|
const internalJournal = new MemoryJournal();
|
|
913
929
|
let draining = false;
|
|
@@ -950,7 +966,7 @@ function createReactiveGraph(configOrLive, options, executionId) {
|
|
|
950
966
|
const taskState = live.state.tasks[taskName];
|
|
951
967
|
if (!taskState || taskState.status !== "running") continue;
|
|
952
968
|
const callbackToken = encodeCallbackToken(taskName);
|
|
953
|
-
runPipeline(taskName, callbackToken, update).catch((error) => {
|
|
969
|
+
const p = runPipeline(taskName, callbackToken, update).catch((error) => {
|
|
954
970
|
if (disposed) return;
|
|
955
971
|
internalJournal.append({
|
|
956
972
|
type: "task-failed",
|
|
@@ -959,7 +975,10 @@ function createReactiveGraph(configOrLive, options, executionId) {
|
|
|
959
975
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
960
976
|
});
|
|
961
977
|
drain();
|
|
978
|
+
}).finally(() => {
|
|
979
|
+
pendingHandlers.delete(p);
|
|
962
980
|
});
|
|
981
|
+
pendingHandlers.add(p);
|
|
963
982
|
}
|
|
964
983
|
}
|
|
965
984
|
}
|
|
@@ -1019,7 +1038,7 @@ function createReactiveGraph(configOrLive, options, executionId) {
|
|
|
1019
1038
|
});
|
|
1020
1039
|
drain();
|
|
1021
1040
|
const callbackToken = encodeCallbackToken(taskName);
|
|
1022
|
-
runPipeline(taskName, callbackToken).catch((error) => {
|
|
1041
|
+
const p = runPipeline(taskName, callbackToken).catch((error) => {
|
|
1023
1042
|
if (disposed) return;
|
|
1024
1043
|
internalJournal.append({
|
|
1025
1044
|
type: "task-failed",
|
|
@@ -1028,7 +1047,10 @@ function createReactiveGraph(configOrLive, options, executionId) {
|
|
|
1028
1047
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1029
1048
|
});
|
|
1030
1049
|
drain();
|
|
1050
|
+
}).finally(() => {
|
|
1051
|
+
pendingHandlers.delete(p);
|
|
1031
1052
|
});
|
|
1053
|
+
pendingHandlers.add(p);
|
|
1032
1054
|
}
|
|
1033
1055
|
return {
|
|
1034
1056
|
push(event) {
|
|
@@ -1142,7 +1164,10 @@ function createReactiveGraph(configOrLive, options, executionId) {
|
|
|
1142
1164
|
getSchedule() {
|
|
1143
1165
|
return schedule(live);
|
|
1144
1166
|
},
|
|
1145
|
-
dispose() {
|
|
1167
|
+
async dispose(options2) {
|
|
1168
|
+
if (options2?.wait && pendingHandlers.size > 0) {
|
|
1169
|
+
await Promise.allSettled([...pendingHandlers]);
|
|
1170
|
+
}
|
|
1146
1171
|
disposed = true;
|
|
1147
1172
|
}
|
|
1148
1173
|
};
|
|
@@ -1164,7 +1189,7 @@ function toTaskConfig(card) {
|
|
|
1164
1189
|
function buildTokenProviders(cards) {
|
|
1165
1190
|
const tokenToCardId = /* @__PURE__ */ new Map();
|
|
1166
1191
|
for (const [cardId, card] of cards.entries()) {
|
|
1167
|
-
const bindings = card.provides && card.provides.length > 0 ? card.provides : [{ bindTo: cardId,
|
|
1192
|
+
const bindings = card.provides && card.provides.length > 0 ? card.provides : [{ bindTo: cardId, ref: "card_data" }];
|
|
1168
1193
|
for (const binding of bindings) tokenToCardId.set(binding.bindTo, cardId);
|
|
1169
1194
|
}
|
|
1170
1195
|
return tokenToCardId;
|
|
@@ -1310,14 +1335,14 @@ function createBoardLiveGraphRuntime(input, options = {}) {
|
|
|
1310
1335
|
requiresData[token] = providesData2[token];
|
|
1311
1336
|
}
|
|
1312
1337
|
const sourcesData = {};
|
|
1313
|
-
if (card.
|
|
1338
|
+
if (card.source_defs && card.source_defs.length > 0) {
|
|
1314
1339
|
const adapter = sourceAdapters[cardId] ?? defaultSourceAdapter;
|
|
1315
1340
|
const fetched = taskExecutor ? await taskExecutor({ card, input: inputArgs }) : adapter ? await adapter({ card, input: inputArgs }) : void 0;
|
|
1316
1341
|
if (fetched && typeof fetched === "object") {
|
|
1317
|
-
for (const src of card.
|
|
1342
|
+
for (const src of card.source_defs) {
|
|
1318
1343
|
if (Object.prototype.hasOwnProperty.call(fetched, src.bindTo)) {
|
|
1319
1344
|
sourcesData[src.bindTo] = fetched[src.bindTo];
|
|
1320
|
-
} else if (card.
|
|
1345
|
+
} else if (card.source_defs.length === 1) {
|
|
1321
1346
|
sourcesData[src.bindTo] = fetched;
|
|
1322
1347
|
}
|
|
1323
1348
|
}
|
|
@@ -1327,7 +1352,7 @@ function createBoardLiveGraphRuntime(input, options = {}) {
|
|
|
1327
1352
|
id: card.id,
|
|
1328
1353
|
card_data: deepClone(card.card_data ?? {}),
|
|
1329
1354
|
requires: requiresData,
|
|
1330
|
-
|
|
1355
|
+
source_defs: card.source_defs,
|
|
1331
1356
|
compute: card.compute
|
|
1332
1357
|
};
|
|
1333
1358
|
computeNode._sourcesData = sourcesData;
|
|
@@ -1336,8 +1361,8 @@ function createBoardLiveGraphRuntime(input, options = {}) {
|
|
|
1336
1361
|
}
|
|
1337
1362
|
const providesData = {};
|
|
1338
1363
|
if (card.provides && card.provides.length > 0) {
|
|
1339
|
-
for (const { bindTo,
|
|
1340
|
-
providesData[bindTo] = CardCompute.resolve(computeNode,
|
|
1364
|
+
for (const { bindTo, ref } of card.provides) {
|
|
1365
|
+
providesData[bindTo] = CardCompute.resolve(computeNode, ref);
|
|
1341
1366
|
}
|
|
1342
1367
|
} else {
|
|
1343
1368
|
providesData[card.id] = {
|