yaml-flow 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +42 -20
  2. package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
  3. package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
  4. package/browser/board-livegraph-engine.js.map +1 -0
  5. package/browser/live-cards.js +362 -38
  6. package/browser/live-cards.schema.json +20 -4
  7. package/dist/board-livegraph-runtime/index.cjs +210 -14
  8. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  9. package/dist/board-livegraph-runtime/index.d.cts +49 -5
  10. package/dist/board-livegraph-runtime/index.d.ts +49 -5
  11. package/dist/board-livegraph-runtime/index.js +209 -15
  12. package/dist/board-livegraph-runtime/index.js.map +1 -1
  13. package/dist/card-compute/index.cjs +63 -7
  14. package/dist/card-compute/index.cjs.map +1 -1
  15. package/dist/card-compute/index.d.cts +2 -2
  16. package/dist/card-compute/index.d.ts +2 -2
  17. package/dist/card-compute/index.js +63 -7
  18. package/dist/card-compute/index.js.map +1 -1
  19. package/dist/cli/board-live-cards-cli.cjs +664 -75
  20. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  21. package/dist/cli/board-live-cards-cli.d.cts +33 -5
  22. package/dist/cli/board-live-cards-cli.d.ts +33 -5
  23. package/dist/cli/board-live-cards-cli.js +661 -76
  24. package/dist/cli/board-live-cards-cli.js.map +1 -1
  25. package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
  26. package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
  27. package/dist/continuous-event-graph/index.cjs +47 -14
  28. package/dist/continuous-event-graph/index.cjs.map +1 -1
  29. package/dist/continuous-event-graph/index.d.cts +9 -9
  30. package/dist/continuous-event-graph/index.d.ts +9 -9
  31. package/dist/continuous-event-graph/index.js +47 -14
  32. package/dist/continuous-event-graph/index.js.map +1 -1
  33. package/dist/event-graph/index.cjs +29 -12
  34. package/dist/event-graph/index.cjs.map +1 -1
  35. package/dist/event-graph/index.d.cts +5 -5
  36. package/dist/event-graph/index.d.ts +5 -5
  37. package/dist/event-graph/index.js +29 -12
  38. package/dist/event-graph/index.js.map +1 -1
  39. package/dist/index.cjs +93 -20
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +7 -7
  42. package/dist/index.d.ts +7 -7
  43. package/dist/index.js +93 -20
  44. package/dist/index.js.map +1 -1
  45. package/dist/inference/index.cjs +29 -12
  46. package/dist/inference/index.cjs.map +1 -1
  47. package/dist/inference/index.d.cts +2 -2
  48. package/dist/inference/index.d.ts +2 -2
  49. package/dist/inference/index.js +29 -12
  50. package/dist/inference/index.js.map +1 -1
  51. package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
  52. package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
  53. package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
  54. package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
  55. package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
  56. package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
  57. package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
  58. package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
  59. package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
  60. package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
  61. package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
  62. package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
  63. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
  64. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
  65. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
  66. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
  67. package/examples/example-board/agent-instructions-cardlayout.md +28 -0
  68. package/examples/example-board/agent-instructions.md +603 -0
  69. package/examples/example-board/cards/card-concentration.json +42 -0
  70. package/examples/example-board/cards/card-market-prices.json +51 -0
  71. package/examples/example-board/cards/card-portfolio-action.json +19 -0
  72. package/examples/example-board/cards/card-portfolio-risks.json +19 -0
  73. package/examples/example-board/cards/card-portfolio-value.json +62 -0
  74. package/examples/example-board/cards/card-portfolio.json +44 -0
  75. package/examples/example-board/demo-chat-handler.js +373 -33
  76. package/examples/example-board/demo-server-config.json +0 -2
  77. package/examples/example-board/demo-server.js +46 -7
  78. package/examples/example-board/demo-shell-browser.html +75 -207
  79. package/examples/example-board/demo-shell-with-server.html +14 -9
  80. package/examples/example-board/demo-shell.html +1 -1
  81. package/examples/example-board/demo-task-executor.js +259 -41
  82. package/package.json +6 -2
  83. package/schema/live-cards.schema.json +20 -4
  84. package/browser/board-livegraph-runtime.js.map +0 -1
  85. package/examples/example-board/board.yaml +0 -23
  86. package/examples/example-board/bootstrap_payload.json +0 -1
  87. package/examples/example-board/cards/card-chain-region-alert.json +0 -39
  88. package/examples/example-board/cards/card-chain-region-totals.json +0 -26
  89. package/examples/example-board/cards/card-chain-top-region.json +0 -24
  90. package/examples/example-board/cards/card-ex-actions.json +0 -32
  91. package/examples/example-board/cards/card-ex-chart.json +0 -30
  92. package/examples/example-board/cards/card-ex-filter.json +0 -36
  93. package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
  94. package/examples/example-board/cards/card-ex-form.json +0 -91
  95. package/examples/example-board/cards/card-ex-list.json +0 -22
  96. package/examples/example-board/cards/card-ex-markdown.json +0 -17
  97. package/examples/example-board/cards/card-ex-metric.json +0 -19
  98. package/examples/example-board/cards/card-ex-narrative.json +0 -36
  99. package/examples/example-board/cards/card-ex-source-http.json +0 -28
  100. package/examples/example-board/cards/card-ex-source.json +0 -21
  101. package/examples/example-board/cards/card-ex-status.json +0 -35
  102. package/examples/example-board/cards/card-ex-table.json +0 -30
  103. package/examples/example-board/cards/card-ex-todo.json +0 -29
  104. package/examples/example-board/mock.db +0 -15
  105. package/examples/example-board/reusable-runtime-artifacts-adapter.js +0 -233
