yaml-flow 5.1.0 → 5.2.1
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 +15 -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
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
# Agent Instructions — Authoring yaml-flow Boards & Cards
|
|
2
|
+
|
|
3
|
+
## What Is This?
|
|
4
|
+
|
|
5
|
+
A **board** is a `board.yaml` file plus a folder of **card** JSON files. Cards are purely declarative — the runtime (board-livegraph-runtime) owns all state mutation and reactivity. Cards never imperative-call each other; they declare data dependencies and the runtime handles everything.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Board File (`board.yaml`)
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
name: My Dashboard
|
|
13
|
+
desc: Short description
|
|
14
|
+
|
|
15
|
+
connects:
|
|
16
|
+
- id: my-db
|
|
17
|
+
type: script # or: http
|
|
18
|
+
url: https://... # for http type
|
|
19
|
+
desc: Human description
|
|
20
|
+
|
|
21
|
+
vocabulary:
|
|
22
|
+
tokens:
|
|
23
|
+
healthy: { color: "green", label: "Healthy" }
|
|
24
|
+
warning: { color: "orange", label: "Warning" }
|
|
25
|
+
alert: { color: "red", label: "Alert" }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`connects` entries describe available data connections. Their fields are passed to the task-executor and are not schema-enforced beyond `id` and `type`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Card File Structure
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"id": "my-card",
|
|
37
|
+
"meta": { "title": "Card Title", "tags": ["tag1"], "desc": "What this card does" },
|
|
38
|
+
|
|
39
|
+
"requires": ["token-a", "token-b"],
|
|
40
|
+
|
|
41
|
+
"sources": [
|
|
42
|
+
{ "bindTo": "raw", "outputFile": "my-card-raw.json", /* task-executor fields */ }
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
"compute": [
|
|
46
|
+
{ "bindTo": "result", "expr": "/* JSONata expression */" }
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
"provides": [
|
|
50
|
+
{ "bindTo": "published-token", "src": "computed_values.result" }
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
"view": {
|
|
54
|
+
"elements": [ /* see Element Kinds below */ ],
|
|
55
|
+
"layout": {
|
|
56
|
+
"board": { "col": 4, "order": 1 },
|
|
57
|
+
"canvas": { "x": 50, "y": 50, "w": 280, "h": 180 }
|
|
58
|
+
},
|
|
59
|
+
"features": { "refresh": true, "chat": true }
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
"card_data": { /* initial mutable state */ }
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Strict Compute Order (within a card)
|
|
69
|
+
|
|
70
|
+
### Stage 1 — Sources
|
|
71
|
+
- **Runs first.** Parameterised by `requires.*` and `card_data.*` only.
|
|
72
|
+
- Each entry: `{ "bindTo": "key", "outputFile": "cache.json", ...customFields }`
|
|
73
|
+
- `bindTo` → key under `fetched_sources`
|
|
74
|
+
- `outputFile` → where the fetched result is cached
|
|
75
|
+
- `customFields` → interpreted by the registered **task-executor** (examples: `mock`, `copilot`, `http`, `script`)
|
|
76
|
+
- Produces: `fetched_sources.*`
|
|
77
|
+
|
|
78
|
+
### Stage 2 — Compute
|
|
79
|
+
- **Runs after sources.** Reads `requires.*`, `fetched_sources.*`, `card_data.*`.
|
|
80
|
+
- Each entry: `{ "bindTo": "key", "expr": "<JSONata>" }`
|
|
81
|
+
- Produces: `computed_values.*`
|
|
82
|
+
|
|
83
|
+
### Stage 3 — Views & Provides
|
|
84
|
+
- **Resolved last.** Can reference all four namespaces:
|
|
85
|
+
`requires.*`, `fetched_sources.*`, `card_data.*`, `computed_values.*`
|
|
86
|
+
- `view.elements[].data.bind` paths are resolved here.
|
|
87
|
+
- `provides[].src` paths are resolved here and published to the graph.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## provides / requires — The Dependency Graph
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
// Publishing a token:
|
|
95
|
+
"provides": [
|
|
96
|
+
{ "bindTo": "orders", "src": "fetched_sources.raw" },
|
|
97
|
+
{ "bindTo": "regionTotals","src": "computed_values.byRegion" },
|
|
98
|
+
{ "bindTo": "my-card", "src": "card_data" }
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
// Consuming tokens:
|
|
102
|
+
"requires": ["orders", "selections", "my-card"]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- `provides` maps a token key to a dot-path inside this card's own namespaces.
|
|
106
|
+
- `requires` is a plain array of token key strings.
|
|
107
|
+
- In compute/source expressions, consumed tokens are at `requires.<key>`.
|
|
108
|
+
- For hyphenated keys, use JSONata `$lookup(requires, 'my-card')`.
|
|
109
|
+
- **These declarations are the only wiring needed** — the runtime builds the reactive DAG automatically.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Reactivity
|
|
114
|
+
|
|
115
|
+
Every card is a live entity. Any of these events triggers automatic recompute of all downstream dependents in dependency order:
|
|
116
|
+
- A source finishes fetching (`fetched_sources` changes)
|
|
117
|
+
- User edits `card_data` via a form/filter/todo/actions element
|
|
118
|
+
- An upstream card's `provides` value changes
|
|
119
|
+
- A card definition is updated via server API from the UI
|
|
120
|
+
|
|
121
|
+
**Authors never trigger recompute manually — just declare the data shape.**
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Task Completion
|
|
126
|
+
|
|
127
|
+
Task completion is determined by one rule: **a card is complete when all non-optional `sources[]` have been fetched**.
|
|
128
|
+
|
|
129
|
+
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
|
+
---
|
|
132
|
+
|
|
133
|
+
## Element Kinds Reference
|
|
134
|
+
|
|
135
|
+
### `metric`
|
|
136
|
+
Single KPI value.
|
|
137
|
+
```json
|
|
138
|
+
{ "kind": "metric", "label": "Revenue", "data": { "bind": "computed_values.total" } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `badge`
|
|
142
|
+
Colour-coded status pill using Bootstrap colour names (`success`, `warning`, `danger`, `info`, `secondary`).
|
|
143
|
+
```json
|
|
144
|
+
{ "kind": "badge", "data": { "bind": "computed_values.health", "colorMap": { "Healthy": "success", "Low": "danger" } } }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `text`
|
|
148
|
+
Plain or styled text. `style`: `muted`, `muted-italic`. Use `hideIfEmpty: true` to suppress if blank.
|
|
149
|
+
```json
|
|
150
|
+
{ "kind": "text", "label": "Note", "style": "muted-italic", "data": { "bind": "card_data.note", "hideIfEmpty": true } }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `list`
|
|
154
|
+
Ordered list from an array. Optional `template` for string interpolation: `"{field1} — {field2}"`.
|
|
155
|
+
```json
|
|
156
|
+
{ "kind": "list", "data": { "bind": "computed_values.topItems", "template": "{name} — ${amount}" } }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `table`
|
|
160
|
+
Data table. `columns` selects fields. `sortable: true` enables click-to-sort. `maxRows` caps display.
|
|
161
|
+
```json
|
|
162
|
+
{ "kind": "table", "data": { "bind": "fetched_sources.raw", "columns": ["id","name","value"], "sortable": true, "maxRows": 20 } }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `editable-table`
|
|
166
|
+
Inline-editable table. Each cell is an `<input>`; changes save on blur. `writeTo` persists the updated array back to `card_data`. `columns` controls which fields appear. Optional `schema.properties` per column sets `type` (`number`/`integer` renders a numeric input). `addRow` (default `true`) shows a "+ Add row" button; `deleteRow` (default `true`) shows per-row delete buttons.
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"kind": "editable-table",
|
|
170
|
+
"label": "Holdings",
|
|
171
|
+
"data": {
|
|
172
|
+
"bind": "card_data.holdings",
|
|
173
|
+
"writeTo": "card_data.holdings",
|
|
174
|
+
"columns": ["ticker", "quantity"],
|
|
175
|
+
"schema": {
|
|
176
|
+
"properties": {
|
|
177
|
+
"quantity": { "type": "number" }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
Pair with a `provides` pointing at `card_data.holdings` so downstream cards receive the live array. Use `addRow: false` / `deleteRow: false` to make the table append-only or read-fixed-length.
|
|
184
|
+
|
|
185
|
+
### `chart`
|
|
186
|
+
Chart.js chart. `chartType`: `bar`, `line`, `pie`, `doughnut`, etc. `chartOptions` passed to Chart.js.
|
|
187
|
+
```json
|
|
188
|
+
{ "kind": "chart", "label": "By Region", "data": { "bind": "computed_values.regionCounts", "chartType": "bar", "chartOptions": { "indexAxis": "x" } } }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `filter`
|
|
192
|
+
Interactive filter controls. `writeTo` persists selections into `card_data`. `fields` is a JSON Schema describing the filter fields.
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"kind": "filter",
|
|
196
|
+
"data": {
|
|
197
|
+
"bind": "computed_values",
|
|
198
|
+
"writeTo": "card_data.fieldValues",
|
|
199
|
+
"fields": {
|
|
200
|
+
"type": "object",
|
|
201
|
+
"properties": {
|
|
202
|
+
"region": { "type": "string", "title": "Region" },
|
|
203
|
+
"product": { "type": "string", "title": "Product" }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
Pair with a `provides` to publish `card_data.fieldValues` as a named token for downstream cards.
|
|
210
|
+
|
|
211
|
+
### `form`
|
|
212
|
+
Full editable form. `writeTo` path in `card_data`. `fields` is a JSON Schema (supports `enum`, `type`, `minimum`, `maximum`, `required`, etc.).
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"kind": "form",
|
|
216
|
+
"label": "Settings",
|
|
217
|
+
"data": {
|
|
218
|
+
"writeTo": "card_data.prefs",
|
|
219
|
+
"fields": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"properties": {
|
|
222
|
+
"region": { "type": "string", "title": "Region", "enum": ["North","South","East","West"] },
|
|
223
|
+
"limit": { "type": "number", "title": "Limit", "minimum": 0 }
|
|
224
|
+
},
|
|
225
|
+
"required": ["region"]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `todo`
|
|
232
|
+
Interactive checklist. Items are `{ text, done }` objects stored in `card_data`.
|
|
233
|
+
```json
|
|
234
|
+
{ "kind": "todo", "label": "Tasks", "data": { "bind": "card_data.items", "writeTo": "card_data.items", "placeholder": "Add a task…" } }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `actions`
|
|
238
|
+
Button row. Events bubble to the board-level chat/action handler. `style` is a Bootstrap colour name.
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"kind": "actions",
|
|
242
|
+
"label": "Workflow",
|
|
243
|
+
"data": {
|
|
244
|
+
"buttons": [
|
|
245
|
+
{ "id": "approve", "label": "Approve", "style": "success" },
|
|
246
|
+
{ "id": "escalate", "label": "Escalate", "style": "danger" }
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `markdown`
|
|
253
|
+
Free-text Markdown rendered in the card. Typically bound to a `card_data` string field.
|
|
254
|
+
```json
|
|
255
|
+
{ "kind": "markdown", "data": { "bind": "card_data.content" } }
|
|
256
|
+
```
|
|
257
|
+
Set initial content in `card_data.content`.
|
|
258
|
+
|
|
259
|
+
### `narrative`
|
|
260
|
+
AI-generated streaming text. Bind to a `fetched_sources` path populated by a `copilot` source.
|
|
261
|
+
```json
|
|
262
|
+
{ "kind": "narrative", "data": { "bind": "fetched_sources.raw" } }
|
|
263
|
+
```
|
|
264
|
+
Pair with `"features": { "refresh": true, "chat": true }` in `view`.
|
|
265
|
+
|
|
266
|
+
### `notes`
|
|
267
|
+
Editable free-text notes field (similar to markdown but editable by the user).
|
|
268
|
+
|
|
269
|
+
### `alert`
|
|
270
|
+
Alert/callout display element.
|
|
271
|
+
|
|
272
|
+
### `custom`
|
|
273
|
+
Custom element kind — behaviour defined by the host application.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Common Card Patterns
|
|
278
|
+
|
|
279
|
+
### Root source card
|
|
280
|
+
```
|
|
281
|
+
sources[mock/http/script] → fetched_sources.raw
|
|
282
|
+
provides: [{ bindTo: "orders", src: "fetched_sources.raw" }]
|
|
283
|
+
view: table showing the raw data
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Compute chain card
|
|
287
|
+
```
|
|
288
|
+
requires: ["orders"]
|
|
289
|
+
compute: JSONata aggregation → computed_values.result
|
|
290
|
+
provides: [{ bindTo: "regionTotals", src: "computed_values.result" }]
|
|
291
|
+
view: table or metric
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Multi-level chain (4 levels example)
|
|
295
|
+
```
|
|
296
|
+
card-source provides "orders"
|
|
297
|
+
card-totals requires "orders" provides "regionTotals"
|
|
298
|
+
card-top requires "regionTotals" provides "topRegion"
|
|
299
|
+
card-alert requires "topRegion", "regionTotals" (no further provides)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Filter → filtered table
|
|
303
|
+
```
|
|
304
|
+
card-filter provides "selections" (src: card_data.fieldValues)
|
|
305
|
+
card-table requires "orders", "selections"
|
|
306
|
+
compute: $filter(requires.orders, fn → match selections)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Form → dependent card
|
|
310
|
+
```
|
|
311
|
+
card-form provides "card-ex-form" (src: card_data)
|
|
312
|
+
card-child requires "card-ex-form"
|
|
313
|
+
compute: $lookup(requires, 'card-ex-form').prefs.field
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### LLM verdict card (completion gating via source)
|
|
317
|
+
```
|
|
318
|
+
requires: ["some-data"]
|
|
319
|
+
sources: [{ bindTo: "verdict", outputFile: "...", copilot: { prompt_template: "..." } }]
|
|
320
|
+
compute: [{ bindTo: "isReady", expr: "fetched_sources.verdict.isTaskCompleted" }]
|
|
321
|
+
provides: [{ bindTo: "readiness-verdict", src: "fetched_sources.verdict" }]
|
|
322
|
+
view: badge(computed_values.isReady, colorMap) + text(fetched_sources.verdict.reason)
|
|
323
|
+
```
|
|
324
|
+
The task executor calls the LLM, writes `{ isTaskCompleted: bool, reason: string }` to `--out`.
|
|
325
|
+
The card is complete when the source is fetched. Downstream cards `requires: ["readiness-verdict"]`.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Card Design Principles & Layout
|
|
330
|
+
|
|
331
|
+
See [agent-instructions-cardlayout.md](agent-instructions-cardlayout.md).
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Source `customFields` and the Task Executor
|
|
336
|
+
|
|
337
|
+
Every field on a source entry beyond `bindTo` and `outputFile` is a **customField**. The runtime passes the entire source object unchanged to the registered task executor — the executor is the sole interpreter of these fields.
|
|
338
|
+
|
|
339
|
+
> **Key principle:** The card author and the task executor author must agree on which customFields are supported. If a card uses `"http": {...}` but the executor only handles `"mock"` and `"copilot"`, the source fetch will fail. The executor must be designed to handle every source kind combination used in your board's cards.
|
|
340
|
+
|
|
341
|
+
Common customField conventions (demo executor supports `mock` and `copilot`):
|
|
342
|
+
|
|
343
|
+
| Field | Meaning |
|
|
344
|
+
|---|---|
|
|
345
|
+
| `"mock": "key"` | Reads from `mock.db` by key — local dev only |
|
|
346
|
+
| `"copilot": { "prompt_template": "...", "args": {} }` | LLM call; `{{key}}` interpolated from `_requires`, `_sourcesData`, `_computed_values`, then explicit `args` |
|
|
347
|
+
| `"prompt_template": "..."` | Shorthand top-level LLM call (equivalent to `copilot.prompt_template`) |
|
|
348
|
+
| `"http": { "url": "...", "method": "GET" }` | HTTP/REST — implement in your executor |
|
|
349
|
+
| `"graphapi": { "query": "..." }` | Microsoft Graph API — implement in your executor |
|
|
350
|
+
| `"script": { "path": "...", "args": {} }` | Local script — implement in your executor |
|
|
351
|
+
| `"teams"`, `"mail"`, `"incidentdb"` | Any domain integration — define in your executor |
|
|
352
|
+
|
|
353
|
+
Sources can reference upstream data in their customFields (e.g. `"url": "https://api.example.com/{{orderId}}"`) — the executor receives `_requires`, `_sourcesData`, `_computed_values` to resolve such references. See [Task Executor Protocol](#task-executor-protocol) for the full `--in` payload shape.
|
|
354
|
+
|
|
355
|
+
### Optional source field
|
|
356
|
+
- `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
|
+
|
|
358
|
+
## LLM Calls — Use a Source
|
|
359
|
+
|
|
360
|
+
**All LLM calls belong in sources[], handled by the task executor.** There is one mechanism for external calls — sources.
|
|
361
|
+
|
|
362
|
+
To incorporate LLM reasoning into a card:
|
|
363
|
+
|
|
364
|
+
1. Add a source entry with a `copilot` (or equivalent) customField and an `outputFile`.
|
|
365
|
+
2. The task executor calls the LLM and writes the result JSON to `--out`.
|
|
366
|
+
3. Card compute reads `fetched_sources.<bindTo>` and derives tokens from it.
|
|
367
|
+
4. The card provides those tokens downstream like any other.
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
"sources": [
|
|
371
|
+
{
|
|
372
|
+
"bindTo": "verdict",
|
|
373
|
+
"outputFile": "my-card-verdict.json",
|
|
374
|
+
"copilot": {
|
|
375
|
+
"prompt_template": "Given this data: {{positions}} — is the portfolio sufficiently diversified? Return JSON: { \"isTaskCompleted\": bool, \"reason\": string }"
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
If the LLM needs computed values (which compute first), chain two cards: Card A computes → Card B `requires` Card A's provides → Card B's source calls the LLM with those values.
|
|
382
|
+
|
|
383
|
+
> **One mechanism for everything.** Sources → compute → provides is the complete model. Every card an agent authors follows this shape regardless of whether the data comes from a database, an API, or an LLM.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Validating Cards
|
|
388
|
+
|
|
389
|
+
### CLI (recommended for authoring)
|
|
390
|
+
```bash
|
|
391
|
+
# Validate all cards in example-board
|
|
392
|
+
npm run validate:cards -- "examples/example-board/cards/*.json"
|
|
393
|
+
|
|
394
|
+
# Validate any glob pattern
|
|
395
|
+
npm run validate:cards -- "path/to/cards/*.json"
|
|
396
|
+
```
|
|
397
|
+
Uses `validateLiveCardDefinition` — structural + schema checks, reports all errors per file.
|
|
398
|
+
|
|
399
|
+
### Programmatic
|
|
400
|
+
```typescript
|
|
401
|
+
import { validateLiveCardSchema } from 'yaml-flow/card-compute';
|
|
402
|
+
// or for the most thorough check:
|
|
403
|
+
import { validateLiveCardDefinition } from 'yaml-flow/src/card-compute/schema-validator.js';
|
|
404
|
+
|
|
405
|
+
const result = validateLiveCardDefinition(cardObject);
|
|
406
|
+
if (!result.ok) {
|
|
407
|
+
console.error(result.errors); // string[]
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
| Function | Validates |
|
|
412
|
+
|---|---|
|
|
413
|
+
| `validateLiveCardDefinition` | Full structural + schema check (most thorough, used by CLI) |
|
|
414
|
+
| `validateLiveCardSchema` | AJV JSON Schema check against `schema/live-cards.schema.json` |
|
|
415
|
+
| `validateLiveCardRuntimeExpressions` | JSONata expression syntax in `compute[].expr` |
|
|
416
|
+
| `validateLiveCard` | Basic structural shape check |
|
|
417
|
+
|
|
418
|
+
### Schema reference
|
|
419
|
+
When in doubt about allowed fields, consult:
|
|
420
|
+
- `yaml-flow/schema/live-cards.schema.json` — canonical JSON Schema for cards
|
|
421
|
+
- `yaml-flow/browser/live-cards.schema.json` — browser-bundled copy (same content)
|
|
422
|
+
|
|
423
|
+
### What the validator checks
|
|
424
|
+
- `id` required, non-empty string
|
|
425
|
+
- `card_data` required, must be an object
|
|
426
|
+
- `requires` must be array of strings (if present)
|
|
427
|
+
- `provides` must be array of `{ bindTo: string, src: string }` (if present)
|
|
428
|
+
- `compute[]` each entry must have `bindTo` + `expr` strings
|
|
429
|
+
- `sources[]` each entry must have `bindTo` + `outputFile` strings; both must be unique across the array
|
|
430
|
+
- `view.elements` required, non-empty; each element must have a valid `kind`
|
|
431
|
+
- 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`
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## mock.db
|
|
437
|
+
|
|
438
|
+
A JSON file at the board root keyed by mock name. Used by `"mock": "key"` sources. Replace with real task-executor integrations in production.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Task Executor Protocol
|
|
443
|
+
|
|
444
|
+
The task executor is a **card-source-driven** component — its behaviour is determined entirely by the `customFields` defined on each card's `sources[]` 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
|
+
|
|
446
|
+
```bash
|
|
447
|
+
node board-live-cards-cli.js init ./my-board --task-executor ./my-executor.js
|
|
448
|
+
# stores path in <boardDir>/.task-executor
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Invocation
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
node <executor.js> run-source-fetch --in <source.json> --out <result.json> [--err <error.txt>]
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
- **`--in`** — path to a JSON file containing a single source object (one entry from `sources[]`), enriched by the runtime with extra context fields:
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"bindTo": "raw",
|
|
462
|
+
"outputFile": "my-card-raw.json",
|
|
463
|
+
"cwd": "/absolute/path/to/board",
|
|
464
|
+
"boardDir": "/absolute/path/to/board",
|
|
465
|
+
"_requires": { "token-a": { ... }, "token-b": { ... } },
|
|
466
|
+
"_sourcesData": { "previously-fetched-source-key": { ... } },
|
|
467
|
+
"_computed_values": { "result": "..." },
|
|
468
|
+
/* ...any other customFields from the card source definition */
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
- `_requires` — resolved values for all card `requires` tokens
|
|
473
|
+
- `_sourcesData` — already-fetched sources for this card (earlier in sources[] order)
|
|
474
|
+
- `_computed_values` — current computed_values for the card
|
|
475
|
+
|
|
476
|
+
- **`--out`** — path to write the fetched value as raw JSON (any shape; stored under `fetched_sources.<bindTo>`)
|
|
477
|
+
- **`--err`** — optional path to write a plain-text error message on failure
|
|
478
|
+
|
|
479
|
+
### Supported source kinds (by convention)
|
|
480
|
+
|
|
481
|
+
| `customField` | Meaning |
|
|
482
|
+
|---|---|
|
|
483
|
+
| `"mock": "key"` | Lookup `key` in `mock.db` — local dev |
|
|
484
|
+
| `"copilot": { "prompt_template": "..." }` | LLM call; supports `{{key}}` interpolation against `_requires`, `_sourcesData`, `_computed_values` |
|
|
485
|
+
| `"http": { "url": "...", "method": "GET" }` | HTTP/REST fetch |
|
|
486
|
+
| `"graphapi": { "query": "..." }` | Microsoft Graph API query |
|
|
487
|
+
| `"script": { "path": "...", "args": {} }` | Run a local script |
|
|
488
|
+
| `"teams"`, `"mail"`, `"incidentdb"` | Custom integrations in your executor |
|
|
489
|
+
|
|
490
|
+
All customFields are executor-defined — the runtime passes them through unchanged.
|
|
491
|
+
|
|
492
|
+
### Exit codes
|
|
493
|
+
- **0** — success; runtime reads `--out`
|
|
494
|
+
- **non-zero** — failure; runtime reads `--err` if present
|
|
495
|
+
|
|
496
|
+
### Probing a source before deploying
|
|
497
|
+
|
|
498
|
+
Before adding a card to a running board, agents should validate that each source can actually be fetched. Use the `probe-source` command — it reads the card file, extracts the source at the chosen index, builds the exact same `--in` payload the runtime would build, invokes the registered executor, and reports pass/fail.
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
node board-live-cards-cli.js probe-source \
|
|
502
|
+
--card cards/card-market-prices.json \
|
|
503
|
+
--source-idx 0 \
|
|
504
|
+
--rg <boardRuntimeDir> \
|
|
505
|
+
--mock-requires '{"holdings":[{"ticker":"AAPL","quantity":10},{"ticker":"MSFT","quantity":5}]}'
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
| Flag | Required | Description |
|
|
509
|
+
|---|---|---|
|
|
510
|
+
| `--card <card.json>` | yes | Path to the card file to probe |
|
|
511
|
+
| `--source-idx <n>` | no (default 0) | 0-based index into `sources[]` |
|
|
512
|
+
| `--source-bind <name>` | no | Select source by `bindTo` name instead of index |
|
|
513
|
+
| `--mock-requires <json>` | no | JSON string (or `@file.json`) providing the `_requires` token values the source needs. If omitted, `_requires` is `{}`. |
|
|
514
|
+
| `--rg <boardDir>` | no | Board runtime directory used to locate `.task-executor`. Defaults to the card file's directory. |
|
|
515
|
+
| `--out <result.json>` | no | Write the raw fetch result to this path |
|
|
516
|
+
|
|
517
|
+
**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
|
+
|
|
519
|
+
**`--mock-requires` is the agent's responsibility.** Craft the minimal payload that exercises the source — for example, if the card `requires: ["holdings"]` and the source uses `tickersFrom: "holdings.ticker"`, supply `{"holdings":[{"ticker":"AAPL","quantity":1}]}`.
|
|
520
|
+
|
|
521
|
+
**Workflow for agents authoring a new card:**
|
|
522
|
+
1. Author the card JSON with `sources[]`.
|
|
523
|
+
2. For each source, run `probe-source` with representative `--mock-requires` data.
|
|
524
|
+
3. If `PROBE_PASS` → proceed with `add-cards` or `upsert-card`.
|
|
525
|
+
4. If `PROBE_FAIL` → inspect the error, fix the source definition or executor, retry.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Chat Handler Protocol
|
|
534
|
+
|
|
535
|
+
The chat handler is a **universal LLM component** — it does not depend on card sources, card model fields, or board state. Its sole job is: read the conversation, call the LLM, write the response.
|
|
536
|
+
|
|
537
|
+
Enable chat on a card by setting `"chat": true` in the card's `view.features`:
|
|
538
|
+
```json
|
|
539
|
+
"view": {
|
|
540
|
+
"features": { "chat": true }
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Register once per board:
|
|
545
|
+
```bash
|
|
546
|
+
node board-live-cards-cli.js init ./my-board --chat-handler ./my-chat-handler.js
|
|
547
|
+
# stores path in <boardDir>/.chat-handler
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Invocation
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
node <handler.js> --boardId <id> --cardId <id> --extraEncJson <base64json>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
- **`--boardId`** — board identifier
|
|
557
|
+
- **`--cardId`** — the specific card where the user clicked the chat button
|
|
558
|
+
- **`--extraEncJson`** — base64-encoded JSON: `{ chatDir, boardDir, lastChatFile }`
|
|
559
|
+
- `chatDir` — absolute path to the directory holding all chat message files for this card
|
|
560
|
+
- `boardDir` — absolute path to the board runtime directory
|
|
561
|
+
- `lastChatFile` — filename of the user message just written (e.g. `007_user.txt`)
|
|
562
|
+
|
|
563
|
+
### Chat message files
|
|
564
|
+
|
|
565
|
+
Messages are stored as serial-numbered `.txt` files in `chatDir`:
|
|
566
|
+
- `001_user.txt`, `002-assistant.txt`, `003_user.txt`, … (alternating)
|
|
567
|
+
- The handler reads **all** files to reconstruct conversation history, writes the next `<serial>-assistant.txt`
|
|
568
|
+
|
|
569
|
+
### What the handler must do
|
|
570
|
+
|
|
571
|
+
1. Read all `*_user.txt` / `*-assistant.txt` files from `chatDir` (sorted) → conversation history
|
|
572
|
+
2. Build a system prompt scoped to `cardId` / `boardId` as grounding context
|
|
573
|
+
3. Call the LLM directly (e.g. Copilot CLI) with `cwd: boardDir` — running from `boardDir` gives the LLM natural file context
|
|
574
|
+
4. Write the response to `<nextSerial>-assistant.txt` in `chatDir`
|
|
575
|
+
|
|
576
|
+
### System prompt guidance
|
|
577
|
+
|
|
578
|
+
The chat is always scoped to the card where the chat button is embedded. The LLM should:
|
|
579
|
+
- Help the user understand, explore, or act on **that card's data**
|
|
580
|
+
- Be concise — this is an inline embedded chat, not a full conversation window
|
|
581
|
+
- Reference specific values when answering
|
|
582
|
+
- Ask one short clarifying question if intent is ambiguous
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
// Minimal system prompt example:
|
|
586
|
+
`You are a helpful assistant embedded in a live data card (card: "${cardId}", board: "${boardId}").
|
|
587
|
+
Help the user understand and act on the data shown in this card.
|
|
588
|
+
Be concise. Ground answers in the card's data context.`
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### LLM invocation
|
|
592
|
+
|
|
593
|
+
Call Copilot CLI directly from `boardDir` — same pattern as `demo-task-executor.js`:
|
|
594
|
+
```javascript
|
|
595
|
+
execFileSync(copilotBin, ['--allow-all'], {
|
|
596
|
+
input: fullPrompt,
|
|
597
|
+
encoding: 'utf-8',
|
|
598
|
+
cwd: boardDir, // ← run from boardDir for natural file context
|
|
599
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
600
|
+
});
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
No rule-based fallback needed. If the LLM fails, write a short acknowledgment message so the user sees something rather than silence.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "card-portfolio-intelligence",
|
|
3
|
+
"meta": {
|
|
4
|
+
"title": "Portfolio Intelligence",
|
|
5
|
+
"tags": ["portfolio", "analysis"],
|
|
6
|
+
"desc": "AI analysis of portfolio mix and P&L. Publishes structured insights as tokens for downstream risk and action cards."
|
|
7
|
+
},
|
|
8
|
+
"requires": ["positions"],
|
|
9
|
+
"sources": [
|
|
10
|
+
{
|
|
11
|
+
"bindTo": "analysis",
|
|
12
|
+
"outputFile": "card-concentration-analysis.json",
|
|
13
|
+
"copilot": {
|
|
14
|
+
"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
|
+
}
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"compute": [
|
|
19
|
+
{ "bindTo": "mix", "expr": "fetched_sources.analysis.mix" },
|
|
20
|
+
{ "bindTo": "pnl", "expr": "fetched_sources.analysis.pnl" },
|
|
21
|
+
{ "bindTo": "risks", "expr": "fetched_sources.analysis.risks" },
|
|
22
|
+
{ "bindTo": "action", "expr": "fetched_sources.analysis.action" }
|
|
23
|
+
],
|
|
24
|
+
"provides": [
|
|
25
|
+
{ "bindTo": "portfolio_mix", "src": "computed_values.mix" },
|
|
26
|
+
{ "bindTo": "portfolio_pnl", "src": "computed_values.pnl" },
|
|
27
|
+
{ "bindTo": "portfolio_risks", "src": "computed_values.risks" },
|
|
28
|
+
{ "bindTo": "portfolio_action", "src": "computed_values.action" }
|
|
29
|
+
],
|
|
30
|
+
"view": {
|
|
31
|
+
"elements": [
|
|
32
|
+
{ "kind": "markdown", "label": "Mix", "data": { "bind": "computed_values.mix" } },
|
|
33
|
+
{ "kind": "markdown", "label": "P&L", "data": { "bind": "computed_values.pnl" } }
|
|
34
|
+
],
|
|
35
|
+
"layout": {
|
|
36
|
+
"board": { "col": 3, "order": 4 },
|
|
37
|
+
"canvas": { "x": 1300, "y": 50, "w": 340, "h": 200 }
|
|
38
|
+
},
|
|
39
|
+
"features": { "refresh": true, "chat": true }
|
|
40
|
+
},
|
|
41
|
+
"card_data": {}
|
|
42
|
+
}
|