yaml-flow 5.2.8 → 5.4.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 (38) hide show
  1. package/board-livecards-server-runtime.js +15 -66
  2. package/browser/board-livegraph-engine.js +4 -1
  3. package/browser/board-livegraph-engine.js.map +1 -1
  4. package/browser/card-compute.js +1 -1
  5. package/browser/live-cards.js +178 -144
  6. package/browser/live-cards.schema.json +1 -1
  7. package/dist/board-livegraph-runtime/index.cjs +4 -1
  8. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  9. package/dist/board-livegraph-runtime/index.js +4 -1
  10. package/dist/board-livegraph-runtime/index.js.map +1 -1
  11. package/dist/card-compute/index.cjs +5 -1
  12. package/dist/card-compute/index.cjs.map +1 -1
  13. package/dist/card-compute/index.js +5 -1
  14. package/dist/card-compute/index.js.map +1 -1
  15. package/dist/cli/board-live-cards-cli.cjs +2416 -2113
  16. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  17. package/dist/cli/board-live-cards-cli.d.cts +59 -113
  18. package/dist/cli/board-live-cards-cli.d.ts +59 -113
  19. package/dist/cli/board-live-cards-cli.js +2413 -2109
  20. package/dist/cli/board-live-cards-cli.js.map +1 -1
  21. package/dist/continuous-event-graph/index.cjs +4 -1
  22. package/dist/continuous-event-graph/index.cjs.map +1 -1
  23. package/dist/continuous-event-graph/index.js +4 -1
  24. package/dist/continuous-event-graph/index.js.map +1 -1
  25. package/dist/index.cjs +5 -1
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +5 -1
  28. package/dist/index.js.map +1 -1
  29. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +4 -4
  30. package/examples/example-board/agent-instructions-cardlayout.md +28 -0
  31. package/examples/example-board/agent-instructions.md +4 -5
  32. package/examples/example-board/cards/card-rebalance-sim.json +13 -3
  33. package/examples/example-board/demo-server.js +77 -11
  34. package/examples/example-board/demo-shell-browser.html +4 -4
  35. package/examples/example-board/demo-shell-with-server.html +4 -4
  36. package/examples/example-board/demo-task-executor.js +22 -2
  37. package/package.json +1 -1
  38. package/schema/live-cards.schema.json +1 -1