@@ -5,11 +5,21 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Example Board Demo (Server Runtime)</title>
7
7
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <style>
9
+ /* Scale down markdown headings inside cards to stay in proportion with card body text */
10
+ .lc-result h1 { font-size: 1rem; font-weight: 700; margin: 0.5rem 0 0.25rem; }
11
+ .lc-result h2 { font-size: 0.9rem; font-weight: 700; margin: 0.5rem 0 0.25rem; border-bottom: 1px solid #dee2e6; padding-bottom: 0.1rem; }
12
+ .lc-result h3 { font-size: 0.85rem; font-weight: 600; margin: 0.4rem 0 0.2rem; }
13
+ .lc-result p { font-size: 0.8rem; margin: 0 0 0.25rem; }
14
+ .lc-result ul, .lc-result ol { font-size: 0.8rem; margin: 0 0 0.25rem; padding-left: 1.2rem; }
15
+ .lc-result li { margin-bottom: 0.15rem; }
16
+ </style>
8
17
  <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/card-compute.js" onerror="this.onerror=null;this.src='../../browser/card-compute.js';"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/live-cards.js" onerror="this.onerror=null;this.src='../../browser/live-cards.js';"></script>
11
- <script src="./reusable-runtime-artifacts-adapter.js"></script>
12
- <script src="./reusable-board-runtime-client.js"></script>
18
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
19
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/card-compute.js"></script>
20
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/live-cards.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livegraph-engine.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livecards-runtime-client.js"></script>
13
23
  </head>
14
24
  <body class="bg-light">
15
25
  <div class="container-fluid py-3">
@@ -19,7 +29,6 @@
19
29
  <div class="small text-muted" id="boardDesc"></div>
20
30
  </div>
21
31
  <div class="d-flex align-items-center gap-2">
22
- <a class="btn btn-sm btn-outline-secondary" href="demo-shell-browser.html">Open Browser Runtime Shell</a>
23
32
  <select class="form-select form-select-sm" id="boardSelector" style="max-width:180px" title="Active board"></select>
24
33
  <button class="btn btn-sm btn-outline-success" id="addBoardBtn">+ Board</button>
25
34
  <div id="addBoardForm" class="d-flex align-items-center gap-1" style="display:none!important">
@@ -44,10 +53,6 @@
44
53
  Source execution and event processing happen on the server side.
45
54
  </div>
46
55
 
47
- <div class="alert alert-secondary small py-2 mb-3">
48
- Contract and decision notes live in <a href="./demo-shell.html#assumptions-and-tradeoffs">Assumptions and Tradeoffs</a>.
49
- </div>
50
-
51
56
  <div id="boardRoot">
52
57
  <div class="d-flex align-items-center justify-content-center" style="height: 72vh;">
53
58
  <div class="text-center">
@@ -17,7 +17,7 @@
17
17
  <div class="card-body">
18
18
  <h2 class="h5">Browser Runtime</h2>
19
19
  <p class="small text-muted mb-3">
20
- Uses <code>board-livegraph-runtime.js</code> directly in-browser.
20
+ Uses <code>board-livegraph-engine.js</code> directly in-browser.
21
21
  Source definitions remain opaque and are executed by an in-page task executor shim.
22
22
  </p>
23
23
  <a class="btn btn-primary" href="./demo-shell-browser.html">Open Browser Runtime Shell</a>
@@ -6,23 +6,89 @@
6
6
  * Protocol (invoked by board-live-cards-cli):
7
7
  * node demo-task-executor.js run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
8
8
  *
9
- * Expected source definition:
10
- * { "bindTo": "...", "outputFile": "...", "mock": "keyName" }
9
+ * Expected source definition (--in payload):
10
+ * {
11
+ * "bindTo": "...",
12
+ * "outputFile": "...",
13
+ * // custom fields authored on the source entry (e.g. mock, copilot, http, prompt_template, etc.)
14
+ * "cwd": "<card directory>",
15
+ * "boardDir": "<board runtime directory>",
16
+ * "_requires": { }, // upstream token data (from card requires[])
17
+ * "_sourcesData": { }, // already-fetched sources on this card
18
+ * "_computed_values": { } // computed_values from the card's compute stage
19
+ * }
11
20
  *
12
- * Behavior:
13
- * 1. Read mock.db (JSON file next to this script)
14
- * 2. Look up source.mock value as a key in mock.db
15
- * 3. Write corresponding value to --out file (as JSON)
16
- * 4. Exit 0 on success, exit 1 on error
21
+ * Supported source kinds (based on custom fields):
22
+ * - { mock: "key" } → look up key in MOCK_DB (hardcoded below)
23
+ * - { copilot: { prompt_template, args? } } → call Copilot CLI with interpolated prompt
24
+ * - { prompt_template: "..." } → shorthand copilot call (top-level template)
25
+ * - { http: { url, method?, headers?, args? }, tickersFrom? } → HTTP fetch (Node 18+ fetch)
26
+ * - { chartApi: { url, headers? }, tickersFrom } → Yahoo Finance chart API, one request per ticker;
27
+ * returns { quoteResponse: { result: [...] } } compatible with the quote API shape
28
+ * A real executor can also handle: graphapi, teams, mail, incidentdb, script, etc.
29
+ *
30
+ * http / chartApi source notes:
31
+ * - URL supports {{key}} interpolation (http) or {{ticker}} (chartApi)
32
+ * - tickersFrom: "tokenName.fieldName" extracts tickers from a _requires array
33
+ * - http and chartApi results are cached in os.tmpdir()/demo-executor-cache/ for 1 hour
34
+ * so Yahoo Finance is not hammered on every card refresh during demos
17
35
  */
18
36
 
19
37
  import fs from 'node:fs';
20
38
  import path from 'node:path';
39
+ import os from 'node:os';
40
+ import crypto from 'node:crypto';
21
41
  import { execFileSync } from 'node:child_process';
22
42
  import { fileURLToPath } from 'node:url';
23
43
 
24
44
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
- const MOCK_DB_PATH = path.join(__dirname, 'mock.db');
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Mock data — used when a source has { mock: "key" }.
48
+ // Edit these values to change the demo data without needing a mock.db file.
49
+ // ---------------------------------------------------------------------------
50
+ const MOCK_DB = {
51
+ quotes: {
52
+ quoteResponse: {
53
+ result: [
54
+ { symbol: 'AAPL', shortName: 'Apple Inc.', regularMarketPrice: 198.15, regularMarketChange: 2.15, regularMarketChangePercent: 1.10 },
55
+ { symbol: 'MSFT', shortName: 'Microsoft Corp.', regularMarketPrice: 415.32, regularMarketChange: -1.23, regularMarketChangePercent: -0.30 },
56
+ { symbol: 'GOOGL', shortName: 'Alphabet Inc.', regularMarketPrice: 174.89, regularMarketChange: 0.89, regularMarketChangePercent: 0.51 },
57
+ { symbol: 'TSLA', shortName: 'Tesla Inc.', regularMarketPrice: 247.12, regularMarketChange: 5.43, regularMarketChangePercent: 2.25 },
58
+ ],
59
+ error: null,
60
+ },
61
+ },
62
+ };
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Simple 1-hour file cache for HTTP / chartApi results.
66
+ // Stored in os.tmpdir()/demo-executor-cache/<hash>.json
67
+ // ---------------------------------------------------------------------------
68
+ const CACHE_DIR = path.join(os.tmpdir(), 'demo-executor-cache');
69
+ const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
70
+
71
+ function cacheKey(str) {
72
+ return crypto.createHash('sha1').update(str).digest('hex');
73
+ }
74
+
75
+ function readCache(key) {
76
+ const file = path.join(CACHE_DIR, `${key}.json`);
77
+ try {
78
+ const stat = fs.statSync(file);
79
+ if (Date.now() - stat.mtimeMs < CACHE_TTL_MS) {
80
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
81
+ }
82
+ } catch {}
83
+ return null;
84
+ }
85
+
86
+ function writeCache(key, value) {
87
+ try {
88
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
89
+ fs.writeFileSync(path.join(CACHE_DIR, `${key}.json`), JSON.stringify(value));
90
+ } catch {}
91
+ }
26
92
 
27
93
  function readJson(filePath) {
28
94
  return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
@@ -37,24 +103,57 @@ function interpolatePrompt(template, args) {
37
103
  });
38
104
  }
