yaml-flow 5.2.1 → 5.2.2

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.
@@ -1,215 +1,27 @@
1
- #!/usr/bin/env node
2
- /**
3
- * demo-chat-handler.js — LLM-based chat handler for example-board.
1
+ /**
2
+ * demo-chat-handler.js - Chat handler for example-board.
4
3
  *
5
- * Protocol (invoked by reusable-server-runtime after a user message is persisted):
4
+ * Invoked by reusable-server-runtime after a user message is persisted:
6
5
  * node demo-chat-handler.js --boardId <id> --cardId <id> --extraEncJson <base64json>
7
6
  *
8
- * --extraEncJson decodes to: { chatDir: "<abs>", boardDir: "<abs>", lastChatFile: "<filename>" }
7
+ * extraEncJson decodes to: { chatDir, boardDir, lastChatFile }
9
8
  *
10
- * Design:
11
- * The chat handler is universal — it does not depend on any source definition or card model.
12
- * It reads the full conversation history from chatDir, builds a grounded system prompt
13
- * (scoped to the card and board), and calls the LLM directly (Copilot CLI).
14
- * Copilot is invoked from boardDir (cwd), so it naturally has access to board files.
15
- *
16
- * The LLM is the sole decision-maker. No rule-based fallback is used here — if the LLM
17
- * is unavailable, the handler writes a short error acknowledgment so the user isn't left
18
- * with a silent failure.
9
+ * If the card has a source entry containing a "copilot" key, invokes copilot_wrapper.bat.
10
+ * Session dir is per-card: os.tmpdir()/demo-chat-handler-sessions/<boardId>_<cardId>
19
11
  */
20
12
 
21
- import * as fs from 'node:fs';
13
+ import * as fs from 'node:fs';
22
14
  import * as path from 'node:path';
23
- import { execFileSync } from 'node:child_process';
24
-
25
- const args = process.argv.slice(2);
26
-
27
- function getArg(name) {
28
- const idx = args.indexOf(name);
29
- return idx !== -1 && args[idx + 1] !== undefined ? args[idx + 1] : null;
30
- }
31
-
32
- const boardId = getArg('--boardId') || '';
33
- const cardId = getArg('--cardId') || '';
34
- const extraStr = getArg('--extraEncJson') || '';
35
-
36
- let extra = {};
37
- try {
38
- extra = JSON.parse(Buffer.from(extraStr, 'base64').toString('utf-8'));
39
- } catch {
40
- console.error('[demo-chat-handler] could not parse --extraEncJson');
41
- process.exit(0);
42
- }
43
-
44
- const { chatDir, boardDir, lastChatFile } = extra;
45
-
46
- if (!chatDir || !lastChatFile) {
47
- console.error('[demo-chat-handler] --extraEncJson must contain chatDir and lastChatFile');
48
- process.exit(0);
49
- }
50
-
51
- // ---------------------------------------------------------------------------
52
- // Read full conversation history from chatDir (all user + assistant turns)
53
- // ---------------------------------------------------------------------------
54
- function readConversationHistory(dir) {
55
- let files;
56
- try {
57
- files = fs.readdirSync(dir).filter(f => /^\d+[-_](user|assistant)\.txt$/i.test(f));
58
- files.sort();
59
- } catch {
60
- return [];
61
- }
62
- return files.map(f => {
63
- const role = /user/i.test(f) ? 'User' : 'Assistant';
64
- let text = '';
65
- try { text = fs.readFileSync(path.join(dir, f), 'utf-8').trim(); } catch {}
66
- return `${role}: ${text}`;
67
- });
68
- }
15
+ import * as os from 'node:os';
16
+ import { spawnSync } from 'node:child_process';
17
+ import { fileURLToPath } from 'node:url';
69
18
 
