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.
- package/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +42 -20
- package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
- package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
- package/browser/board-livegraph-engine.js.map +1 -0
- package/browser/live-cards.js +362 -38
- package/browser/live-cards.schema.json +20 -4
- package/dist/board-livegraph-runtime/index.cjs +210 -14
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +49 -5
- package/dist/board-livegraph-runtime/index.d.ts +49 -5
- package/dist/board-livegraph-runtime/index.js +209 -15
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/card-compute/index.cjs +63 -7
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +2 -2
- package/dist/card-compute/index.d.ts +2 -2
- package/dist/card-compute/index.js +63 -7
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +664 -75
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +33 -5
- package/dist/cli/board-live-cards-cli.d.ts +33 -5
- package/dist/cli/board-live-cards-cli.js +661 -76
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
- package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
- package/dist/continuous-event-graph/index.cjs +47 -14
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +9 -9
- package/dist/continuous-event-graph/index.d.ts +9 -9
- package/dist/continuous-event-graph/index.js +47 -14
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +29 -12
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +5 -5
- package/dist/event-graph/index.d.ts +5 -5
- package/dist/event-graph/index.js +29 -12
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +93 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +93 -20
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +29 -12
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +29 -12
- package/dist/inference/index.js.map +1 -1
- package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
- package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
- package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
- package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
- package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
- package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
- package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
- package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
- package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
- package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
- package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
- package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
- package/examples/example-board/agent-instructions-cardlayout.md +28 -0
- package/examples/example-board/agent-instructions.md +603 -0
- package/examples/example-board/cards/card-concentration.json +42 -0
- package/examples/example-board/cards/card-market-prices.json +51 -0
- package/examples/example-board/cards/card-portfolio-action.json +19 -0
- package/examples/example-board/cards/card-portfolio-risks.json +19 -0
- package/examples/example-board/cards/card-portfolio-value.json +62 -0
- package/examples/example-board/cards/card-portfolio.json +44 -0
- package/examples/example-board/demo-chat-handler.js +373 -33
- package/examples/example-board/demo-server-config.json +0 -2
- package/examples/example-board/demo-server.js +46 -7
- package/examples/example-board/demo-shell-browser.html +75 -207
- package/examples/example-board/demo-shell-with-server.html +14 -9
- package/examples/example-board/demo-shell.html +1 -1
- package/examples/example-board/demo-task-executor.js +259 -41
- package/package.json +6 -2
- package/schema/live-cards.schema.json +20 -4
- package/browser/board-livegraph-runtime.js.map +0 -1
- package/examples/example-board/board.yaml +0 -23
- package/examples/example-board/bootstrap_payload.json +0 -1
- package/examples/example-board/cards/card-chain-region-alert.json +0 -39
- package/examples/example-board/cards/card-chain-region-totals.json +0 -26
- package/examples/example-board/cards/card-chain-top-region.json +0 -24
- package/examples/example-board/cards/card-ex-actions.json +0 -32
- package/examples/example-board/cards/card-ex-chart.json +0 -30
- package/examples/example-board/cards/card-ex-filter.json +0 -36
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
- package/examples/example-board/cards/card-ex-form.json +0 -91
- package/examples/example-board/cards/card-ex-list.json +0 -22
- package/examples/example-board/cards/card-ex-markdown.json +0 -17
- package/examples/example-board/cards/card-ex-metric.json +0 -19
- package/examples/example-board/cards/card-ex-narrative.json +0 -36
- package/examples/example-board/cards/card-ex-source-http.json +0 -28
- package/examples/example-board/cards/card-ex-source.json +0 -21
- package/examples/example-board/cards/card-ex-status.json +0 -35
- package/examples/example-board/cards/card-ex-table.json +0 -30
- package/examples/example-board/cards/card-ex-todo.json +0 -29
- package/examples/example-board/mock.db +0 -15
- 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/
|
|
10
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow
|
|
11
|
-
<script src="
|
|
12
|
-
<script src="
|
|
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-
|
|
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
|
-
* {
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
/^Changes\b/i.test(
|
|
50
|
-
/^Requests\b/i.test(
|
|
51
|
-
/^Tokens\b/i.test(
|
|
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
|
-
|
|
152
|
+
filtered.splice(filtered.length - 3, 3);
|
|
54
153
|
}
|
|
55
154
|
|
|
56
|
-
while (
|
|
57
|
-
return
|
|
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
|
|
66
|
-
|
|
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.
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
//
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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 "${
|
|
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.
|
|
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
|
|
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
|
|
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
|
}
|