@@ -79,14 +79,14 @@ function cliCommand() {
79
79
 
80
80
  function runCli(args, capture = false) {
81
81
  const { cmd, prefixArgs } = cliCommand();
82
+ const env = { ...process.env };
83
+ // This demo needs real worker dispatch; suppressing spawn keeps source/inference tasks in running state.
84
+ delete env.BOARD_LIVE_CARDS_NO_SPAWN;
82
85
  const result = spawnSync(cmd, [...prefixArgs, ...args], {
83
86
  stdio: capture ? 'pipe' : 'inherit',
84
87
  shell: false,
85
88
  windowsHide: true,
86
- env: {
87
- ...process.env,
88
- BOARD_LIVE_CARDS_NO_SPAWN: '1',
89
- },
89
+ env,
90
90
  encoding: capture ? 'utf-8' : undefined,
91
91
  });
92
92
 
@@ -26,3 +26,31 @@
26
26
  - `board.col` — Bootstrap 12-column span: `3`=quarter, `4`=third, `6`=half, `8`=two-thirds, `12`=full
27
27
  - `board.order` — ascending integer, controls vertical sort in board view
28
28
  - `canvas` — pixel coordinates/size for drag-layout (canvas mode). `h` must be tall enough for all rendered content — a card with metrics + a 4-row table typically needs 400–500px. Too small a height causes an in-card scrollbar; when in doubt, size generously.
29
+
30
+ ---
31
+
32
+ ## LLM View-Type Guidance (for `kind: "ref"`)
33
+
34
+ When a source uses Copilot/LLM and the card renders via a `ref` element, the model should emit a `_view` hint alongside the data.
35
+
36
+ - Keep `_view.kind` within the renderer whitelist only:
37
+ `table`, `editable-table`, `chart`, `metric`, `list`, `badge`, `text`, `narrative`, `markdown`, `form`, `filter`, `todo`, `alert`
38
+ - Default to `table` when uncertain.
39
+ - Use `editable-table` for user-adjustable tabular rows and include `data.writeTo` to a `card_data` path.
40
+ - Use `chart` only when one clear category/value mapping exists; include `data.chartType` and `data.columns: [labelField, valueField]`.
41
+ - Keep `_view.data` minimal. Static card JSON may override it for safety.
42
+
43
+ Recommended response shape:
44
+
45
+ ```json
46
+ {
47
+ "<data_key>": [ ... ],
48
+ "_view": {
49
+ "kind": "editable-table",
50
+ "data": {
51
+ "writeTo": "card_data.<key>",
52
+ "columns": ["field1", "field2"]
53
+ }
54
+ }
55
+ }
56
+ ```
@@ -509,11 +509,10 @@ The optional `projections` map lets a source definition declare which upstream d
509
509
  "outputFile": "quotes.json",
510
510
  "projections": {
511
511
  "holdings": "requires.holdings",
512
- "topHoldings": "requires.holdings[weight > 0.05]",
513
- "threshold": "card_data.threshold"
512
+ "url_list": "requires.holdings.ticker.('https://query1.finance.yahoo.com/v8/finance/chart/' & $ & '?interval=1d&range=1d')"
514
513
  },
515
- "chartApi": {
516
- "tickersFrom": "holdings.ticker"
514
+ "url-list": {
515
+ "cacheTimeout": 3600
517
516
  }
518
517
  }
519
518
  ]
@@ -538,7 +537,7 @@ node board-live-cards-cli.js describe-task-executor-capabilities --rg <boardDir>
538
537
  ```
539
538
 
540
539
  This invokes the executor's `describe-capabilities` subcommand and prints its capabilities JSON to stdout. The output includes:
541
- - **`sourceKinds`** — every source kind the executor handles (e.g. `mock`, `copilot`, `http`, `chartApi`), each with:
540
+ - **`sourceKinds`** — every source kind the executor handles (e.g. `mock`, `copilot`, `workiq`, `url`, `url-list`), each with:
542
541
  - `description` — what the kind does
543
542
  - `inputSchema` — the exact `customFields` the executor expects on the source entry
544
543
  - `outputShape` — the shape of the JSON written to `--out`
@@ -19,7 +19,7 @@
19
19
  "portfolio_action": "requires.portfolio_action"
20
20
  },
21
21
  "copilot": {
22
- "prompt_template": "You are a portfolio rebalance advisor. Using the intelligence analysis and current holdings below, propose specific trades.\n\nCurrent holdings:\n{{holdings}}\nFields: ticker, quantity, cost_basis ($).\n\nPositions (with live prices):\n{{positions}}\nFields: ticker, quantity, cost_basis, price ($), value ($), gain_$, gain_%, chg_$, chg_pct.\n\nPortfolio intelligence:\n- Mix: {{portfolio_mix}}\n- Risks: {{portfolio_risks}}\n- Action signal: {{portfolio_action}}\n\nIMPORTANT OUTPUT RULES:\n- Return ONLY a valid JSON object. No markdown, no preamble, no trailing text.\n- Start with { and end with }.\n- proposed_trades must be an array of objects with EXACTLY these fields per entry:\n ticker (string), direction (\"reduce\" or \"increase\"), current_qty (number, current shares held), change (number, shares to trade), proposed_qty (number, resulting shares after trade), price (number, current price per share), trade_value (number, change * price rounded to 2 decimals), reason (string, one short sentence).\n- Include one entry per ticker you recommend changing. Do not include tickers with no change.\n\n{\n \"proposed_trades\": [\n {\"ticker\": \"AAPL\", \"direction\": \"reduce\", \"current_qty\": 15, \"change\": 5, \"proposed_qty\": 10, \"price\": 210.50, \"trade_value\": 1052.50, \"reason\": \"overweight at 41%, trim before earnings\"}\n ]\n}"
22
+ "prompt_template": "You are a portfolio rebalance advisor. Using the intelligence analysis and current holdings below, propose specific trades.\n\nCurrent holdings:\n{{holdings}}\nFields: ticker, quantity, cost_basis ($).\n\nPositions (with live prices):\n{{positions}}\nFields: ticker, quantity, cost_basis, price ($), value ($), gain_$, gain_%, chg_$, chg_pct.\n\nPortfolio intelligence:\n- Mix: {{portfolio_mix}}\n- Risks: {{portfolio_risks}}\n- Action signal: {{portfolio_action}}\n\n{{view_kind_guidance}}\n\n{{card_layout_guidance}}\n\nIMPORTANT OUTPUT RULES:\n- Return ONLY a valid JSON object. No markdown, no preamble, no trailing text.\n- Start with { and end with }.\n- proposed_trades must be an array of objects with EXACTLY these fields per entry:\n ticker (string), direction (\"reduce\" or \"increase\"), current_qty (number, current shares held), change (number, shares to trade), proposed_qty (number, resulting shares after trade), price (number, current price per share), trade_value (number, change * price rounded to 2 decimals), reason (string, one short sentence).\n- Include one entry per ticker you recommend changing. Do not include tickers with no change.\n- Include a top-level _view object for a ref element with this shape:\n { \"kind\": \"editable-table\"|\"table\"|\"chart\", \"data\": { ... } }\n- If kind is editable-table or table, include data.columns with the exact proposed_trades fields in desired order.\n- If kind is editable-table, also include data.writeTo = \"card_data.proposed_trades\".\n- If kind is chart, include data.chartType and data.columns as [\"ticker\", \"trade_value\"].\n- If unsure, choose editable-table.\n\n{\n \"proposed_trades\": [\n {\"ticker\": \"AAPL\", \"direction\": \"reduce\", \"current_qty\": 15, \"change\": 5, \"proposed_qty\": 10, \"price\": 210.50, \"trade_value\": 1052.50, \"reason\": \"overweight at 41%, trim before earnings\"}\n ],\n \"_view\": {\n \"kind\": \"editable-table\",\n \"data\": {\n \"writeTo\": \"card_data.proposed_trades\",\n \"columns\": [\"ticker\", \"direction\", \"current_qty\", \"change\", \"proposed_qty\", \"price\", \"trade_value\", \"reason\"]\n }\n }\n}"
23
23
  }
24
24
  }
25
25
  ],
@@ -29,10 +29,12 @@
29
29
  "view": {
30
30
  "elements": [
31
31
  {
32
- "kind": "editable-table",
32
+ "kind": "ref",
33
33
  "label": "Proposed Trades",
34
34
  "data": {
35
35
  "bind": "fetched_sources.rebalance.proposed_trades",
36
+ "viewBind": "fetched_sources.rebalance._view",
37
+ "fallbackKind": "editable-table",
36
38
  "writeTo": "card_data.proposed_trades",
37
39
  "columns": ["ticker", "direction", "current_qty", "change", "proposed_qty", "price", "trade_value", "reason"],
38
40
  "schema": {
@@ -45,13 +47,21 @@
45
47
  }
46
48
  }
47
49
  }
50
+ },
51
+ {
52
+ "kind": "notes",
53
+ "label": "Notes",
54
+ "data": {
55
+ "bind": "card_data.notes",
56
+ "writeTo": "card_data.notes"
57
+ }
48
58
  }
49
59
  ],
50
60
  "layout": {
51
61
  "board": { "col": 6, "order": 7 },
52
62
  "canvas": { "x": 840, "y": 480, "w": 500, "h": 280 }
53
63
  },
54
- "features": { "refresh": true, "notes": true }
64
+ "features": { "refresh": true }
55
65
  },
56
66
  "card_data": {}
57
67
  }
@@ -126,13 +126,67 @@ function jsonReply(res, status, payload) {
126
126
  res.end(body);
127
127
  }
128
128
 
129
+ // ---------------------------------------------------------------------------
130
+ // Card preparation — host-level concern, not a reusable runtime concern.
131
+ // Copies source card JSON files into the runtime's tmpCardsDir and writes
132
+ // the concatenated copilot-instructions.md at the board setup root.
133
+ // The runtime's bootstrap operations assume cards are already in tmpCardsDir;
134
+ // the host (this file) decides how and when they get there.
135
+ // ---------------------------------------------------------------------------
136
+
137
+ const _demoPrepSetupDone = new Map(); // boardId → true
138
+
139
+ function isDemoSetupDone(boardId, service) {
140
+ return _demoPrepSetupDone.get(boardId) === true && fs.existsSync(service.tmpCardsDir);
141
+ }
142
+
143
+ function demoPrepSetup(boardId, service) {
144
+ const { tmpSurfaceDir, tmpCardsDir, cardsDir, gandalfCardsDir, tmpGandalfCardsDir, boardDir } = service;
145
+
146
+ fs.mkdirSync(tmpSurfaceDir, { recursive: true });
147
+ fs.rmSync(tmpCardsDir, { recursive: true, force: true });
148
+ fs.mkdirSync(tmpCardsDir, { recursive: true });
149
+
150
+ const entries = fs.readdirSync(cardsDir, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ if (!entry.isFile()) continue;
153
+ if (!entry.name.toLowerCase().endsWith('.json')) continue;
154
+ fs.copyFileSync(path.join(cardsDir, entry.name), path.join(tmpCardsDir, entry.name));
155
+ }
156
+
157
+ // Copy gandalf-card templates if gandalfCardsDir is configured.
158
+ if (gandalfCardsDir && fs.existsSync(gandalfCardsDir)) {
159
+ fs.rmSync(tmpGandalfCardsDir, { recursive: true, force: true });
160
+ fs.mkdirSync(tmpGandalfCardsDir, { recursive: true });
161
+ for (const entry of fs.readdirSync(gandalfCardsDir, { withFileTypes: true })) {
162
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.json')) continue;
163
+ fs.copyFileSync(path.join(gandalfCardsDir, entry.name), path.join(tmpGandalfCardsDir, entry.name));
164
+ }
165
+ }
166
+
167
+ // Concatenate agent-instructions*.md into copilot-instructions.md at boardSetupRoot.
168
+ const boardSetupRoot = path.dirname(boardDir);
169
+ const srcDir = path.dirname(cardsDir);
170
+ const agentInstructionFiles = ['agent-instructions.md', 'agent-instructions-cardlayout.md'];
171
+ const parts = [];
172
+ for (const fname of agentInstructionFiles) {
173
+ const fpath = path.join(srcDir, fname);
174
+ if (fs.existsSync(fpath)) parts.push(fs.readFileSync(fpath, 'utf-8').trimEnd());
175
+ }
176
+ if (parts.length > 0) {
177
+ fs.writeFileSync(path.join(boardSetupRoot, 'copilot-instructions.md'), parts.join('\n\n') + '\n', 'utf-8');
178
+ }
179
+
180
+ _demoPrepSetupDone.set(boardId, true);
181
+ }
182
+
129
183
  async function handleDemoSetup(req, res, boardId) {
130
184
  try {
131
- const { service, boardRoot } = runtime.requireBoardService(boardId);
185
+ const { service } = runtime.requireBoardService(boardId);
132
186
  let setupPerformed = false;
133
187
 
134
- if (!service.isDemoSetupDone()) {
135
- service.ensureDemoSetup();
188
+ if (!isDemoSetupDone(boardId, service)) {
189
+ demoPrepSetup(boardId, service);
136
190
  setupPerformed = true;
137
191
  }
138
192
 
@@ -168,26 +222,38 @@ async function handleWorkiqAsk(req, res) {
168
222
  await new Promise((resolve) => {
169
223
  let stdout = '';
170
224
  let stderr = '';
225
+ let responded = false;
171
226
  const child = spawn(process.execPath, [workiqJs, 'ask', '-q', query], {
172
227
  stdio: ['inherit', 'pipe', 'pipe'],
173
228
  });
174
229
  child.stdout.on('data', chunk => { stdout += chunk; });
175
230
  child.stderr.on('data', chunk => { stderr += chunk; });
176
231
  child.on('error', (err) => {
177
- jsonReply(res, 500, { error: `workiq spawn error: ${err.message}` });
232
+ if (!responded) {
233
+ responded = true;
234
+ clearTimeout(timeoutId);
235
+ jsonReply(res, 500, { error: `workiq spawn error: ${err.message}` });
236
+ }
178
237
  resolve();
179
238
  });
180
239
  child.on('close', (code) => {
181
- if (code !== 0) {
182
- jsonReply(res, 500, { error: `workiq exited ${code}`, stderr });
183
- } else {
184
- jsonReply(res, 200, { response: stdout });
240
+ if (!responded) {
241
+ responded = true;
242
+ clearTimeout(timeoutId);
243
+ if (code !== 0) {
244
+ jsonReply(res, 500, { error: `workiq exited ${code}`, stderr });
245
+ } else {
246
+ jsonReply(res, 200, { response: stdout });
247
+ }
185
248
  }
186
249
  resolve();
187
250
  });
188
- setTimeout(() => {
189
- child.kill();
190
- jsonReply(res, 504, { error: 'workiq timed out after 60s' });
251
+ const timeoutId = setTimeout(() => {
252
+ if (!responded) {
253
+ responded = true;
254
+ child.kill();
255
+ jsonReply(res, 504, { error: 'workiq timed out after 60s' });
256
+ }
191
257
  resolve();
192
258
  }, 60_000);
193
259
  });
@@ -6,9 +6,9 @@
6
6
  <title>Example Board Demo (Browser Runtime)</title>
7
7
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
8
  <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/card-compute.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/live-cards.js"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/board-livegraph-engine.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/card-compute.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/live-cards.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/board-livegraph-engine.js"></script>
12
12
  </head>
13
13
  <body class="bg-light">
14
14
  <div class="container-fluid py-3">
@@ -378,7 +378,7 @@
378
378
 
379
379
  return {
380
380
  fetchSource: async function (card, sourceDef) {
381
- // chartApi source with mock="quotes" → return mock quote data
381
+ // mock source with mock="quotes" → return mock quote data
382
382
  if (sourceDef && sourceDef.mock === 'quotes') return clone(quoteData);
383
383
  // copilot source → return mock analysis object
384
384
  if (sourceDef && sourceDef.copilot) return clone(mockAnalysis);
@@ -16,10 +16,10 @@
16
16
  </style>
17
17
  <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
18
18
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
19
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/card-compute.js"></script>
20
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/live-cards.js"></script>
21
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/board-livegraph-engine.js"></script>
22
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.8/browser/board-livecards-runtime-client.js"></script>
19
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/card-compute.js"></script>
20
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/live-cards.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/board-livegraph-engine.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.4.0/browser/board-livecards-runtime-client.js"></script>
23
23
  </head>
24
24
  <body class="bg-light">
25
25
  <div class="container-fluid py-3">
@@ -45,8 +45,7 @@
45
45
  * - { "url-list": { method?, headers?, cacheTimeout? } }
46
46
  * → fan-out over _projections.url_list (string[]); returns array of responses.
47
47
  * Build url_list in projections: e.g. `requires.holdings.ticker.('https://host/' & $ & '?q=1')`
48
- * - { chartApi: { url, headers? }, tickersFrom } → removed; use url-list instead
49
- * prefer url-list for new sources
48
+ * Prefer url-list for multi-URL fan-out sources.
50
49
  * A real executor can also handle: graphapi, teams, mail, incidentdb, script, etc.
51
50
  *
52
51
  * url / url-list notes:
@@ -137,6 +136,26 @@ function interpolatePrompt(template, args) {
137
136
  });
138
137
  }
139
138
 
139
+ // Reusable prompt fragments available to all copilot source templates.
140
+ // Source definitions can interpolate them with {{view_kind_guidance}} and {{card_layout_guidance}}.
141
+ const COPILOT_PROMPT_CONTEXT = {
142
+ view_kind_guidance: [
143
+ 'VIEW KIND GUIDANCE (for dynamic ref rendering):',
144
+ '- Return a _view object whenever your output data is meant for a ref element.',
145
+ '- Allowed _view.kind values only: table, editable-table, chart, metric, list, badge, text, narrative, markdown, form, filter, todo, alert.',
146
+ '- If uncertain, use "table".',
147
+ '- For array rows that users should edit, prefer "editable-table" and set _view.data.writeTo to a card_data path.',
148
+ '- For chart, set _view.data.chartType and _view.data.columns with [labelField, valueField].',
149
+ '- Keep _view.data minimal and valid JSON (no comments, no trailing text).',
150
+ ].join('\n'),
151
+ card_layout_guidance: [
152
+ 'CARD LAYOUT GUIDANCE:',
153
+ '- Prefer compact outputs that fit a card: one primary structure plus concise rationale text.',
154
+ '- Avoid repeating values already present in upstream inputs.',
155
+ '- If you produce both machine-readable and human-readable content, keep machine-readable fields top-level and concise prose in a separate field.',
156
+ ].join('\n'),
157
+ };
158
+
140
159
  /**
141
160
  * Fetch a URL using the system curl binary (synchronous, no Node event-loop handles).
142
161
  * Throws if curl exits non-zero (e.g. HTTP 4xx/5xx with -f, or network error).
@@ -167,6 +186,7 @@ function resolveCopilotPrompt(sourceDef) {
167
186
  // evaluated by the engine from card_data/requires before invoking this executor.
168
187
  // Explicit args defined on the source take highest precedence.
169
188
  const interpolationContext = {
189
+ ...COPILOT_PROMPT_CONTEXT,
170
190
  ...sourceDef._projections,
171
191
  ...args,
172
192
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml-flow",
3
- "version": "5.2.8",
3
+ "version": "5.4.0",
4
4
  "description": "Unified workflow engine: step-machine (sequential) + event-graph (stateless DAG) with pluggable storage",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -126,7 +126,7 @@
126
126
  "kind": {
127
127
  "enum": ["metric", "table", "editable-table", "chart", "form", "filter", "list",
128
128
  "notes", "todo", "alert", "narrative", "badge", "text",
129
- "markdown", "custom", "actions"]
129
+ "markdown", "ref", "custom", "actions"]
130
130
  },
131
131
  "label": { "type": "string", "description": "Heading above this element" },
132
132
  "className": { "type": "string", "description": "Bootstrap grid class, e.g. 'col-12 col-md-6'" },