39
105
 
106
+ /**
107
+ * Fetch a URL using the system curl binary (synchronous, no Node event-loop handles).
108
+ * Throws if curl exits non-zero (e.g. HTTP 4xx/5xx with -f, or network error).
109
+ */
110
+ function curlFetchJson(url, method, headers) {
111
+ const bin = process.platform === 'win32' ? 'curl.exe' : 'curl';
112
+ // -s : silent (no progress bar)
113
+ // -S : show errors despite -s
114
+ // -f : fail (non-zero exit) on HTTP 4xx/5xx
115
+ // -L : follow redirects
116
+ // --max-time 10 : hard timeout
117
+ const args = ['-s', '-S', '-f', '-L', '--max-time', '10', '-X', method];
118
+ for (const [k, v] of Object.entries(headers)) {
119
+ args.push('-H', `${k}: ${v}`);
120
+ }
121
+ args.push(url);
122
+ const raw = execFileSync(bin, args, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
123
+ return JSON.parse(raw);
124
+ }
125
+
40
126
  function stripCopilotFooter(rawText) {
41
127
  const lines = String(rawText ?? '').split(/\r?\n/);
42
128
 
43
- // Remove trailing blank lines first.
44
- while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
129
+ // Strip CLI-level tool-call telemetry lines emitted by the copilot binary when
130
+ // --allow-all activates MCP tools. These are NOT model output the prompt cannot
131
+ // suppress them. They look like:
132
+ // ● Web Search (MCP: github-mcp-server) · ...
133
+ // └ {"type":"output_text",...}
134
+ const filtered = lines.filter(line => {
135
+ const t = line.trimStart();
136
+ return !(
137
+ /^[●•]\s+/.test(t) || // tool invocation lines
138
+ /^└\s+/.test(t) // tool result lines
139
+ );
140
+ });
141
+
142
+ // Remove trailing blank lines.
143
+ while (filtered.length > 0 && filtered[filtered.length - 1].trim() === '') filtered.pop();
45
144
 
46
145
  // Remove the standard trailing Copilot metadata footer, if present.
47
146
  if (
48
- lines.length >= 3 &&
49
- /^Changes\b/i.test(lines[lines.length - 3]) &&
50
- /^Requests\b/i.test(lines[lines.length - 2]) &&
51
- /^Tokens\b/i.test(lines[lines.length - 1])
147
+ filtered.length >= 3 &&
148
+ /^Changes\b/i.test(filtered[filtered.length - 3]) &&
149
+ /^Requests\b/i.test(filtered[filtered.length - 2]) &&
150
+ /^Tokens\b/i.test(filtered[filtered.length - 1])
52
151
  ) {
53
- lines.splice(lines.length - 3, 3);
152
+ filtered.splice(filtered.length - 3, 3);
54
153
  }
55
154
 
56
- while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
57
- return lines.join('\n');
155
+ while (filtered.length > 0 && filtered[filtered.length - 1].trim() === '') filtered.pop();
156
+ return filtered.join('\n');
58
157
  }
59
158
 
60
159
  function resolveCopilotPrompt(sourceDef) {
@@ -62,8 +161,16 @@ function resolveCopilotPrompt(sourceDef) {
62
161
  const template = cfg.prompt_template ?? sourceDef.prompt_template;
63
162
  const args = cfg.args ?? cfg.prompt_args ?? sourceDef.prompt_args ?? sourceDef.args ?? {};
64
163
 
65
- // Merge explicit args with context from card-handler (_requires includes card-level computed values)
66
- const interpolationContext = { ...sourceDef._requires, ...args };
164
+ // Merge all injected context for template interpolation.
165
+ // _requires = upstream token data, _computed_values = card compute stage outputs,
166
+ // _sourcesData = already-fetched sources on this card.
167
+ // Explicit args defined on the source take highest precedence.
168
+ const interpolationContext = {
169
+ ...sourceDef._requires,
170
+ ...sourceDef._sourcesData,
171
+ ...sourceDef._computed_values,
172
+ ...args,
173
+ };
67
174
 
68
175
  if (!template || typeof template !== 'string') return null;
69
176
  return interpolatePrompt(template, interpolationContext);
@@ -193,7 +300,121 @@ function runSourceFetchSubcommand(argv) {
193
300
 
194
301
  let resultValue;
195
302
 
196
- if (sourceDef.copilot || sourceDef.prompt_template) {
303
+ if (sourceDef.chartApi) {
304
+ // ---------------------------------------------------------------------------
305
+ // chartApi source kind — Yahoo Finance v8/finance/chart (free, per-ticker)
306
+ // Uses curl (synchronous subprocess) to avoid Node.js libuv handle issues.
307
+ // ---------------------------------------------------------------------------
308
+ const chartCfg = sourceDef.chartApi;
309
+ const headers = { ...(chartCfg.headers || {}) };
310
+
311
+ // Extract tickers array from _requires via tickersFrom
312
+ let tickers = [];
313
+ if (sourceDef.tickersFrom) {
314
+ const dotIdx = sourceDef.tickersFrom.indexOf('.');
315
+ if (dotIdx > 0) {
316
+ const tokenName = sourceDef.tickersFrom.slice(0, dotIdx);
317
+ const fieldName = sourceDef.tickersFrom.slice(dotIdx + 1);
318
+ const arr = sourceDef._requires?.[tokenName];
319
+ if (Array.isArray(arr)) {
320
+ tickers = arr.map(h => h[fieldName]).filter(Boolean);
321
+ }
322
+ }
323
+ }
324
+
325
+ if (tickers.length === 0) {
326
+ console.warn('[demo-task-executor] chartApi: tickersFrom resolved to empty list — falling back to mock');
327
+ } else {
328
+ const chartCacheKey = cacheKey('chartApi:' + tickers.sort().join(',') + chartCfg.url);
329
+ const cached = readCache(chartCacheKey);
330
+ if (cached) {
331
+ console.warn(`[demo-task-executor] chartApi: cache hit for [${tickers.join(', ')}]`);
332
+ resultValue = cached;
333
+ } else {
334
+ try {
335
+ const results = [];
336
+ for (const ticker of tickers) {
337
+ const url = interpolatePrompt(chartCfg.url, { ticker });
338
+ const data = curlFetchJson(url, 'GET', headers);
339
+ const meta = data?.chart?.result?.[0]?.meta;
340
+ if (!meta) throw new Error(`No chart meta for ${ticker}`);
341
+ // Map to quote-compatible shape; compute change from chartPreviousClose
342
+ const price = meta.regularMarketPrice ?? 0;
343
+ const prevClose = meta.chartPreviousClose ?? price;
344
+ const change = price - prevClose;
345
+ const changePct = prevClose !== 0 ? (change / prevClose) * 100 : 0;
346
+ results.push({
347
+ symbol: meta.symbol ?? ticker,
348
+ shortName: meta.shortName ?? meta.longName ?? ticker,
349
+ regularMarketPrice: price,
350
+ regularMarketChange: change,
351
+ regularMarketChangePercent: changePct,
352
+ });
353
+ }
354
+ resultValue = { quoteResponse: { result: results, error: null } };
355
+ writeCache(chartCacheKey, resultValue);
356
+ } catch (chartErr) {
357
+ fail(`chartApi fetch failed: ${chartErr.message}`, errFile);
358
+ }
359
+ }
360
+ }
361
+
362
+ if (resultValue === undefined) {
363
+ fail('chartApi: no tickers resolved — cannot fetch', errFile);
364
+ }
365
+
366
+ } else if (sourceDef.http) {
367
+ // ---------------------------------------------------------------------------
368
+ // HTTP source kind — uses curl (synchronous subprocess)
369
+ // ---------------------------------------------------------------------------
370
+ const httpCfg = sourceDef.http;
371
+
372
+ // Build tickers string if tickersFrom is specified on the source
373
+ // e.g. tickersFrom: "holdings.ticker" → joins _requires.holdings[*].ticker with ','
374
+ const httpArgs = { ...(httpCfg.args || {}) };
375
+ if (sourceDef.tickersFrom) {
376
+ const dotIdx = sourceDef.tickersFrom.indexOf('.');
377
+ if (dotIdx > 0) {
378
+ const tokenName = sourceDef.tickersFrom.slice(0, dotIdx);
379
+ const fieldName = sourceDef.tickersFrom.slice(dotIdx + 1);
380
+ const arr = sourceDef._requires?.[tokenName];
381
+ if (Array.isArray(arr)) {
382
+ httpArgs.tickers = arr.map(h => h[fieldName]).filter(Boolean).join(',');
383
+ }
384
+ }
385
+ }
386
+
387
+ // Interpolate URL template with all available context
388
+ const urlContext = {
389
+ ...(sourceDef._requires || {}),
390
+ ...(sourceDef._computed_values || {}),
391
+ ...httpArgs,
392
+ };
393
+ const url = interpolatePrompt(httpCfg.url, urlContext);
394
+ const method = (httpCfg.method || 'GET').toUpperCase();
395
+ const headers = { ...(httpCfg.headers || {}) };
396
+
397
+ // Skip fetch entirely if tickers ended up empty (guard against empty ?symbols=)
398
+ const httpFetchSkipped = sourceDef.tickersFrom && !httpArgs.tickers;
399
+
400
+ const httpCacheKey = cacheKey(`http:${method}:${url}`);
401
+ const httpCached = readCache(httpCacheKey);
402
+ if (httpCached && !httpFetchSkipped) {
403
+ console.warn(`[demo-task-executor] http: cache hit for ${url}`);
404
+ resultValue = httpCached;
405
+ } else {
406
+ try {
407
+ if (httpFetchSkipped) {
408
+ throw new Error('tickersFrom resolved to empty list — skipping fetch');
409
+ }
410
+ resultValue = curlFetchJson(url, method, headers);
411
+ writeCache(httpCacheKey, resultValue);
412
+ } catch (httpErr) {
413
+ fail(`HTTP fetch failed: ${httpErr.message}`, errFile);
414
+ }
415
+ }
416
+
417
+ } else if (sourceDef.copilot || sourceDef.prompt_template) {
197
418
  const prompt = resolveCopilotPrompt(sourceDef);
198
419
  if (!prompt) {
199
420
  fail('Source definition missing copilot.prompt_template (or prompt_template)', errFile);
@@ -207,28 +428,23 @@ function runSourceFetchSubcommand(argv) {
207
428
  fail(`copilot invocation failed: ${msg}`, errFile);
208
429
  }
209
430
 
210
- resultValue = stripCopilotFooter(rawOutput);
211
- } else {
212
- // Default mode: mockdb lookup
213
- let mockDb;
431
+ const cleaned = stripCopilotFooter(rawOutput);
432
+ // If the response is a JSON object/array, parse it so downstream compute
433
+ // can reference fields directly (e.g. fetched_sources.analysis.mix).
214
434
  try {
215
- if (!fs.existsSync(MOCK_DB_PATH)) {
216
- fail(`mock.db not found at ${MOCK_DB_PATH}`, errFile);
217
- }
218
- mockDb = readJson(MOCK_DB_PATH);
219
- } catch (err) {
220
- fail(`Cannot parse mock.db: ${String(err && err.message || err)}`, errFile);
221
- }
222
-
223
- const mockKey = sourceDef.mock;
224
- if (!mockKey) {
225
- fail('Source definition missing "mock" field (key to lookup)', errFile);
435
+ const parsed = JSON.parse(cleaned);
436
+ resultValue = (parsed && typeof parsed === 'object') ? parsed : cleaned;
437
+ } catch {
438
+ resultValue = cleaned;
226
439
  }
227
-
228
- resultValue = mockDb[mockKey];
440
+ } else if (sourceDef.mock) {
441
+ // MOCK_DB lookup — data hardcoded at the top of this file
442
+ resultValue = MOCK_DB[sourceDef.mock];
229
443
  if (resultValue === undefined) {
230
- fail(`Key "${mockKey}" not found in mock.db`, errFile);
444
+ fail(`Key "${sourceDef.mock}" not found in MOCK_DB`, errFile);
231
445
  }
446
+ } else {
447
+ fail('Source definition has no recognised kind (copilot, http, chartApi, mock)', errFile);
232
448
  }
233
449
 
234
450
  // Write result to --out as JSON payload, same contract as current mock mode.
@@ -238,10 +454,9 @@ function runSourceFetchSubcommand(argv) {
238
454
  fail(`Cannot write output file: ${String(err && err.message || err)}`, errFile);
239
455
  }
240
456
 
241
- process.exit(0);
242
457
  }
243
458
 
244
- function main() {
459
+ async function main() {
245
460
  const sub = process.argv[2];
246
461
  if (sub === 'run-source-fetch') {
247
462
  runSourceFetchSubcommand(process.argv.slice(3));
@@ -252,4 +467,7 @@ function main() {
252
467
  process.exit(0);
253
468
  }
254
469
 
255
- main();
470
+ main().catch(err => {
471
+ console.error(`[demo-task-executor] fatal: ${err && err.message || err}`);
472
+ process.exit(1);
473
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml-flow",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Unified workflow engine: step-machine (sequential) + event-graph (stateless DAG) with pluggable storage",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -82,7 +82,9 @@
82
82
  "types": "./dist/runtime-artifacts/index.d.ts",
83
83
  "import": "./dist/runtime-artifacts/index.js",
84
84
  "require": "./dist/runtime-artifacts/index.cjs"
85
- }
85
+ },
86
+ "./board-livecards-server-runtime": "./board-livecards-server-runtime.js",
87
+ "./package.json": "./package.json"
86
88
  },
87
89
  "browser": {
88
90
  "./stores/file": false
@@ -90,6 +92,7 @@
90
92
  "files": [
91
93
  "board-live-cards-cli.js",
92
94
  "step-machine-cli.js",
95
+ "board-livecards-server-runtime.js",
93
96
  "dist",
94
97
  "schema",
95
98
  "browser",
@@ -98,6 +101,7 @@
98
101
  "scripts": {
99
102
  "build": "tsup",
100
103
  "build:browser": "tsup --config tsup.browser.config.ts",
104
+ "build:example": "node example-board-src/build.js",
101
105
  "dev": "tsup --watch",
102
106
  "test": "vitest",
103
107
  "test:run": "vitest run",
@@ -100,9 +100,9 @@
100
100
  },
101
101
 
102
102
  "source_def": {
103
- "description": "One source entry. The engine only cares about 'bindTo' (compute namespace key) and 'outputFile' (delivery signal). 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.",
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 sources 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
- "required": ["bindTo"],
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" },
@@ -119,7 +119,7 @@
119
119
  "properties": {
120
120
  "id": { "type": "string", "description": "Optional element ID for targeted updates" },
121
121
  "kind": {
122
- "enum": ["metric", "table", "chart", "form", "filter", "list",
122
+ "enum": ["metric", "table", "editable-table", "chart", "form", "filter", "list",
123
123
  "notes", "todo", "alert", "narrative", "badge", "text",
124
124
  "markdown", "custom", "actions"]
125
125
  },
@@ -227,7 +227,7 @@
227
227
  "view": { "$ref": "#/definitions/view" },
228
228
  "card_data": {
229
229
  "type": "object",
230
- "description": "Authored card data supplied in the card definition",
230
+ "description": "Authored card data and runtime metadata. Includes uploaded-file metadata maintained by host handlers and inference evaluation results.",
231
231
  "properties": {
232
232
  "files": {
233
233
  "type": "array",
@@ -255,6 +255,18 @@
255
255
  },
256
256
  "additionalProperties": false
257
257
  }
258
+ },
259
+ "llm_task_completion_inference": {
260
+ "type": "object",
261
+ "description": "Runtime state written by the inference adapter (advanced/undocumented). Prefer the standard sources → compute → provides pattern for LLM-based signals.",
262
+ "properties": {
263
+ "inferenceRequested": { "type": "string", "format": "date-time", "description": "Timestamp when the latest inference request was initiated" },
264
+ "inferenceCompletedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the latest inference request completed" },
265
+ "isTaskCompleted": { "type": "boolean", "description": "Whether the task is considered complete by the adapter" },
266
+ "reasoning": { "type": "string", "description": "Explanation of completion decision" },
267
+ "evidence": { "type": "array", "description": "Supporting evidence from sources/compute" }
268
+ },
269
+ "additionalProperties": true
258
270
  }
259
271
  },
260
272
  "additionalProperties": true
@@ -268,6 +280,10 @@
268
280
  "type": "array",
269
281
  "description": "Ordered array of compute steps. Each reads card_data.*/requires.*/fetched_sources.*/computed_values.* and writes to ephemeral computed_values[bindTo].",
270
282
  "items": { "$ref": "#/definitions/compute_step" }
283
+ },
284
+ "when_is_task_completed": {
285
+ "type": "string",
286
+ "description": "Advanced/undocumented: invokes a registered inference adapter instead of the default source-delivery completion gate. Prefer the standard pattern: use an LLM-backed source, compute a verdict token, and publish it via provides."
271
287
  }
272
288
  }
273
289
  }