70
- // ---------------------------------------------------------------------------
71
- // Build prompt: system instruction + conversation turns
72
- // ---------------------------------------------------------------------------
73
- function buildPrompt(bId, cId, history) {
74
- return [
75
- `You are a helpful assistant embedded in a live data card (card: "${cId}", board: "${bId}").`,
76
- 'Help the user understand and act on the data shown in this card.',
77
- 'Be concise — this is an inline card chat, not a full conversation window.',
78
- 'Ground answers in the card\'s data context. Ask one short question if the intent is ambiguous.',
79
- '',
80
- ...history,
81
- 'Assistant:',
82
- ].join('\n');
83
- }
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
84
20
 
85
21
  // ---------------------------------------------------------------------------
86
- // Call Copilot CLI — same pattern as demo-task-executor.js
22
+ // Args
87
23
  // ---------------------------------------------------------------------------
88
- function stripCopilotFooter(rawText) {
89
- const lines = String(rawText ?? '').split(/\r?\n/);
90
- while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
91
- if (
92
- lines.length >= 3 &&
93
- /^Changes\b/i.test(lines[lines.length - 3]) &&
94
- /^Requests\b/i.test(lines[lines.length - 2]) &&
95
- /^Tokens\b/i.test(lines[lines.length - 1])
96
- ) {
97
- lines.splice(lines.length - 3, 3);
98
- }
99
- while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
100
- return lines.join('\n');
101
- }
102
-
103
- function resolveCopilotExecutable() {
104
- const envBin = process.env.COPILOT_BIN;
105
- if (envBin && fs.existsSync(envBin)) return envBin;
106
- if (process.platform === 'win32') {
107
- try {
108
- const out = execFileSync('where.exe', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
109
- const candidates = out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
110
- return candidates.find(p => /\.(cmd|exe|bat)$/i.test(p)) ?? candidates[0] ?? 'copilot';
111
- } catch {}
112
- } else {
113
- try {
114
- const out = execFileSync('which', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
115
- return out.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? 'copilot';
116
- } catch {}
117
- }
118
- return 'copilot';
119
- }
120
-
121
- function runCopilotPrompt(prompt, cwd) {
122
- const copilotBin = resolveCopilotExecutable();
123
- const opts = {
124
- input: String(prompt),
125
- encoding: 'utf-8',
126
- stdio: ['pipe', 'pipe', 'pipe'],
127
- maxBuffer: 10 * 1024 * 1024,
128
- timeout: 60000,
129
- ...(cwd ? { cwd } : {}),
130
- };
131
- try {
132
- return execFileSync(copilotBin, ['--allow-all'], opts);
133
- } catch (directErr) {
134
- if (process.platform === 'win32') {
135
- try {
136
- return execFileSync('cmd.exe', ['/d', '/c', 'copilot --allow-all'], opts);
137
- } catch (cmdErr) {
138
- const msg = [
139
- directErr?.stderr?.trim?.(),
140
- cmdErr?.stderr?.trim?.(),
141
- String(cmdErr?.message ?? cmdErr),
142
- ].filter(Boolean).join(' | ');
143
- throw new Error(msg || 'copilot invocation failed');
144
- }
145
- }
146
- const msg = [directErr?.stderr?.trim?.(), String(directErr?.message ?? directErr)].filter(Boolean).join(' | ');
147
- throw new Error(msg || 'copilot invocation failed');
148
- }
149
- }
150
-
151
- // ---------------------------------------------------------------------------
152
- // Main
153
- // ---------------------------------------------------------------------------
154
- const history = readConversationHistory(chatDir);
155
- const prompt = buildPrompt(boardId, cardId, history);
156
- const cwd = boardDir && fs.existsSync(boardDir) ? boardDir : undefined;
157
-
158
- let response = '';
159
- try {
160
- response = stripCopilotFooter(runCopilotPrompt(prompt, cwd)).trim();
161
- } catch (err) {
162
- const lastUser = [...history].reverse().find(l => l.startsWith('User:')) ?? '';
163
- response = `Sorry, I could not reach the LLM right now. (${String(err?.message ?? err).slice(0, 120)})`;
164
- console.error(`[demo-chat-handler] LLM call failed: ${err?.message ?? err}`);
165
- }
166
-
167
- // Write assistant response as next serial file
168
- const serialMatch = String(lastChatFile).match(/^(\d+)/);
169
- const nextSerial = serialMatch ? parseInt(serialMatch[1], 10) + 1 : 1;
170
- const nextName = `${String(nextSerial).padStart(3, '0')}-assistant.txt`;
171
- const nextPath = path.join(chatDir, nextName);
172
-
173
- try {
174
- fs.writeFileSync(nextPath, response + '\n', 'utf-8');
175
- console.log(`[demo-chat-handler] boardId="${boardId}" cardId="${cardId}" → ${nextPath}`);
176
- } catch (err) {
177
- console.error(`[demo-chat-handler] write failed: ${err.message}`);
178
- }
179
- *
180
- * Protocol (invoked by reusable-server-runtime after a user message is persisted):
181
- * node demo-chat-handler.js --boardId <id> --cardId <id> --extraEncJson <base64json>
182
- *
183
- * --extraEncJson decodes to: { chatDir: "<abs>", boardDir: "<abs>", lastChatFile: "<filename>" }
184
- *
185
- * Responsibilities:
186
- * 1. Read the full conversation history from chatDir (all *_user.txt / *-assistant.txt files).
187
- * 2. Read the current card state from boardDir/board-graph.json (card_data, fetched_sources,
188
- * computed_values for cardId) to use as grounding context.
189
- * 3. Build a system prompt that situates the LLM as an assistant for this specific card,
190
- * including the card's current data as context.
191
- * 4. Send the conversation + context to the LLM (Copilot via CLI).
192
- * 5. Write the response as <nextSerial>-assistant.txt to chatDir.
193
- *
194
- * Design principle:
195
- * The chat is always scoped to the card where the chat button is embedded.
196
- * The card's current state (card_data, computed_values, fetched_sources) is the primary
197
- * grounding context. The LLM should help the user understand, explore, or act on that card's
198
- * data — not give generic answers disconnected from the card's content.
199
- *
200
- * The system prompt should encourage the LLM to:
201
- * - Reference the card's actual values when answering
202
- * - Ask clarifying questions if the user's intent is ambiguous
203
- * - Suggest next steps relevant to the card's domain
204
- * - Be concise (the chat is embedded in a card, not a full chat window)
205
- */
206
-
207
- import * as fs from 'node:fs';
208
- import * as path from 'node:path';
209
- import { execFileSync } from 'node:child_process';
210
-
211
24
  const args = process.argv.slice(2);
212
-
213
25
  function getArg(name) {
214
26
  const idx = args.indexOf(name);
215
27
  return idx !== -1 && args[idx + 1] !== undefined ? args[idx + 1] : null;
@@ -220,190 +32,120 @@ const cardId = getArg('--cardId') || '';
220
32
  const extraStr = getArg('--extraEncJson') || '';
221
33
 
222
34
  let extra = {};
223
- try {
224
- extra = JSON.parse(Buffer.from(extraStr, 'base64').toString('utf-8'));
225
- } catch {
226
- console.error('[demo-chat-handler] could not parse --extraEncJson');
227
- process.exit(0);
228
- }
35
+ try { extra = JSON.parse(Buffer.from(extraStr, 'base64').toString('utf-8')); }
36
+ catch { console.error('[demo-chat-handler] bad --extraEncJson'); process.exit(0); }
229
37
 
230
38
  const { chatDir, boardDir, lastChatFile } = extra;
231
-
232
- if (!chatDir || !lastChatFile) {
233
- console.error('[demo-chat-handler] --extraEncJson must contain chatDir and lastChatFile');
39
+ console.error('[demo-chat-handler] missing chatDir/lastChatFile');
234
40
  process.exit(0);
235
41
  }
236
42
 
237
43
  // ---------------------------------------------------------------------------
238
- // 1. Read full conversation history from chatDir
44
+ // Read conversation history
239
45
  // ---------------------------------------------------------------------------
240
- function readConversationHistory(dir) {
241
- let files;
46
+ function readHistory(dir) {
242
47
  try {
243
- files = fs.readdirSync(dir).filter(f => /^\d+[-_](user|assistant)\.txt$/i.test(f));
244
- files.sort();
245
- } catch {
246
- return [];
247
- }
248
- return files.map(f => {
249
- const role = /user/i.test(f) ? 'user' : 'assistant';
250
- let text = '';
251
- try { text = fs.readFileSync(path.join(dir, f), 'utf-8').trim(); } catch {}
252
- return { role, text };
253
- });
48
+ return fs.readdirSync(dir)
49
+ .filter(f => /^\d+[-_](user|assistant)\.txt$/i.test(f))
50
+ .sort()
51
+ .map(f => {
52
+ const role = /user/i.test(f) ? 'User' : 'Assistant';
53
+ let text = '';
54
+ try { text = fs.readFileSync(path.join(dir, f), 'utf-8').trim(); } catch {}
55
+ return role + ': ' + text;
56
+ });
57
+ } catch { return []; }
254
58
  }
255
59
 
256
60
  // ---------------------------------------------------------------------------
257
- // 2. Read card state from board-graph.json
61
+ // Check if card has a 'copilot' source entry
258
62
  // ---------------------------------------------------------------------------
259
- function readCardState(bDir, cId) {
260
- if (!bDir) return null;
63
+ function hasCopilotSource(bDir, cId) {
261
64
  try {
262
65
  const boardGraph = JSON.parse(fs.readFileSync(path.join(bDir, 'board-graph.json'), 'utf-8'));
263
- // board-graph.json wraps a LiveGraph snapshot; cards live under graph.nodes
264
66
  const nodes = boardGraph?.graph?.nodes ?? boardGraph?.nodes ?? {};
265
- return nodes[cId] ?? null;
266
- } catch {
267
- return null;
268
- }
67
+ const card = nodes[cId];
68
+ const sources = card.sources ?? card.card_data?.sources ?? [];
69
+ if (Array.isArray(sources)) return sources.some(s => s && typeof s === 'object' && 'copilot' in s);
70
+ if (typeof sources === 'object') return 'copilot' in sources;
71
+ return false;
72
+ } catch { return false; }
269
73
  }
270
74
 
271
75
  // ---------------------------------------------------------------------------
272
- // 3. Build system prompt grounded in the card's current state
76
+ // Build prompt
273
77
  // ---------------------------------------------------------------------------
274
- function buildSystemPrompt(cId, cardState) {
275
- const lines = [
276
- `You are a helpful assistant embedded inside a live card (id: "${cId}") on a data dashboard.`,
277
- 'Your role is to help the user understand, interpret, and act on the data shown in this card.',
278
- 'Always ground your answers in the card\'s actual current values. Be concise — this is an embedded card chat, not a full conversation window.',
279
- 'If the user\'s question is ambiguous, ask one short clarifying question.',
280
- 'Suggest relevant next steps or insights when appropriate.',
78
+ function buildPrompt(cId, bId, history) {
79
+ return [
80
+ 'You are a helpful assistant embedded in a live card (card: "' + cId + '", board: "' + bId + '").',
81
+ 'Help the user understand and act on the data shown in this card.',
82
+ 'Be concise.',
281
83
  '',
282
- '--- Current card state ---',
283
- ];
284
-
285
- if (cardState) {
286
- if (cardState.card_data && Object.keys(cardState.card_data).length > 0) {
287
- lines.push('card_data: ' + JSON.stringify(cardState.card_data, null, 2));
288
- }
289
- if (cardState.computed_values && Object.keys(cardState.computed_values).length > 0) {
290
- lines.push('computed_values: ' + JSON.stringify(cardState.computed_values, null, 2));
291
- }
292
- if (cardState.fetched_sources && Object.keys(cardState.fetched_sources).length > 0) {
293
- lines.push('fetched_sources: ' + JSON.stringify(cardState.fetched_sources, null, 2));
294
- }
295
- } else {
296
- lines.push('(card state not available)');
297
- }
298
-
299
- lines.push('--- End card state ---');
300
- return lines.join('\n');
84
+ ...history,
85
+ 'Assistant:',
86
+ ].join('\n');
301
87
  }
302
88
 
303
89
  // ---------------------------------------------------------------------------
304
- // 4. Build the full prompt (system + conversation turns)
90
+ // Invoke copilot_wrapper.bat
305
91
  // ---------------------------------------------------------------------------
306
- function buildPrompt(systemPrompt, history) {
307
- const parts = [systemPrompt, ''];
308
- for (const turn of history) {
309
- parts.push(`${turn.role === 'user' ? 'User' : 'Assistant'}: ${turn.text}`);
310
- }
311
- parts.push('Assistant:');
312
- return parts.join('\n');
313
- }
92
+ function runWrapper(prompt, sessionDir, workingDir) {
93
+ const wrapperPath = path.join(__dirname, 'scripts', 'copilot_wrapper.bat');
94
+ const tmpBase = os.tmpdir();
95
+ const ts = Date.now();
96
+ const outFile = path.join(tmpBase, 'dch-out-' + cardId + '-' + ts + '.txt');
97
+ const promptFile = path.join(tmpBase, 'dch-prompt-' + cardId + '-' + ts + '.txt');
314
98
 
315
- // ---------------------------------------------------------------------------
316
- // 5. Call LLM (Copilot CLI)
317
- // ---------------------------------------------------------------------------
318
- function resolveCopilotExecutable() {
319
- const envBin = process.env.COPILOT_BIN;
320
- if (envBin && fs.existsSync(envBin)) return envBin;
99
+ fs.mkdirSync(sessionDir, { recursive: true });
100
+ fs.writeFileSync(promptFile, prompt, 'utf-8');
321
101
 
322
- if (process.platform === 'win32') {
323
- try {
324
- const out = execFileSync('where.exe', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
325
- const candidates = out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
326
- return candidates.find(p => /\.(cmd|exe|bat)$/i.test(p)) ?? candidates[0] ?? 'copilot';
327
- } catch {}
328
- } else {
329
- try {
330
- const out = execFileSync('which', ['copilot'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
331
- return out.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? 'copilot';
332
- } catch {}
333
- }
334
- return 'copilot';
335
- }
336
-
337
- function stripCopilotFooter(rawText) {
338
- const lines = String(rawText ?? '').split(/\r?\n/);
339
- while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
340
- if (
341
- lines.length >= 3 &&
342
- /^Changes\b/i.test(lines[lines.length - 3]) &&
343
- /^Requests\b/i.test(lines[lines.length - 2]) &&
344
- /^Tokens\b/i.test(lines[lines.length - 1])
345
- ) {
346
- lines.splice(lines.length - 3, 3);
347
- }
348
- while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
349
- return lines.join('\n');
350
- }
351
-
352
- function runLLM(prompt) {
353
- const copilotBin = resolveCopilotExecutable();
354
102
  try {
355
- const raw = execFileSync(copilotBin, ['--allow-all'], {
356
- input: String(prompt),
357
- encoding: 'utf-8',
358
- stdio: ['pipe', 'pipe', 'pipe'],
359
- maxBuffer: 10 * 1024 * 1024,
360
- timeout: 60000,
361
- });
362
- return stripCopilotFooter(raw).trim();
363
- } catch (err) {
364
- if (process.platform === 'win32') {
365
- try {
366
- const raw = execFileSync('cmd.exe', ['/d', '/c', 'copilot --allow-all'], {
367
- input: String(prompt),
368
- encoding: 'utf-8',
369
- stdio: ['pipe', 'pipe', 'pipe'],
370
- maxBuffer: 10 * 1024 * 1024,
371
- timeout: 60000,
372
- });
373
- return stripCopilotFooter(raw).trim();
374
- } catch {}
375
- }
376
- throw err;
103
+ spawnSync('cmd.exe', [
104
+ '/c', wrapperPath,
105
+ outFile,
106
+ sessionDir,
107
+ workingDir,
108
+ '@' + promptFile,
109
+ 'raw',
110
+ 'demo-chat',
111
+ ], { stdio: 'inherit', timeout: 120000 });
112
+
113
+ return fs.existsSync(outFile) ? fs.readFileSync(outFile, 'utf-8').trim() : '';
114
+ } finally {
115
+ try { fs.unlinkSync(promptFile); } catch {}
116
+ try { fs.unlinkSync(outFile); } catch {}
377
117
  }
378
118
  }
379
119
 
380
120
  // ---------------------------------------------------------------------------
381
121
  // Main
382
122
  // ---------------------------------------------------------------------------
383
- const history = readConversationHistory(chatDir);
384
- const cardState = readCardState(boardDir, cardId);
385
- const systemPmt = buildSystemPrompt(cardId, cardState);
386
- const fullPrompt = buildPrompt(systemPmt, history);
123
+ const history = readHistory(chatDir);
124
+ const sessionDir = path.join(os.tmpdir(), 'demo-chat-handler-sessions', boardId + '_' + cardId);
125
+ const workingDir = boardDir || process.cwd();
126
+ const prompt = buildPrompt(cardId, boardId, history);
387
127
 
388
128
  let response = '';
389
- try {
390
- response = runLLM(fullPrompt);
391
- } catch (err) {
392
- // Fallback: acknowledge the message so the user sees something
393
- const lastUserMsg = [...history].reverse().find(t => t.role === 'user')?.text ?? '';
394
- response = `I received your message ("${lastUserMsg.slice(0, 80)}") but could not reach the LLM right now. Please try again.`;
395
- console.error(`[demo-chat-handler] LLM call failed: ${err && err.message || err}`);
129
+ if (hasCopilotSource(boardDir, cardId)) {
130
+ try {
131
+ response = runWrapper(prompt, sessionDir, workingDir);
132
+ } catch (err) {
133
+ response = 'Sorry, I could not reach the LLM right now. (' + String(err?.message ?? err).slice(0, 120) + ')';
134
+ console.error('[demo-chat-handler] wrapper failed: ' + (err?.message ?? err));
135
+ }
136
+ } else {
137
+ response = 'No copilot source configured for this card.';
396
138
  }
397
139
 
398
- // Derive next serial and write assistant response
140
+ // Write assistant response as next serial file
399
141
  const serialMatch = String(lastChatFile).match(/^(\d+)/);
400
142
  const nextSerial = serialMatch ? parseInt(serialMatch[1], 10) + 1 : 1;
401
- const nextName = `${String(nextSerial).padStart(3, '0')}-assistant.txt`;
143
+ const nextName = String(nextSerial).padStart(3, '0') + '-assistant.txt';
402
144
  const nextPath = path.join(chatDir, nextName);
403
145
 
404
146
  try {
405
147
  fs.writeFileSync(nextPath, response + '\n', 'utf-8');
406
- console.log(`[demo-chat-handler] boardId="${boardId}" cardId="${cardId}" wrote response ${nextPath}`);
148
+ console.log('[demo-chat-handler] cardId="' + cardId + '" wrote response -> ' + nextPath);
407
149
  } catch (err) {
408
- console.error(`[demo-chat-handler] write failed: ${err.message}`);
409
- }
150
+ console.error('[demo-chat-handler] write failed: ' + err.message);
151
+ }
@@ -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.1/browser/card-compute.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.1/browser/live-cards.js"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.1/browser/board-livegraph-engine.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/card-compute.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/live-cards.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/board-livegraph-engine.js"></script>
12
12
  </head>
13
13
  <body class="bg-light">
14
14
  <div class="container-fluid py-3">
@@ -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.1/browser/card-compute.js"></script>
20
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.1/browser/live-cards.js"></script>
21
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.1/browser/board-livegraph-engine.js"></script>
22
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.1/browser/board-livecards-runtime-client.js"></script>
19
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/card-compute.js"></script>
20
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/live-cards.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/board-livegraph-engine.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@5.2.2/browser/board-livecards-runtime-client.js"></script>
23
23
  </head>
24
24
  <body class="bg-light">
25
25
  <div class="container-fluid py-3">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml-flow",
3
- "version": "5.2.1",
3
+ "version": "5.2.2",
4
4
  "description": "Unified workflow engine: step-machine (sequential) + event-graph (stateless DAG) with pluggable storage",
5
5
  "author": "",
6
6
  "license": "MIT",