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
|
@@ -38,7 +38,7 @@ vocabulary:
|
|
|
38
38
|
|
|
39
39
|
"requires": ["token-a", "token-b"],
|
|
40
40
|
|
|
41
|
-
"
|
|
41
|
+
"source_defs": [
|
|
42
42
|
{ "bindTo": "raw", "outputFile": "my-card-raw.json", /* task-executor fields */ }
|
|
43
43
|
],
|
|
44
44
|
|
|
@@ -47,7 +47,7 @@ vocabulary:
|
|
|
47
47
|
],
|
|
48
48
|
|
|
49
49
|
"provides": [
|
|
50
|
-
{ "bindTo": "published-token", "
|
|
50
|
+
{ "bindTo": "published-token", "ref": "computed_values.result" }
|
|
51
51
|
],
|
|
52
52
|
|
|
53
53
|
"view": {
|
|
@@ -74,9 +74,10 @@ vocabulary:
|
|
|
74
74
|
- `outputFile` → where the fetched result is cached
|
|
75
75
|
- `customFields` → interpreted by the registered **task-executor** (examples: `mock`, `copilot`, `http`, `script`)
|
|
76
76
|
- Produces: `fetched_sources.*`
|
|
77
|
+
- **`source_defs` is NOT a valid data namespace** — it is the config array of source definitions. Use `fetched_sources.*` to reference fetched data.
|
|
77
78
|
|
|
78
79
|
### Stage 2 — Compute
|
|
79
|
-
- **Runs after
|
|
80
|
+
- **Runs after source_defs.** Reads `requires.*`, `fetched_sources.*`, `card_data.*`.
|
|
80
81
|
- Each entry: `{ "bindTo": "key", "expr": "<JSONata>" }`
|
|
81
82
|
- Produces: `computed_values.*`
|
|
82
83
|
|
|
@@ -84,7 +85,7 @@ vocabulary:
|
|
|
84
85
|
- **Resolved last.** Can reference all four namespaces:
|
|
85
86
|
`requires.*`, `fetched_sources.*`, `card_data.*`, `computed_values.*`
|
|
86
87
|
- `view.elements[].data.bind` paths are resolved here.
|
|
87
|
-
- `provides[].
|
|
88
|
+
- `provides[].ref` paths are resolved here and published to the graph.
|
|
88
89
|
|
|
89
90
|
---
|
|
90
91
|
|
|
@@ -93,9 +94,9 @@ vocabulary:
|
|
|
93
94
|
```json
|
|
94
95
|
// Publishing a token:
|
|
95
96
|
"provides": [
|
|
96
|
-
{ "bindTo": "orders", "
|
|
97
|
-
{ "bindTo": "regionTotals","
|
|
98
|
-
{ "bindTo": "my-card", "
|
|
97
|
+
{ "bindTo": "orders", "ref": "fetched_sources.raw" },
|
|
98
|
+
{ "bindTo": "regionTotals","ref": "computed_values.byRegion" },
|
|
99
|
+
{ "bindTo": "my-card", "ref": "card_data" }
|
|
99
100
|
]
|
|
100
101
|
|
|
101
102
|
// Consuming tokens:
|
|
@@ -124,7 +125,7 @@ Every card is a live entity. Any of these events triggers automatic recompute of
|
|
|
124
125
|
|
|
125
126
|
## Task Completion
|
|
126
127
|
|
|
127
|
-
Task completion is determined by one rule: **a card is complete when all non-optional `
|
|
128
|
+
Task completion is determined by one rule: **a card is complete when all non-optional `source_defs[]` have been fetched**.
|
|
128
129
|
|
|
129
130
|
If completion requires a judgment call — e.g. "is the data sufficient?", "does this narrative indicate done?" — model it as data using the standard source → compute → provides chain (see LLM source pattern below). The card is complete when that source has been fetched.
|
|
130
131
|
|
|
@@ -272,14 +273,131 @@ Alert/callout display element.
|
|
|
272
273
|
### `custom`
|
|
273
274
|
Custom element kind — behaviour defined by the host application.
|
|
274
275
|
|
|
276
|
+
### `ref`
|
|
277
|
+
Indirection element that resolves its rendered kind at runtime from any namespace variable. The card author declares the data source (`bind`) and where to look up the view definition (`viewBind`). The resolved view definition can come from `card_data` (user-selectable UI), `fetched_sources` (LLM-suggested), `requires` (cross-card), or `computed_values`.
|
|
278
|
+
|
|
279
|
+
**Resolved value shape:**
|
|
280
|
+
- A **string** — used directly as the element kind: `"table"`, `"chart"`, `"editable-table"`, etc.
|
|
281
|
+
- An **object** — `{ kind, label, data: { columns, chartType, chartOptions, writeTo, ... } }` — merged with the static elemDef (static fields always win, so card authors can fence LLM suggestions).
|
|
282
|
+
- `null`/`undefined` — falls back to `fallbackKind` or shape inference (`array→table`, `string→text`, `object→narrative`).
|
|
283
|
+
|
|
284
|
+
**Allowed kind values** (whitelist; unknown values fall to `table`):
|
|
285
|
+
`table`, `editable-table`, `chart`, `metric`, `list`, `badge`, `text`, `narrative`, `markdown`, `form`, `filter`, `todo`, `alert`
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{ "kind": "ref",
|
|
289
|
+
"label": "Trade Data",
|
|
290
|
+
"data": {
|
|
291
|
+
"bind": "fetched_sources.rebalance.proposed_trades",
|
|
292
|
+
"viewBind": "card_data.display_mode",
|
|
293
|
+
"fallbackKind": "table"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Pattern 1 — User-selectable view (selector pattern)**
|
|
299
|
+
|
|
300
|
+
Pair a `form` element with a `ref` element. The form writes the chosen kind into `card_data`; the `ref` reads it back. The user can switch between table, chart, etc. with no card JSON changes:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{ "kind": "form",
|
|
304
|
+
"data": {
|
|
305
|
+
"writeTo": "card_data.display_mode",
|
|
306
|
+
"fields": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"properties": {
|
|
309
|
+
"kind": { "type": "string", "title": "Display as",
|
|
310
|
+
"enum": ["table", "chart", "editable-table"] }
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
{ "kind": "ref",
|
|
316
|
+
"data": {
|
|
317
|
+
"bind": "fetched_sources.rebalance.proposed_trades",
|
|
318
|
+
"viewBind": "card_data.display_mode",
|
|
319
|
+
"fallbackKind": "table"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Note: `card_data.display_mode` can be a plain string (`"chart"`) or an object (`{ "kind": "chart", ... }`). Both are handled.
|
|
325
|
+
|
|
326
|
+
**Pattern 2 — LLM-suggested view (source carries `_view`)**
|
|
327
|
+
|
|
328
|
+
The LLM source response includes a `_view` key alongside the data. The `ref` element reads `_view` from the source namespace. The LLM decides chart vs table vs editable-table at runtime based on what the data looks like:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
// LLM returns:
|
|
332
|
+
{ "proposed_trades": [ {"ticker": "AAPL", "trade_value": 1084} ],
|
|
333
|
+
"_view": { "kind": "chart", "data": { "chartType": "bar", "columns": ["ticker","trade_value"] } }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Card element:
|
|
337
|
+
{ "kind": "ref",
|
|
338
|
+
"data": {
|
|
339
|
+
"bind": "fetched_sources.rebalance.proposed_trades",
|
|
340
|
+
"viewBind": "fetched_sources.rebalance._view",
|
|
341
|
+
"fallbackKind": "table"
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Add this to the `copilot` prompt template to instruct the LLM:
|
|
347
|
+
```
|
|
348
|
+
Return JSON with shape:
|
|
349
|
+
{
|
|
350
|
+
"<data_key>": [ ... ],
|
|
351
|
+
"_view": {
|
|
352
|
+
"kind": "editable-table" | "table" | "chart",
|
|
353
|
+
"data": {
|
|
354
|
+
// for editable-table: { "writeTo": "card_data.<key>", "columns": ["field1","field2"] }
|
|
355
|
+
// for chart: { "chartType": "bar" | "line" | "pie", "columns": ["labelField","valueField"] }
|
|
356
|
+
// for table: { "columns": ["field1","field2"] }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
Choose kind based on data shape and user intent.
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The `_view` key is ignored by any card that reads only `<data_key>` — it is inert to downstream cards.
|
|
364
|
+
|
|
365
|
+
**Pattern 3 — Cross-card view hint (view definition flows as a token)**
|
|
366
|
+
|
|
367
|
+
Card A provides both its data and the view hint as separate tokens. Card B requires both and uses `ref` to render whatever Card A’s LLM suggested:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
// Card A provides:
|
|
371
|
+
"provides": [
|
|
372
|
+
{ "bindTo": "trade_data", "ref": "fetched_sources.rebalance.proposed_trades" },
|
|
373
|
+
{ "bindTo": "trade_view", "ref": "fetched_sources.rebalance._view" }
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
// Card B requires and renders:
|
|
377
|
+
"requires": ["trade_data", "trade_view"],
|
|
378
|
+
"view": {
|
|
379
|
+
"elements": [
|
|
380
|
+
{ "kind": "ref",
|
|
381
|
+
"data": {
|
|
382
|
+
"bind": "requires.trade_data",
|
|
383
|
+
"viewBind": "requires.trade_view",
|
|
384
|
+
"fallbackKind": "table"
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Card B’s author does not need to know what kind Card A’s LLM will choose. The view decision propagates through the token graph.
|
|
392
|
+
|
|
275
393
|
---
|
|
276
394
|
|
|
277
395
|
## Common Card Patterns
|
|
278
396
|
|
|
279
397
|
### Root source card
|
|
280
398
|
```
|
|
281
|
-
|
|
282
|
-
provides: [{ bindTo: "orders",
|
|
399
|
+
source_defs[mock/http/script] → fetched_sources.raw
|
|
400
|
+
provides: [{ bindTo: "orders", ref: "fetched_sources.raw" }]
|
|
283
401
|
view: table showing the raw data
|
|
284
402
|
```
|
|
285
403
|
|
|
@@ -287,7 +405,7 @@ view: table showing the raw data
|
|
|
287
405
|
```
|
|
288
406
|
requires: ["orders"]
|
|
289
407
|
compute: JSONata aggregation → computed_values.result
|
|
290
|
-
provides: [{ bindTo: "regionTotals",
|
|
408
|
+
provides: [{ bindTo: "regionTotals", ref: "computed_values.result" }]
|
|
291
409
|
view: table or metric
|
|
292
410
|
```
|
|
293
411
|
|
|
@@ -316,14 +434,42 @@ card-child requires "card-ex-form"
|
|
|
316
434
|
### LLM verdict card (completion gating via source)
|
|
317
435
|
```
|
|
318
436
|
requires: ["some-data"]
|
|
319
|
-
|
|
437
|
+
source_defs: [{ bindTo: "verdict", outputFile: "...", copilot: { prompt_template: "..." } }]
|
|
320
438
|
compute: [{ bindTo: "isReady", expr: "fetched_sources.verdict.isTaskCompleted" }]
|
|
321
|
-
provides: [{ bindTo: "readiness-verdict",
|
|
439
|
+
provides: [{ bindTo: "readiness-verdict", ref: "fetched_sources.verdict" }]
|
|
322
440
|
view: badge(computed_values.isReady, colorMap) + text(fetched_sources.verdict.reason)
|
|
323
441
|
```
|
|
324
442
|
The task executor calls the LLM, writes `{ isTaskCompleted: bool, reason: string }` to `--out`.
|
|
325
443
|
The card is complete when the source is fetched. Downstream cards `requires: ["readiness-verdict"]`.
|
|
326
444
|
|
|
445
|
+
### User-selectable view (selector + ref)
|
|
446
|
+
```
|
|
447
|
+
source_defs: fetch data into fetched_sources.raw
|
|
448
|
+
view:
|
|
449
|
+
form writeTo:card_data.display_mode (enum: table, chart, editable-table)
|
|
450
|
+
ref bind:fetched_sources.raw viewBind:card_data.display_mode fallbackKind:table
|
|
451
|
+
```
|
|
452
|
+
User picks the display kind from a dropdown; the `ref` element re-renders immediately.
|
|
453
|
+
No card JSON changes needed — the form + ref pair handles all switching.
|
|
454
|
+
|
|
455
|
+
### LLM-suggested view
|
|
456
|
+
```
|
|
457
|
+
source_defs: copilot source returns { data: [...], _view: { kind, data: {...} } }
|
|
458
|
+
view:
|
|
459
|
+
ref bind:fetched_sources.raw.data viewBind:fetched_sources.raw._view fallbackKind:table
|
|
460
|
+
```
|
|
461
|
+
LLM decides chart vs table vs editable-table at runtime. `_view` is ignored by downstream
|
|
462
|
+
cards that only read `data`. Card author can fence with static `columns` in the ref element data.
|
|
463
|
+
|
|
464
|
+
### Cross-card view propagation
|
|
465
|
+
```
|
|
466
|
+
Card A provides: trade_data (fetched_sources.rebalance.proposed_trades)
|
|
467
|
+
+ trade_view (fetched_sources.rebalance._view)
|
|
468
|
+
Card B requires: trade_data, trade_view
|
|
469
|
+
view: ref bind:requires.trade_data viewBind:requires.trade_view
|
|
470
|
+
```
|
|
471
|
+
Card B author writes no view knowledge — Card A's LLM drives the rendering decision for both cards.
|
|
472
|
+
|
|
327
473
|
---
|
|
328
474
|
|
|
329
475
|
## Card Design Principles & Layout
|
|
@@ -343,21 +489,86 @@ Common customField conventions (demo executor supports `mock` and `copilot`):
|
|
|
343
489
|
| Field | Meaning |
|
|
344
490
|
|---|---|
|
|
345
491
|
| `"mock": "key"` | Reads from `mock.db` by key — local dev only |
|
|
346
|
-
| `"copilot": { "prompt_template": "...", "args": {} }` | LLM call; `{{key}}` interpolated from `
|
|
492
|
+
| `"copilot": { "prompt_template": "...", "args": {} }` | LLM call; `{{key}}` interpolated from `_projections` (named data projections declared in `projections`), then explicit `args` |
|
|
347
493
|
| `"prompt_template": "..."` | Shorthand top-level LLM call (equivalent to `copilot.prompt_template`) |
|
|
348
494
|
| `"http": { "url": "...", "method": "GET" }` | HTTP/REST — implement in your executor |
|
|
349
495
|
| `"graphapi": { "query": "..." }` | Microsoft Graph API — implement in your executor |
|
|
350
496
|
| `"script": { "path": "...", "args": {} }` | Local script — implement in your executor |
|
|
351
497
|
| `"teams"`, `"mail"`, `"incidentdb"` | Any domain integration — define in your executor |
|
|
352
498
|
|
|
353
|
-
Sources can
|
|
499
|
+
Sources can access upstream data via the `projections` property — named JSONata projections from `card_data` or `requires` that the engine evaluates before invoking the executor. The executor receives `_projections` containing the resolved values. See [source_defs projections](#source_defs-projections) and [Task Executor Protocol](#task-executor-protocol) for details.
|
|
500
|
+
|
|
501
|
+
### source_defs projections
|
|
502
|
+
|
|
503
|
+
The optional `projections` map lets a source definition declare which upstream data it needs. Each key maps to a JSONata expression rooted at `card_data` or `requires`. The engine evaluates these before invoking the executor and attaches the results as `_projections` on the source payload.
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
"source_defs": [
|
|
507
|
+
{
|
|
508
|
+
"bindTo": "quotes",
|
|
509
|
+
"outputFile": "quotes.json",
|
|
510
|
+
"projections": {
|
|
511
|
+
"holdings": "requires.holdings",
|
|
512
|
+
"topHoldings": "requires.holdings[weight > 0.05]",
|
|
513
|
+
"threshold": "card_data.threshold"
|
|
514
|
+
},
|
|
515
|
+
"chartApi": {
|
|
516
|
+
"tickersFrom": "holdings.ticker"
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
]
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Rules:**
|
|
523
|
+
- Only `card_data` and `requires` are valid namespaces in `projections` expressions
|
|
524
|
+
- `fetched_sources`, `computed_values`, and `source_defs` are **forbidden** in projections
|
|
525
|
+
- Full JSONata syntax is supported (same as `compute[].expr`)
|
|
526
|
+
- Sources without `projections` receive `_projections: {}` — executor must handle empty projections gracefully
|
|
527
|
+
- `tickersFrom: "refKey.fieldName"` reads from `_projections[refKey]` — the `projections` key must exist
|
|
354
528
|
|
|
355
529
|
### Optional source field
|
|
356
530
|
- `optionalForCompletionGating: true` — marks this source as optional for default task-completion gating. If set, the card can complete even if this source hasn't been fetched yet.
|
|
357
531
|
|
|
532
|
+
### Discovering supported source kinds
|
|
533
|
+
|
|
534
|
+
Rather than guessing which source `customFields` the registered executor supports, query it directly:
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
node board-live-cards-cli.js describe-task-executor-capabilities --rg <boardDir>
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
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:
|
|
542
|
+
- `description` — what the kind does
|
|
543
|
+
- `inputSchema` — the exact `customFields` the executor expects on the source entry
|
|
544
|
+
- `outputShape` — the shape of the JSON written to `--out`
|
|
545
|
+
- `example` — sample input/output pair
|
|
546
|
+
- **`extraSchema`** — fields available via `--extra` (board topology context)
|
|
547
|
+
- **`subcommands`** — supported subcommands (typically `run-source-fetch` + `describe-capabilities`)
|
|
548
|
+
|
|
549
|
+
**Use this before authoring a card** to confirm the executor handles your intended source kind and to discover the correct field names and types. If the kind is missing from the output, the executor needs extending before the card will work.
|
|
550
|
+
|
|
551
|
+
Example output (excerpt):
|
|
552
|
+
```json
|
|
553
|
+
{
|
|
554
|
+
"sourceKinds": {
|
|
555
|
+
"mock": {
|
|
556
|
+
"description": "Look up a key in a hardcoded MOCK_DB dictionary.",
|
|
557
|
+
"inputSchema": { "mock": { "type": "string", "required": true } }
|
|
558
|
+
},
|
|
559
|
+
"copilot": {
|
|
560
|
+
"description": "Invoke GitHub Copilot CLI with an interpolated prompt template.",
|
|
561
|
+
"inputSchema": {
|
|
562
|
+
"copilot": { "type": "object", "properties": { "prompt_template": { "type": "string" } } }
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
358
569
|
## LLM Calls — Use a Source
|
|
359
570
|
|
|
360
|
-
**All LLM calls belong in
|
|
571
|
+
**All LLM calls belong in source_defs[], handled by the task executor.** There is one mechanism for external calls — source_defs.
|
|
361
572
|
|
|
362
573
|
To incorporate LLM reasoning into a card:
|
|
363
574
|
|
|
@@ -367,7 +578,7 @@ To incorporate LLM reasoning into a card:
|
|
|
367
578
|
4. The card provides those tokens downstream like any other.
|
|
368
579
|
|
|
369
580
|
```json
|
|
370
|
-
"
|
|
581
|
+
"source_defs": [
|
|
371
582
|
{
|
|
372
583
|
"bindTo": "verdict",
|
|
373
584
|
"outputFile": "my-card-verdict.json",
|
|
@@ -388,13 +599,13 @@ If the LLM needs computed values (which compute first), chain two cards: Card A
|
|
|
388
599
|
|
|
389
600
|
### CLI (recommended for authoring)
|
|
390
601
|
```bash
|
|
391
|
-
# Validate
|
|
392
|
-
|
|
602
|
+
# Validate a single card
|
|
603
|
+
node board-live-cards-cli.js validate-card --card cards/my-card.json
|
|
393
604
|
|
|
394
|
-
# Validate
|
|
395
|
-
|
|
605
|
+
# Validate all cards matching a glob
|
|
606
|
+
node board-live-cards-cli.js validate-card --card-glob "cards/*.json"
|
|
396
607
|
```
|
|
397
|
-
|
|
608
|
+
Checks JSON Schema structure, JSONata expression syntax in `compute[].expr`, and `provides[].ref` namespace validity. Reports per-file OK/FAIL with detailed errors. Exits with code 1 if any card fails.
|
|
398
609
|
|
|
399
610
|
### Programmatic
|
|
400
611
|
```typescript
|
|
@@ -424,24 +635,25 @@ When in doubt about allowed fields, consult:
|
|
|
424
635
|
- `id` required, non-empty string
|
|
425
636
|
- `card_data` required, must be an object
|
|
426
637
|
- `requires` must be array of strings (if present)
|
|
427
|
-
- `provides` must be array of `{ bindTo: string,
|
|
428
|
-
- `
|
|
429
|
-
- `
|
|
638
|
+
- `provides` must be array of `{ bindTo: string, ref: string }` (if present)
|
|
639
|
+
- `provides[].ref` must start with a valid namespace: `card_data`, `requires`, `fetched_sources`, or `computed_values`
|
|
640
|
+
- `compute[]` each entry must have `bindTo` + `expr` strings; `expr` must be valid JSONata
|
|
641
|
+
- `source_defs[]` each entry must have `bindTo` + `outputFile` strings; both must be unique across the array
|
|
430
642
|
- `view.elements` required, non-empty; each element must have a valid `kind`
|
|
431
643
|
- Top-level unknown keys are flagged as errors
|
|
432
|
-
- Valid element `kind` values: `metric`, `table`, `editable-table`, `chart`, `form`, `filter`, `list`, `notes`, `todo`, `alert`, `narrative`, `badge`, `text`, `markdown`, `custom`
|
|
644
|
+
- Valid element `kind` values: `metric`, `table`, `editable-table`, `chart`, `form`, `filter`, `list`, `notes`, `todo`, `alert`, `narrative`, `badge`, `text`, `markdown`, `ref`, `custom`
|
|
433
645
|
|
|
434
646
|
---
|
|
435
647
|
|
|
436
648
|
## mock.db
|
|
437
649
|
|
|
438
|
-
A JSON file at the board root keyed by mock name. Used by `"mock": "key"`
|
|
650
|
+
A JSON file at the board root keyed by mock name. Used by `"mock": "key"` source_defs. Replace with real task-executor integrations in production.
|
|
439
651
|
|
|
440
652
|
---
|
|
441
653
|
|
|
442
654
|
## Task Executor Protocol
|
|
443
655
|
|
|
444
|
-
The task executor is a **card-source-driven** component — its behaviour is determined entirely by the `customFields` defined on each card's `
|
|
656
|
+
The task executor is a **card-source-driven** component — its behaviour is determined entirely by the `customFields` defined on each card's `source_defs[]` entries. One executor is registered for the whole board, but it must know how to handle every source kind (`mock`, `copilot`, `http`, `graphapi`, etc.) used by any card on the board. The executor is the only handler where the card's source definition directly drives what the handler needs to do. It is registered once per board:
|
|
445
657
|
|
|
446
658
|
```bash
|
|
447
659
|
node board-live-cards-cli.js init ./my-board --task-executor ./my-executor.js
|
|
@@ -454,7 +666,7 @@ node board-live-cards-cli.js init ./my-board --task-executor ./my-executor.js
|
|
|
454
666
|
node <executor.js> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
|
|
455
667
|
```
|
|
456
668
|
|
|
457
|
-
- **`--in`** — path to a JSON file containing a single source object (one entry from `
|
|
669
|
+
- **`--in`** — path to a JSON file containing a single source object (one entry from `source_defs[]`), enriched by the runtime with extra context fields:
|
|
458
670
|
|
|
459
671
|
```json
|
|
460
672
|
{
|
|
@@ -462,16 +674,12 @@ node <executor.js> run-source-fetch --in <source.json> --out <result.json> [--er
|
|
|
462
674
|
"outputFile": "my-card-raw.json",
|
|
463
675
|
"cwd": "/absolute/path/to/board",
|
|
464
676
|
"boardDir": "/absolute/path/to/board",
|
|
465
|
-
"
|
|
466
|
-
"_sourcesData": { "previously-fetched-source-key": { ... } },
|
|
467
|
-
"_computed_values": { "result": "..." },
|
|
677
|
+
"_projections": { "holdings": [ ... ], "threshold": 0.05 },
|
|
468
678
|
/* ...any other customFields from the card source definition */
|
|
469
679
|
}
|
|
470
680
|
```
|
|
471
681
|
|
|
472
|
-
- `
|
|
473
|
-
- `_sourcesData` — already-fetched sources for this card (earlier in sources[] order)
|
|
474
|
-
- `_computed_values` — current computed_values for the card
|
|
682
|
+
- `_projections` — resolved values for all entries declared in the source's `projections` map (evaluated from `card_data`/`requires` before executor invocation). Empty object `{}` if `projections` was not declared.
|
|
475
683
|
|
|
476
684
|
- **`--out`** — path to write the fetched value as raw JSON (any shape; stored under `fetched_sources.<bindTo>`)
|
|
477
685
|
- **`--err`** — optional path to write a plain-text error message on failure
|
|
@@ -481,7 +689,7 @@ node <executor.js> run-source-fetch --in <source.json> --out <result.json> [--er
|
|
|
481
689
|
| `customField` | Meaning |
|
|
482
690
|
|---|---|
|
|
483
691
|
| `"mock": "key"` | Lookup `key` in `mock.db` — local dev |
|
|
484
|
-
| `"copilot": { "prompt_template": "..." }` | LLM call; supports `{{key}}` interpolation against `
|
|
692
|
+
| `"copilot": { "prompt_template": "..." }` | LLM call; supports `{{key}}` interpolation against `_projections` |
|
|
485
693
|
| `"http": { "url": "...", "method": "GET" }` | HTTP/REST fetch |
|
|
486
694
|
| `"graphapi": { "query": "..." }` | Microsoft Graph API query |
|
|
487
695
|
| `"script": { "path": "...", "args": {} }` | Run a local script |
|
|
@@ -502,27 +710,45 @@ node board-live-cards-cli.js probe-source \
|
|
|
502
710
|
--card cards/card-market-prices.json \
|
|
503
711
|
--source-idx 0 \
|
|
504
712
|
--rg <boardRuntimeDir> \
|
|
505
|
-
--mock-
|
|
713
|
+
--mock-projections '{"holdings":[{"ticker":"AAPL","quantity":10},{"ticker":"MSFT","quantity":5}]}'
|
|
506
714
|
```
|
|
507
715
|
|
|
508
716
|
| Flag | Required | Description |
|
|
509
717
|
|---|---|---|
|
|
510
718
|
| `--card <card.json>` | yes | Path to the card file to probe |
|
|
511
|
-
| `--source-idx <n>` | no (default 0) | 0-based index into `
|
|
719
|
+
| `--source-idx <n>` | no (default 0) | 0-based index into `source_defs[]` |
|
|
512
720
|
| `--source-bind <name>` | no | Select source by `bindTo` name instead of index |
|
|
513
|
-
| `--mock-
|
|
721
|
+
| `--mock-projections <json>` | no | JSON string (or `@file.json`) providing the `_projections` values the source needs. If omitted, `_projections` is `{}`. |
|
|
514
722
|
| `--rg <boardDir>` | no | Board runtime directory used to locate `.task-executor`. Defaults to the card file's directory. |
|
|
515
723
|
| `--out <result.json>` | no | Write the raw fetch result to this path |
|
|
516
724
|
|
|
517
725
|
**Output:** the command prints a human-readable report ending with a machine-readable `[probe-source:result]` JSON line. Exit `0` = `PROBE_PASS`, exit `1` = `PROBE_FAIL`.
|
|
518
726
|
|
|
519
|
-
**`--mock-
|
|
727
|
+
**`--mock-projections` is the agent's responsibility.** Craft the minimal payload that exercises the source — for example, if the card declares `"projections": { "holdings": "requires.holdings" }` and the source uses `tickersFrom: "holdings.ticker"`, supply `{"holdings":[{"ticker":"AAPL","quantity":1}]}`.
|
|
520
728
|
|
|
521
729
|
**Workflow for agents authoring a new card:**
|
|
522
|
-
1.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
730
|
+
1. **Discover available source kinds** — run `describe-task-executor-capabilities` to see exactly which source kinds the registered executor supports, their required `customFields`, and expected output shapes. Only use source kinds present in this output.
|
|
731
|
+
```bash
|
|
732
|
+
node board-live-cards-cli.js describe-task-executor-capabilities --rg <boardDir>
|
|
733
|
+
```
|
|
734
|
+
2. **Author the card JSON** with `source_defs[]`, `compute[]`, `provides[]`, and `view` using only the source kinds confirmed in step 1.
|
|
735
|
+
3. **Validate the card structure** — run `validate-card` to catch schema errors, invalid JSONata expressions, and namespace violations before attempting any live fetch.
|
|
736
|
+
```bash
|
|
737
|
+
node board-live-cards-cli.js validate-card --card cards/my-card.json
|
|
738
|
+
```
|
|
739
|
+
Fix any reported errors and re-run until validation passes.
|
|
740
|
+
4. **Probe each source** — run `probe-source` with representative `--mock-projections` data to confirm the executor can successfully fetch each source.
|
|
741
|
+
5. If `PROBE_PASS` → proceed with `upsert-card`.
|
|
742
|
+
6. If `PROBE_FAIL` → inspect the error, fix the source definition or executor, retry from step 3.
|
|
743
|
+
|
|
744
|
+
**Workflow for agents editing an existing card:**
|
|
745
|
+
1. Make the change to the card JSON.
|
|
746
|
+
2. **Validate immediately** — run `validate-card` after every edit.
|
|
747
|
+
```bash
|
|
748
|
+
node board-live-cards-cli.js validate-card --card cards/my-card.json
|
|
749
|
+
```
|
|
750
|
+
3. If validation fails, fix the reported errors and re-run — repeat until the card is clean.
|
|
751
|
+
4. If the change touches a `source_defs[]` entry, also run `probe-source` to confirm the source still fetches correctly before deploying.
|
|
526
752
|
|
|
527
753
|
---
|
|
528
754
|
|
|
@@ -532,7 +758,7 @@ node board-live-cards-cli.js probe-source \
|
|
|
532
758
|
|
|
533
759
|
## Chat Handler Protocol
|
|
534
760
|
|
|
535
|
-
The chat handler is a **universal LLM component** — it does not depend on card
|
|
761
|
+
The chat handler is a **universal LLM component** — it does not depend on card source_defs, card model fields, or board state. Its sole job is: read the conversation, call the LLM, write the response.
|
|
536
762
|
|
|
537
763
|
Enable chat on a card by setting `"chat": true` in the card's `view.features`:
|
|
538
764
|
```json
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
"desc": "AI analysis of portfolio mix and P&L. Publishes structured insights as tokens for downstream risk and action cards."
|
|
7
7
|
},
|
|
8
8
|
"requires": ["positions"],
|
|
9
|
-
"
|
|
9
|
+
"source_defs": [
|
|
10
10
|
{
|
|
11
11
|
"bindTo": "analysis",
|
|
12
12
|
"outputFile": "card-concentration-analysis.json",
|
|
13
|
+
"projections": {
|
|
14
|
+
"positions": "requires.positions"
|
|
15
|
+
},
|
|
13
16
|
"copilot": {
|
|
14
17
|
"prompt_template": "You are a concise equity analyst. Use web search to get today's news and upcoming events for each ticker before answering.\n\nPortfolio positions:\n{{positions}}\n\nFields: ticker, quantity, cost_basis ($), price ($), value ($), gain_$ (unrealised P&L), gain_% (unrealised return), chg_$ (today's position P&L $), chg_pct (today's % move).\n\nIMPORTANT OUTPUT RULES:\n- Return ONLY a valid JSON object. No markdown code fences, no preamble, no trailing text.\n- Start your response with { and end with }.\n- No emojis anywhere.\n- Each value is a Markdown string using - bullet points. No sub-headings inside values.\n- Maximum 40 words per section.\n\n{\n \"mix\": \"- <dominant holding ticker, % of total, and whether that is a risk>\\n- <sector clustering in one line>\",\n \"pnl\": \"- Best: <ticker> +X% (+$Y)\\n- Worst: <ticker> +X% (+$Y)\",\n \"risks\": \"- <TICKER>: <one specific current risk with event name or date>\\n- <TICKER>: ...\\n- (one bullet per position from today's news)\",\n \"action\": \"- <one sentence: specific action and reason grounded in today's market>\"\n}"
|
|
15
18
|
}
|
|
@@ -22,10 +25,10 @@
|
|
|
22
25
|
{ "bindTo": "action", "expr": "fetched_sources.analysis.action" }
|
|
23
26
|
],
|
|
24
27
|
"provides": [
|
|
25
|
-
{ "bindTo": "portfolio_mix", "
|
|
26
|
-
{ "bindTo": "portfolio_pnl", "
|
|
27
|
-
{ "bindTo": "portfolio_risks", "
|
|
28
|
-
{ "bindTo": "portfolio_action", "
|
|
28
|
+
{ "bindTo": "portfolio_mix", "ref": "computed_values.mix" },
|
|
29
|
+
{ "bindTo": "portfolio_pnl", "ref": "computed_values.pnl" },
|
|
30
|
+
{ "bindTo": "portfolio_risks", "ref": "computed_values.risks" },
|
|
31
|
+
{ "bindTo": "portfolio_action", "ref": "computed_values.action" }
|
|
29
32
|
],
|
|
30
33
|
"view": {
|
|
31
34
|
"elements": [
|
|
@@ -6,28 +6,33 @@
|
|
|
6
6
|
"desc": "Fetches live prices for portfolio tickers. Publishes enriched quote data for downstream portfolio value calculations."
|
|
7
7
|
},
|
|
8
8
|
"requires": ["holdings"],
|
|
9
|
-
"
|
|
9
|
+
"source_defs": [
|
|
10
10
|
{
|
|
11
11
|
"bindTo": "quotes",
|
|
12
12
|
"outputFile": "market-prices-quotes.json",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
13
|
+
"projections": {
|
|
14
|
+
"url_list": "requires.holdings.ticker.('https://query1.finance.yahoo.com/v8/finance/chart/' & $ & '?interval=1d&range=1d')"
|
|
15
|
+
},
|
|
16
|
+
"url-list": {
|
|
15
17
|
"headers": {
|
|
16
18
|
"User-Agent": "Mozilla/5.0 (compatible; portfolio-tracker-demo/1.0)"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"mock": "quotes"
|
|
19
|
+
},
|
|
20
|
+
"cacheTimeout": 3600
|
|
21
|
+
}
|
|
21
22
|
}
|
|
22
23
|
],
|
|
23
24
|
"compute": [
|
|
25
|
+
{
|
|
26
|
+
"bindTo": "normalizedQuotes",
|
|
27
|
+
"expr": "{ \"quoteResponse\": { \"result\": $map(fetched_sources.quotes, function($r) { $r.chart.result[0].meta.{ \"symbol\": symbol, \"shortName\": longName, \"regularMarketPrice\": regularMarketPrice, \"regularMarketChange\": regularMarketChange, \"regularMarketChangePercent\": regularMarketChangePercent } }), \"error\": null } }"
|
|
28
|
+
},
|
|
24
29
|
{
|
|
25
30
|
"bindTo": "prices",
|
|
26
|
-
"expr": "$map(
|
|
31
|
+
"expr": "$map(computed_values.normalizedQuotes.quoteResponse.result, function($q) { {\"ticker\": $q.symbol, \"name\": $q.shortName, \"price\": $round($q.regularMarketPrice, 2), \"change\": $round($q.regularMarketChange, 2), \"chg_pct\": $round($q.regularMarketChangePercent, 2)} })"
|
|
27
32
|
}
|
|
28
33
|
],
|
|
29
34
|
"provides": [
|
|
30
|
-
{ "bindTo": "quotes", "
|
|
35
|
+
{ "bindTo": "quotes", "ref": "computed_values.normalizedQuotes" }
|
|
31
36
|
],
|
|
32
37
|
"view": {
|
|
33
38
|
"elements": [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "card-my-identity",
|
|
3
|
+
"meta": {
|
|
4
|
+
"title": "My Identity",
|
|
5
|
+
"tags": ["m365", "workiq"],
|
|
6
|
+
"desc": "Displays your M365 display name via WorkIQ (Microsoft 365 Copilot)."
|
|
7
|
+
},
|
|
8
|
+
"source_defs": [
|
|
9
|
+
{
|
|
10
|
+
"bindTo": "identity",
|
|
11
|
+
"outputFile": "card-my-identity.txt",
|
|
12
|
+
"workiq": {
|
|
13
|
+
"query_template": "what is my full display name. No Fluff. No Commentary. No Descriptions. Just Answer the Question"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"view": {
|
|
18
|
+
"elements": [
|
|
19
|
+
{ "kind": "markdown", "data": { "bind": "fetched_sources.identity" } }
|
|
20
|
+
],
|
|
21
|
+
"layout": {
|
|
22
|
+
"board": { "col": 4, "order": 10 },
|
|
23
|
+
"canvas": { "x": 50, "y": 450, "w": 420, "h": 260 }
|
|
24
|
+
},
|
|
25
|
+
"features": { "refresh": true }
|
|
26
|
+
},
|
|
27
|
+
"card_data": {}
|
|
28
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"desc": "Manage your stock holdings — edit tickers and quantities inline, add or delete rows. Changes propagate downstream immediately."
|
|
7
7
|
},
|
|
8
8
|
"provides": [
|
|
9
|
-
{ "bindTo": "holdings", "
|
|
9
|
+
{ "bindTo": "holdings", "ref": "card_data.holdings" }
|
|
10
10
|
],
|
|
11
11
|
"compute": [],
|
|
12
12
|
"view": {
|