yaml-flow 8.2.5 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/browser/asset-integrity.json +3 -3
  2. package/browser/board-livecards-client.js +1 -1
  3. package/browser/board-livecards-localstorage.js +4 -4
  4. package/browser/live-cards.js +19 -19
  5. package/cli/{board-live-cards-lib-Iq_XAC09.d.ts → board-live-cards-lib-tjYsPt5U.d.ts} +1 -1
  6. package/cli/browser-api/board-live-cards-browser-adapter.d.ts +3 -3
  7. package/cli/browser-api/card-store-browser-api.d.ts +1 -1
  8. package/cli/{execution-interface-ftO1W7Po.d.ts → execution-interface-CrG5gzAx.d.ts} +116 -2
  9. package/cli/node/batch-runner-cli.d.ts +3 -0
  10. package/cli/node/batch-runner-cli.js +2 -1
  11. package/cli/node/board-live-cards-cli.js +9 -9
  12. package/cli/node/chat-store-cli.d.ts +23 -0
  13. package/cli/node/chat-store-cli.js +8 -0
  14. package/cli/node/execution-adapter.d.ts +4 -2
  15. package/cli/node/execution-adapter.js +2 -2
  16. package/cli/node/fs-board-adapter.d.ts +7 -6
  17. package/cli/node/fs-board-adapter.js +8 -8
  18. package/cli/node/source-cli-task-executor.js +4 -4
  19. package/cli/node/step-machine-cli.js +3 -3
  20. package/cli/{types--rXGWbSR.d.ts → types-PUfPBxc_.d.ts} +4 -109
  21. package/examples/board/demo-shell-with-server.html +3 -196
  22. package/examples/board/doc.html +465 -0
  23. package/examples/board/server/board-server.js +20 -81
  24. package/examples/board/server/board-worker/source_def_flows.json +2 -2
  25. package/examples/board/server/chat-flow/copilot-chat/assistant.js +44 -185
  26. package/examples/board/server/chat-flow/copilot-chat/copilot_wrapper.bat +157 -0
  27. package/examples/board/server/chat-flow/copilot-chat/copilot_wrapper_helper.ps1 +190 -0
  28. package/examples/board/server/chat-flow/flow-steps.json +122 -56
  29. package/examples/board/test/server-http-test.js +252 -220
  30. package/examples/board-local/demo-shell-localstorage.html +3 -3
  31. package/lib/{artifacts-store-lib-public-C5UL5tyG.d.cts → artifacts-store-lib-public-Cz8-kdXG.d.cts} +1 -1
  32. package/lib/{artifacts-store-lib-public-GD4H-fFp.d.ts → artifacts-store-lib-public-ksGIExhc.d.ts} +1 -1
  33. package/lib/artifacts-store-public.d.cts +2 -2
  34. package/lib/artifacts-store-public.d.ts +2 -2
  35. package/lib/board-live-cards-node.cjs +8 -8
  36. package/lib/board-live-cards-node.d.cts +26 -6
  37. package/lib/board-live-cards-node.d.ts +26 -6
  38. package/lib/board-live-cards-node.js +8 -8
  39. package/lib/{board-live-cards-public-BLXbcBNk.d.cts → board-live-cards-public-BwZYGAsF.d.cts} +1 -1
  40. package/lib/{board-live-cards-public-BZaNb2mi.d.ts → board-live-cards-public-DWpZVDXN.d.ts} +1 -1
  41. package/lib/board-live-cards-public.d.cts +1 -1
  42. package/lib/board-live-cards-public.d.ts +1 -1
  43. package/lib/board-live-cards-server-runtime.cjs +3 -3
  44. package/lib/board-live-cards-server-runtime.d.cts +3 -2
  45. package/lib/board-live-cards-server-runtime.d.ts +3 -2
  46. package/lib/board-live-cards-server-runtime.js +3 -3
  47. package/lib/board-worker-adapter.cjs +2 -2
  48. package/lib/board-worker-adapter.js +2 -2
  49. package/lib/card-store-public.d.cts +1 -1
  50. package/lib/card-store-public.d.ts +1 -1
  51. package/lib/chat-storage-lib-BK5Njslc.d.ts +53 -0
  52. package/lib/chat-storage-lib-C5bQ7bGe.d.cts +53 -0
  53. package/lib/chat-store-public.cjs +2 -0
  54. package/lib/chat-store-public.d.cts +128 -0
  55. package/lib/chat-store-public.d.ts +128 -0
  56. package/lib/chat-store-public.js +2 -0
  57. package/lib/execution-refs.d.cts +10 -1
  58. package/lib/execution-refs.d.ts +10 -1
  59. package/lib/server-runtime/index.cjs +3 -3
  60. package/lib/server-runtime/index.d.cts +4 -3
  61. package/lib/server-runtime/index.d.ts +4 -3
  62. package/lib/server-runtime/index.js +3 -3
  63. package/lib/{types-Bztd1KoK.d.cts → types-D9B0Vrwg.d.cts} +4 -53
  64. package/lib/{types-D-xVWPdY.d.ts → types-DNYhCFNJ.d.ts} +4 -53
  65. package/package.json +8 -2
  66. package/examples/board/.board-ws/cards/store/_index.json +0 -17
  67. package/examples/board/.board-ws/cards/store/card-market-prices.json +0 -80
  68. package/examples/board/.board-ws/cards/store/card-portfolio-value.json +0 -90
  69. package/examples/board/.board-ws/cards/store/card-portfolio.json +0 -78
  70. package/examples/board/server/chat-flow/chat-clear-processing.js +0 -41
  71. package/examples/board/server/chat-flow/chat-open-turn.js +0 -144
  72. package/examples/board/server/chat-flow/chat-write-assistant.js +0 -44
  73. package/examples/board/server/chat-flow/echo-probe/assistant.js +0 -28
@@ -13,12 +13,17 @@
13
13
  * node test/server-http-test.js [--port 7799]
14
14
  */
15
15
 
16
- import { spawn } from 'node:child_process';
16
+ import { spawn, spawnSync } from 'node:child_process';
17
17
  import { Worker } from 'node:worker_threads';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import path from 'node:path';
20
20
  import http from 'node:http';
21
+ import net from 'node:net';
21
22
  import fs from 'node:fs';
23
+ import os from 'node:os';
24
+
25
+ const ECHO_PROBE_MARKER = '__probe__echo__probe__';
26
+ const PROBE_IN_PROGRESS_TEXT = 'in-progress';
22
27
 
23
28
  const __filename = fileURLToPath(import.meta.url);
24
29
  const __dirname = path.dirname(__filename);
@@ -26,6 +31,18 @@ const __dirname = path.dirname(__filename);
26
31
  const cliArgs = process.argv.slice(2);
27
32
  const portArg = cliArgs.indexOf('--port');
28
33
  const cliPort = portArg !== -1 ? parseInt(cliArgs[portArg + 1], 10) : NaN;
34
+ const skipT1 = cliArgs.includes('--skip-t1');
35
+ const skipT2 = cliArgs.includes('--skip-t2');
36
+ const skipT3 = cliArgs.includes('--skip-t3');
37
+ function isCopilotAvailable() {
38
+ try {
39
+ const r = spawnSync('copilot', ['--version'], { timeout: 5_000, stdio: 'ignore', windowsHide: true });
40
+ return !r.error;
41
+ } catch { return false; }
42
+ }
43
+
44
+ const skipT3a = cliArgs.includes('--skip-t3a') || !isCopilotAvailable();
45
+ const skipT3b = cliArgs.includes('--skip-t3b');
29
46
  const RUN_ID = `run-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
30
47
 
31
48
  const BOARD_ID = 'live';
@@ -35,34 +52,28 @@ const SSE_WORKER_SCRIPT = path.join(__dirname, 'sse-worker.js');
35
52
  const CARD_PATTERN = 'cardT*';
36
53
  const CHAT_CARD_ID = 'card-portfolio';
37
54
 
38
- function resolveServerPort() {
55
+ function findFreePort() {
56
+ return new Promise((resolve, reject) => {
57
+ const srv = net.createServer();
58
+ srv.listen(0, '127.0.0.1', () => {
59
+ const addr = /** @type {import('node:net').AddressInfo} */ (srv.address());
60
+ srv.close(() => resolve(addr.port));
61
+ });
62
+ srv.on('error', reject);
63
+ });
64
+ }
65
+
66
+ async function resolveServerPort() {
39
67
  if (Number.isInteger(cliPort) && cliPort > 0) return cliPort;
40
- const configPath = path.join(BOARD_DIR, 'server-config.json');
41
- try {
42
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
43
- const configured = Number(cfg?.port);
44
- if (Number.isInteger(configured) && configured > 0) return configured;
45
- } catch { /* ignore */ }
46
- return 7799;
68
+ return findFreePort();
47
69
  }
48
70
 
49
- const PORT = resolveServerPort();
71
+ const PORT = await resolveServerPort();
50
72
  const BASE = `http://127.0.0.1:${PORT}/api/boards/${BOARD_ID}`;
51
73
 
52
- // Resolve and wipe the setup directory so each test run starts clean.
74
+ // Always use a system temp directory so parallel runs and vitest don't collide.
53
75
  function resolveSetupDirRoot() {
54
- const configPath = path.join(BOARD_DIR, 'server-config.json');
55
- try {
56
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
57
- const boardSetupDir = cfg?.boards?.[BOARD_ID]?.setupDir;
58
- if (typeof boardSetupDir === 'string' && boardSetupDir.trim()) {
59
- return path.resolve(BOARD_DIR, boardSetupDir.trim());
60
- }
61
- if (cfg && typeof cfg.setupDir === 'string' && cfg.setupDir.trim()) {
62
- return path.resolve(BOARD_DIR, cfg.setupDir.trim());
63
- }
64
- } catch { /* ignore */ }
65
- return path.join(BOARD_DIR, '.demo-setup');
76
+ return os.tmpdir();
66
77
  }
67
78
 
68
79
  const SETUP_DIR = path.join(resolveSetupDirRoot(), RUN_ID);
@@ -220,6 +231,49 @@ const waitForAllCompleted = (ms = 60_000, label = 'all completed') =>
220
231
  const waitForChatPredicate = (predicate, ms, label) =>
221
232
  waitUntil(() => predicate(NS.chatEvents) || false, ms, label);
222
233
 
234
+ function deriveProbeLifecycleMilestones(events, opts) {
235
+ const milestones = [];
236
+ let prevMessageCount = Number(opts.beforeCount || 0);
237
+ let prevProcessing = Boolean(opts.beforeProcessing);
238
+ const prompt = String(opts.prompt || '');
239
+ const inProgressText = String(opts.inProgressText || PROBE_IN_PROGRESS_TEXT);
240
+
241
+ for (const event of events) {
242
+ const messages = Array.isArray(event?.messages) ? event.messages : [];
243
+ const nextMessageCount = Number(event?.messageCount || messages.length || 0);
244
+ const newMessages = nextMessageCount > prevMessageCount
245
+ ? messages.slice(prevMessageCount, nextMessageCount)
246
+ : [];
247
+
248
+ for (const message of newMessages) {
249
+ const role = String(message?.role || '');
250
+ const text = String(message?.text || '');
251
+ if (role === 'user' && text.includes(prompt)) milestones.push('user');
252
+ else if (role === 'system' && text.trim().toLowerCase() === inProgressText) milestones.push('in-progress');
253
+ else if (role === 'assistant' && text.includes(`Echo: ${prompt}`)) milestones.push('assistant');
254
+ }
255
+
256
+ const processing = Boolean(event?.processing);
257
+ if (processing !== prevProcessing) milestones.push(processing ? 'processing-true' : 'processing-false');
258
+
259
+ prevMessageCount = nextMessageCount;
260
+ prevProcessing = processing;
261
+ }
262
+
263
+ return milestones;
264
+ }
265
+
266
+ function matchOrderedProbeLifecycle(events, opts) {
267
+ const milestones = deriveProbeLifecycleMilestones(events, opts);
268
+ if (milestones.length !== 5) return false;
269
+ const firstPair = milestones.slice(0, 2);
270
+ const lastPair = milestones.slice(3, 5);
271
+ const firstOk = firstPair.includes('user') && firstPair.includes('processing-true');
272
+ const middleOk = milestones[2] === 'in-progress';
273
+ const lastOk = lastPair.includes('assistant') && lastPair.includes('processing-false');
274
+ return (firstOk && middleOk && lastOk) ? { milestones } : false;
275
+ }
276
+
223
277
  function httpGet(url) {
224
278
  return new Promise((resolve, reject) => {
225
279
  http.get(url, (res) => {
@@ -402,7 +456,10 @@ try {
402
456
  console.log(`[T0] ok: ${t0Positions.length} positions computed`);
403
457
 
404
458
  // ── T1: PATCH holdings (+1 row), verify recomputation ──
405
- console.log('\n=== T1: patch holdings (+1 row) ===');
459
+ if (skipT1) {
460
+ console.log('\n=== T1: skipped (--skip-t1) ===');
461
+ } else {
462
+ console.log('\n=== T1: patch holdings (+1 row) ===');
406
463
 
407
464
  // Read current holdings from card store
408
465
  const portfolioCardRes = await httpGet(`${BASE}/cards/card-portfolio`);
@@ -446,49 +503,57 @@ try {
446
503
  `Expected positions rows +1 (before=${t0PositionsCount}, after=${afterPositionsCount})`);
447
504
  console.log(`[T1] ok: holdings ${t0HoldingsCount}->${afterHoldingsCount}, ` +
448
505
  `positions ${t0PositionsCount}->${afterPositionsCount}, added=${newTicker}`);
506
+ }
449
507
 
450
508
  // ── T2: plain file upload API + card_data.files + download roundtrip ──
451
- console.log('\n=== T2: plain file upload -> card_data.files -> download ===');
452
- const t2CardBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
453
- assert(t2CardBefore.status === 200, `T2 pre card read returned ${t2CardBefore.status}`);
454
- const t2FilesBefore = Array.isArray(t2CardBefore.data?.card_data?.files)
455
- ? t2CardBefore.data.card_data.files
456
- : [];
457
- const t2BeforeCount = t2FilesBefore.length;
458
-
459
- const t2UploadText = `plain-file-upload-${Date.now()}`;
460
- const t2UploadName = 't2-upload.txt';
461
- const t2UploadRes = await httpUploadChatFile(
462
- `${BASE}/cards/${CHAT_CARD_ID}/files`,
463
- t2UploadName,
464
- t2UploadText,
465
- );
466
- assert(t2UploadRes.status === 200, `T2 file upload returned ${t2UploadRes.status}`);
467
- const t2UploadedFile = t2UploadRes.data?.file;
468
- assert(t2UploadedFile && typeof t2UploadedFile === 'object', 'T2 upload response missing file metadata');
469
- assert(String(t2UploadedFile?.name || '') === t2UploadName, 'T2 uploaded file name mismatch');
470
-
471
- const t2CardAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
472
- assert(t2CardAfter.status === 200, `T2 post card read returned ${t2CardAfter.status}`);
473
- const t2FilesAfter = Array.isArray(t2CardAfter.data?.card_data?.files)
474
- ? t2CardAfter.data.card_data.files
475
- : [];
476
- assert(t2FilesAfter.length === t2BeforeCount + 1, `T2 expected files +1 (before=${t2BeforeCount}, after=${t2FilesAfter.length})`);
477
-
478
- const t2FileIndex = t2FilesAfter.findIndex((f) => String(f?.stored_name || '') === String(t2UploadedFile?.stored_name || ''));
479
- assert(t2FileIndex >= 0, 'T2 uploaded file metadata not found in card_data.files');
480
-
481
- const t2DownloadRes = await httpGetRaw(
482
- `${BASE}/cards/${CHAT_CARD_ID}/files/${t2FileIndex}?sn=${encodeURIComponent(String(t2UploadedFile?.stored_name || ''))}`,
483
- );
484
- assert(t2DownloadRes.status === 200, `T2 file download returned ${t2DownloadRes.status}`);
485
- const t2DownloadedText = t2DownloadRes.body.toString('utf-8');
486
- assert(t2DownloadedText === t2UploadText, 'T2 downloaded content mismatch');
487
- console.log('[T2] ok: card_data.files updated and file download endpoint returned exact bytes');
509
+ if (skipT2) {
510
+ console.log('\n=== T2: skipped (--skip-t2) ===');
511
+ } else {
512
+ console.log('\n=== T2: plain file upload -> card_data.files -> download ===');
513
+ const t2CardBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
514
+ assert(t2CardBefore.status === 200, `T2 pre card read returned ${t2CardBefore.status}`);
515
+ const t2FilesBefore = Array.isArray(t2CardBefore.data?.card_data?.files)
516
+ ? t2CardBefore.data.card_data.files
517
+ : [];
518
+ const t2BeforeCount = t2FilesBefore.length;
519
+
520
+ const t2UploadText = `plain-file-upload-${Date.now()}`;
521
+ const t2UploadName = 't2-upload.txt';
522
+ const t2UploadRes = await httpUploadChatFile(
523
+ `${BASE}/cards/${CHAT_CARD_ID}/files`,
524
+ t2UploadName,
525
+ t2UploadText,
526
+ );
527
+ assert(t2UploadRes.status === 200, `T2 file upload returned ${t2UploadRes.status}`);
528
+ const t2UploadedFile = t2UploadRes.data?.file;
529
+ assert(t2UploadedFile && typeof t2UploadedFile === 'object', 'T2 upload response missing file metadata');
530
+ assert(String(t2UploadedFile?.name || '') === t2UploadName, 'T2 uploaded file name mismatch');
531
+
532
+ const t2CardAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}`);
533
+ assert(t2CardAfter.status === 200, `T2 post card read returned ${t2CardAfter.status}`);
534
+ const t2FilesAfter = Array.isArray(t2CardAfter.data?.card_data?.files)
535
+ ? t2CardAfter.data.card_data.files
536
+ : [];
537
+ assert(t2FilesAfter.length === t2BeforeCount + 1, `T2 expected files +1 (before=${t2BeforeCount}, after=${t2FilesAfter.length})`);
538
+
539
+ const t2FileIndex = t2FilesAfter.findIndex((f) => String(f?.stored_name || '') === String(t2UploadedFile?.stored_name || ''));
540
+ assert(t2FileIndex >= 0, 'T2 uploaded file metadata not found in card_data.files');
541
+
542
+ const t2DownloadRes = await httpGetRaw(
543
+ `${BASE}/cards/${CHAT_CARD_ID}/files/${t2FileIndex}?sn=${encodeURIComponent(String(t2UploadedFile?.stored_name || ''))}`,
544
+ );
545
+ assert(t2DownloadRes.status === 200, `T2 file download returned ${t2DownloadRes.status}`);
546
+ const t2DownloadedText = t2DownloadRes.body.toString('utf-8');
547
+ assert(t2DownloadedText === t2UploadText, 'T2 downloaded content mismatch');
548
+ console.log('[T2] ok: card_data.files updated and file download endpoint returned exact bytes');
549
+ }
488
550
 
489
551
  // ── T3*: chat protocol over API + SSE ──
490
552
  {
491
- console.log('\n=== T3: probe chat protocol (SSE lifecycle) ===');
553
+ if (skipT3) {
554
+ console.log('\n=== T3: skipped (--skip-t3) ===');
555
+ } else {
556
+ console.log(`\n[${new Date().toISOString()}] === T3: probe chat protocol (SSE lifecycle) ===`);
492
557
  chatSseClientId = `chat-proto-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
493
558
  chatSseClient = startSseClient(`${BASE}/sse?clientId=${encodeURIComponent(chatSseClientId)}`, (payload) => {
494
559
  captureChatEvents(payload, CHAT_CARD_ID);
@@ -502,189 +567,156 @@ try {
502
567
  assert(t2Before.status === 200, `T3 pre chats returned ${t2Before.status}`);
503
568
  const t2BeforeMessages = Array.isArray(t2Before.data?.messages) ? t2Before.data.messages : [];
504
569
  const t2BeforeCount = t2BeforeMessages.length;
570
+ const t2EventStart = NS.chatEvents.length;
505
571
  const t2ProbePrompt = `Probe protocol validation ${Date.now()}`;
506
572
 
507
573
  const t2SendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
508
574
  actionType: 'chat-send',
509
575
  payload: {
510
- text: JSON.stringify({
511
- prompt: t2ProbePrompt,
512
- probe: true,
513
- chatTimeMs: 2200,
514
- chatTimeoutMs: 20000,
515
- }),
576
+ text: `${ECHO_PROBE_MARKER}${t2ProbePrompt}${ECHO_PROBE_MARKER}`,
516
577
  },
517
578
  });
518
579
  assert(t2SendRes.status === 200, `T3 chat-send returned ${t2SendRes.status}`);
519
580
 
520
- const t2UserOrProcessing = await waitForChatPredicate((events) => {
521
- const slice = events.filter((e) => e.messageCount >= t2BeforeCount + 1 || e.processing === true);
522
- return slice.length > 0 ? slice[slice.length - 1] : false;
523
- }, 30_000, 'T3 user/proc signal');
524
- assert(!!t2UserOrProcessing, 'T3 missing user/proc signal');
525
-
526
- const t2Assistant = await waitForChatPredicate((events) => {
527
- for (let i = events.length - 1; i >= 0; i -= 1) {
528
- const e = events[i];
529
- if (e.messageCount < t2BeforeCount + 2) continue;
530
- const last = e.messages[e.messages.length - 1];
531
- if (last?.role === 'assistant' && String(last.text || '').includes(`Echo: ${t2ProbePrompt}`)) {
532
- return e;
533
- }
534
- }
535
- return false;
536
- }, 45_000, 'T3 assistant echo');
537
- assert(!!t2Assistant, 'T3 assistant echo not observed on SSE');
538
-
539
- const t2ProcessingCleared = await waitForChatPredicate((events) => {
540
- for (let i = events.length - 1; i >= 0; i -= 1) {
541
- const e = events[i];
542
- if (e.messageCount >= t2BeforeCount + 2 && e.processing === false) return e;
543
- }
544
- return false;
545
- }, 30_000, 'T3 processing clear');
546
- assert(!!t2ProcessingCleared, 'T3 processing clear not observed');
581
+ const t2Lifecycle = await waitForChatPredicate((events) => {
582
+ return matchOrderedProbeLifecycle(events.slice(t2EventStart), {
583
+ beforeCount: t2BeforeCount,
584
+ beforeProcessing: false,
585
+ prompt: t2ProbePrompt,
586
+ inProgressText: PROBE_IN_PROGRESS_TEXT,
587
+ });
588
+ }, 45_000, 'T3 ordered lifecycle');
589
+ assert(!!t2Lifecycle, 'T3 ordered lifecycle not observed');
547
590
 
548
591
  const t2After = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
549
592
  assert(t2After.status === 200, `T3 post chats returned ${t2After.status}`);
550
593
  const t2AfterMessages = Array.isArray(t2After.data?.messages) ? t2After.data.messages : [];
551
594
  const t2NewMessages = t2AfterMessages.slice(t2BeforeCount);
552
- assert(t2NewMessages.length >= 2, `T3 expected at least 2 new chat messages, got ${t2NewMessages.length}`);
595
+ assert(t2NewMessages.length >= 3, `T3 expected at least 3 new chat messages, got ${t2NewMessages.length}`);
553
596
  const t2User = t2NewMessages.find((m) => m?.role === 'user');
597
+ const t2InProgress = t2NewMessages.find((m) => m?.role === 'system' && String(m?.text || '').trim().toLowerCase() === PROBE_IN_PROGRESS_TEXT);
554
598
  const t2AssistantMsg = t2NewMessages.find((m) => m?.role === 'assistant');
555
599
  assert(!!t2User && typeof t2User.id === 'string', 'T3 user chat message missing id');
556
600
  assert(String(t2User?.text || '').includes(t2ProbePrompt), 'T3 user file text mismatch');
601
+ assert(!!t2InProgress && typeof t2InProgress.id === 'string', 'T3 in-progress system message missing id');
557
602
  assert(!!t2AssistantMsg && typeof t2AssistantMsg.id === 'string', 'T3 assistant chat message missing id');
558
603
  assert(String(t2AssistantMsg?.text || '').includes(`Echo: ${t2ProbePrompt}`), 'T3 assistant echo file content mismatch');
559
- console.log('[T3] ok: probe lifecycle observed (processing/user any-order, assistant write, processing clear)');
604
+ console.log(`[${new Date().toISOString()}] [T3] ok: ordered probe lifecycle observed (user+processing, in-progress, assistant+processing clear)`);
605
+ }
560
606
 
561
- /*
562
607
  // ── T3a: non-probe chat protocol over API + SSE ──
563
- // Disabled in the public example — requires a configured Azure Foundry
564
- // endpoint and agent_id in server-config.json.
565
- console.log('\n=== T3a: non-probe chat protocol (expect paris) ===');
566
- const t2aBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
567
- assert(t2aBefore.status === 200, `T3a pre chats returned ${t2aBefore.status}`);
568
- const t2aBeforeMessages = Array.isArray(t2aBefore.data?.messages) ? t2aBefore.data.messages : [];
569
- const t2aBeforeCount = t2aBeforeMessages.length;
570
- const t2aPrompt = 'Just answer what is the capital of France. No Fluff. No COmmentary. No Markup Respond in lower case in one word.';
571
-
572
- const t2aSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
573
- actionType: 'chat-send',
574
- payload: {
575
- text: JSON.stringify({
576
- prompt: t2aPrompt,
577
- chatTimeoutMs: 180000,
578
- }),
579
- },
580
- });
581
- assert(t2aSendRes.status === 200, `T3a chat-send returned ${t2aSendRes.status}`);
582
-
583
- const t2aAssistant = await waitForChatPredicate((events) => {
584
- for (let i = events.length - 1; i >= 0; i -= 1) {
585
- const e = events[i];
586
- if (e.messageCount < t2aBeforeCount + 2) continue;
587
- const last = e.messages[e.messages.length - 1];
588
- if (last?.role === 'assistant' && /paris/i.test(String(last.text || ''))) return e;
589
- }
590
- return false;
591
- }, 240_000, 'T3a assistant response with paris');
592
- assert(!!t2aAssistant, 'T3a assistant response with paris not observed on SSE');
593
-
594
- const t2aAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
595
- assert(t2aAfter.status === 200, `T3a post chats returned ${t2aAfter.status}`);
596
- const t2aAfterMessages = Array.isArray(t2aAfter.data?.messages) ? t2aAfter.data.messages : [];
597
- const t2aNewMessages = t2aAfterMessages.slice(t2aBeforeCount);
598
- assert(t2aNewMessages.length >= 2, `T3a expected at least 2 new chat messages, got ${t2aNewMessages.length}`);
599
- const t2aAssistantMsg = [...t2aNewMessages].reverse().find((m) => m?.role === 'assistant');
600
- assert(!!t2aAssistantMsg && typeof t2aAssistantMsg.id === 'string', 'T3a assistant chat message missing id');
601
- assert(/paris/i.test(String(t2aAssistantMsg?.text || '')), 'T3a assistant file content missing paris');
602
- console.log('[T3a] ok: non-probe response contains paris');
603
- */
608
+ // Disabled in the public example unless explicitly requested — requires a
609
+ // configured Azure Foundry endpoint and agent_id in server-config.json.
610
+ if (skipT3a) {
611
+ console.log('\n=== T3a: skipped (--skip-t3a) ===');
612
+ } else {
613
+ console.log('\n=== T3a: non-probe chat protocol (expect paris) ===');
614
+ const t2aBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
615
+ assert(t2aBefore.status === 200, `T3a pre chats returned ${t2aBefore.status}`);
616
+ const t2aBeforeMessages = Array.isArray(t2aBefore.data?.messages) ? t2aBefore.data.messages : [];
617
+ const t2aBeforeCount = t2aBeforeMessages.length;
618
+ const t2aPrompt = 'Just answer what is the capital of France. No Fluff. No COmmentary. No Markup Respond in lower case in one word.';
619
+
620
+ const t2aSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
621
+ actionType: 'chat-send',
622
+ payload: {
623
+ text: JSON.stringify({
624
+ prompt: t2aPrompt,
625
+ chatTimeoutMs: 180000,
626
+ }),
627
+ },
628
+ });
629
+ assert(t2aSendRes.status === 200, `T3a chat-send returned ${t2aSendRes.status}`);
630
+
631
+ const t2aAssistant = await waitForChatPredicate((events) => {
632
+ for (let i = events.length - 1; i >= 0; i -= 1) {
633
+ const e = events[i];
634
+ if (e.messageCount < t2aBeforeCount + 2) continue;
635
+ const last = e.messages[e.messages.length - 1];
636
+ if (last?.role === 'assistant' && /paris/i.test(String(last.text || ''))) return e;
637
+ }
638
+ return false;
639
+ }, 240_000, 'T3a assistant response with paris');
640
+ assert(!!t2aAssistant, 'T3a assistant response with paris not observed on SSE');
641
+
642
+ const t2aAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
643
+ assert(t2aAfter.status === 200, `T3a post chats returned ${t2aAfter.status}`);
644
+ const t2aAfterMessages = Array.isArray(t2aAfter.data?.messages) ? t2aAfter.data.messages : [];
645
+ const t2aNewMessages = t2aAfterMessages.slice(t2aBeforeCount);
646
+ assert(t2aNewMessages.length >= 2, `T3a expected at least 2 new chat messages, got ${t2aNewMessages.length}`);
647
+ const t2aAssistantMsg = [...t2aNewMessages].reverse().find((m) => m?.role === 'assistant');
648
+ assert(!!t2aAssistantMsg && typeof t2aAssistantMsg.id === 'string', 'T3a assistant chat message missing id');
649
+ assert(/paris/i.test(String(t2aAssistantMsg?.text || '')), 'T3a assistant file content missing paris');
650
+ console.log('[T3a] ok: non-probe response contains paris');
651
+ }
604
652
 
605
653
  // ── T3b: probe-echo chat + file upload protocol over API + SSE ──
606
- console.log('\n=== T3b: probe-echo chat with file upload protocol ===');
607
- const t2bBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
608
- assert(t2bBefore.status === 200, `T3b pre chats returned ${t2bBefore.status}`);
609
- const t2bBeforeMessages = Array.isArray(t2bBefore.data?.messages) ? t2bBefore.data.messages : [];
610
- const t2bBeforeCount = t2bBeforeMessages.length;
611
-
612
- const t2bUploadRes = await httpUploadChatFile(
613
- `${BASE}/cards/${CHAT_CARD_ID}/files?inChat=true`,
614
- 'q1.txt',
615
- 'what is the capital of japan',
616
- );
617
- assert(t2bUploadRes.status === 200, `T3b file upload returned ${t2bUploadRes.status}`);
618
- const uploadedFile = t2bUploadRes.data?.file;
619
- assert(uploadedFile && typeof uploadedFile === 'object', 'T3b upload response missing file metadata');
620
-
621
- const t2bAfterUpload = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
622
- assert(t2bAfterUpload.status === 200, `T3b chats after upload returned ${t2bAfterUpload.status}`);
623
- const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.messages) ? t2bAfterUpload.data.messages : [];
624
- const t2bUploadNewMessages = t2bUploadMessages.slice(t2bBeforeCount);
625
- const t2bUploadSystem = t2bUploadNewMessages.find((m) => m?.role === 'system');
626
- assert(!!t2bUploadSystem, 'T3b upload protocol missing system chat file');
627
- assert(String(t2bUploadSystem?.text || '').toLowerCase().includes('file uploaded:'), 'T3b upload system message does not describe uploaded file');
628
-
629
- const t2bSendBaseline = t2bUploadMessages.length;
630
-
631
- const t2bPrompt = `probe echo file-upload validation ${Date.now()}`;
632
- const t2bSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
633
- actionType: 'chat-send',
634
- payload: {
635
- text: JSON.stringify({
636
- prompt: t2bPrompt,
637
- probe: true,
638
- chatTimeMs: 2200,
639
- }),
640
- files: [uploadedFile],
641
- },
642
- });
643
- assert(t2bSendRes.status === 200, `T3b chat-send returned ${t2bSendRes.status}`);
654
+ if (skipT3b) {
655
+ console.log('\n=== T3b: skipped (--skip-t3b) ===');
656
+ } else {
657
+ console.log('\n=== T3b: probe-echo chat with file upload protocol ===');
658
+ const t2bBefore = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
659
+ assert(t2bBefore.status === 200, `T3b pre chats returned ${t2bBefore.status}`);
660
+ const t2bBeforeMessages = Array.isArray(t2bBefore.data?.messages) ? t2bBefore.data.messages : [];
661
+ const t2bBeforeCount = t2bBeforeMessages.length;
662
+
663
+ const t2bUploadRes = await httpUploadChatFile(
664
+ `${BASE}/cards/${CHAT_CARD_ID}/files?inChat=true`,
665
+ 'q1.txt',
666
+ 'what is the capital of japan',
667
+ );
668
+ assert(t2bUploadRes.status === 200, `T3b file upload returned ${t2bUploadRes.status}`);
669
+ const uploadedFile = t2bUploadRes.data?.file;
670
+ assert(uploadedFile && typeof uploadedFile === 'object', 'T3b upload response missing file metadata');
671
+
672
+ const t2bAfterUpload = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
673
+ assert(t2bAfterUpload.status === 200, `T3b chats after upload returned ${t2bAfterUpload.status}`);
674
+ const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.messages) ? t2bAfterUpload.data.messages : [];
675
+ const t2bUploadNewMessages = t2bUploadMessages.slice(t2bBeforeCount);
676
+ const t2bUploadSystem = t2bUploadNewMessages.find((m) => m?.role === 'system');
677
+ assert(!!t2bUploadSystem, 'T3b upload protocol missing system chat file');
678
+ assert(String(t2bUploadSystem?.text || '').toLowerCase().includes('file uploaded:'), 'T3b upload system message does not describe uploaded file');
679
+
680
+ const t2bSendBaseline = t2bUploadMessages.length;
681
+ const t2bEventStart = NS.chatEvents.length;
682
+
683
+ const t2bPrompt = `probe echo file-upload validation ${Date.now()}`;
684
+ const t2bSendRes = await httpJson('POST', `${BASE}/cards/${CHAT_CARD_ID}/actions`, {
685
+ actionType: 'chat-send',
686
+ payload: {
687
+ text: `${ECHO_PROBE_MARKER}${t2bPrompt}${ECHO_PROBE_MARKER}`,
688
+ files: [uploadedFile],
689
+ },
690
+ });
691
+ assert(t2bSendRes.status === 200, `T3b chat-send returned ${t2bSendRes.status}`);
644
692
 
645
- const t2bUserOrProcessing = await waitForChatPredicate((events) => {
646
- for (let i = events.length - 1; i >= 0; i -= 1) {
647
- const e = events[i];
648
- if (e.messageCount >= t2bSendBaseline + 1 || e.processing === true) return e;
649
- }
650
- return false;
651
- }, 45_000, 'T3b user/proc signal');
652
- assert(!!t2bUserOrProcessing, 'T3b user/proc signal not observed');
653
-
654
- const t2bAssistantOnSse = await waitForChatPredicate((events) => {
655
- for (let i = events.length - 1; i >= 0; i -= 1) {
656
- const e = events[i];
657
- if (e.messageCount < t2bSendBaseline + 2) continue;
658
- const last = e.messages[e.messages.length - 1];
659
- if (last?.role === 'assistant' && String(last.text || '').includes(`Echo: ${t2bPrompt}`)) return e;
660
- }
661
- return false;
662
- }, 60_000, 'T3b assistant response');
663
- assert(!!t2bAssistantOnSse, 'T3b assistant response not observed on SSE');
664
-
665
- const t2bAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
666
- assert(t2bAfter.status === 200, `T3b post chats returned ${t2bAfter.status}`);
667
- const t2bAfterMessages = Array.isArray(t2bAfter.data?.messages) ? t2bAfter.data.messages : [];
668
- const t2bNewMessages = t2bAfterMessages.slice(t2bSendBaseline);
669
- assert(t2bNewMessages.length >= 2, `T3b expected at least 2 chat messages after send, got ${t2bNewMessages.length}`);
670
-
671
- const t2bUser = t2bNewMessages.find((m) => m?.role === 'user');
672
- const t2bAssistantMsg = t2bNewMessages.find((m) => m?.role === 'assistant');
673
-
674
- assert(!!t2bUser && typeof t2bUser.id === 'string', 'T3b missing user chat message notification');
675
- assert(!!t2bAssistantMsg && typeof t2bAssistantMsg.id === 'string', 'T3b missing assistant chat message notification');
676
- assert(Array.isArray(t2bUser?.files) && t2bUser.files.length === 1, 'T3b user chat message missing uploaded file metadata');
677
- assert(String(t2bAssistantMsg?.text || '').includes(`Echo: ${t2bPrompt}`), 'T3b assistant file content mismatch');
678
-
679
- const t2bProcessingCleared = await waitForChatPredicate((events) => {
680
- for (let i = events.length - 1; i >= 0; i -= 1) {
681
- const e = events[i];
682
- if (e.messageCount >= t2bSendBaseline + 2 && e.processing === false) return e;
683
- }
684
- return false;
685
- }, 30_000, 'T3b processing clear');
686
- assert(!!t2bProcessingCleared, 'T3b processing clear not observed');
687
- console.log('[T3b] ok: upload protocol (system/user) and chat protocol (.processing/user/assistant/.processing clear) observed');
693
+ const t2bLifecycle = await waitForChatPredicate((events) => {
694
+ return matchOrderedProbeLifecycle(events.slice(t2bEventStart), {
695
+ beforeCount: t2bSendBaseline,
696
+ beforeProcessing: false,
697
+ prompt: t2bPrompt,
698
+ inProgressText: PROBE_IN_PROGRESS_TEXT,
699
+ });
700
+ }, 60_000, 'T3b ordered lifecycle');
701
+ assert(!!t2bLifecycle, 'T3b ordered lifecycle not observed');
702
+
703
+ const t2bAfter = await httpGet(`${BASE}/cards/${CHAT_CARD_ID}/chats`);
704
+ assert(t2bAfter.status === 200, `T3b post chats returned ${t2bAfter.status}`);
705
+ const t2bAfterMessages = Array.isArray(t2bAfter.data?.messages) ? t2bAfter.data.messages : [];
706
+ const t2bNewMessages = t2bAfterMessages.slice(t2bSendBaseline);
707
+ assert(t2bNewMessages.length >= 3, `T3b expected at least 3 chat messages after send, got ${t2bNewMessages.length}`);
708
+
709
+ const t2bUser = t2bNewMessages.find((m) => m?.role === 'user');
710
+ const t2bInProgress = t2bNewMessages.find((m) => m?.role === 'system' && String(m?.text || '').trim().toLowerCase() === PROBE_IN_PROGRESS_TEXT);
711
+ const t2bAssistantMsg = t2bNewMessages.find((m) => m?.role === 'assistant');
712
+
713
+ assert(!!t2bUser && typeof t2bUser.id === 'string', 'T3b missing user chat message notification');
714
+ assert(!!t2bInProgress && typeof t2bInProgress.id === 'string', 'T3b missing in-progress system chat message');
715
+ assert(!!t2bAssistantMsg && typeof t2bAssistantMsg.id === 'string', 'T3b missing assistant chat message notification');
716
+ assert(Array.isArray(t2bUser?.files) && t2bUser.files.length === 1, 'T3b user chat message missing uploaded file metadata');
717
+ assert(String(t2bAssistantMsg?.text || '').includes(`Echo: ${t2bPrompt}`), 'T3b assistant file content mismatch');
718
+ console.log('[T3b] ok: upload protocol and ordered probe lifecycle observed (user+processing, in-progress, assistant+processing clear)');
719
+ }
688
720
  }
689
721
 
690
722
  console.log('\n=== All smoke checks passed ===\n');
@@ -6,13 +6,13 @@
6
6
  <title>Example Board Demo (LocalStorage Runtime)</title>
7
7
  <link rel="icon" type="image/svg+xml" href="../../browser/favicon.svg" />
8
8
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
9
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.2.5/browser/compute-jsonata.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.3.0/browser/compute-jsonata.js"></script>
10
10
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
11
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
12
12
  <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
13
13
  <script src="https://cdn.jsdelivr.net/npm/leader-line/leader-line.min.js"></script>
14
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.2.5/browser/live-cards.js"></script>
15
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.2.5/browser/board-livecards-localstorage.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.3.0/browser/live-cards.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@8.3.0/browser/board-livecards-localstorage.js"></script>
16
16
  </head>
17
17
  <body class="bg-light">
18
18
  <div class="container-fluid py-3">
@@ -1,4 +1,4 @@
1
- import { C as CommandInput, a as CommandResult } from './board-live-cards-public-BLXbcBNk.cjs';
1
+ import { C as CommandInput, a as CommandResult } from './board-live-cards-public-BwZYGAsF.cjs';
2
2
  import { B as BlobStorage } from './storage-interface-B6ecOulj.cjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { C as CommandInput, a as CommandResult } from './board-live-cards-public-BZaNb2mi.js';
1
+ import { C as CommandInput, a as CommandResult } from './board-live-cards-public-DWpZVDXN.js';
2
2
  import { B as BlobStorage } from './storage-interface-B6ecOulj.js';
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
- import './board-live-cards-public-BLXbcBNk.cjs';
2
- export { A as ArtifactsStorePublic, a as createArtifactsStorePublic } from './artifacts-store-lib-public-C5UL5tyG.cjs';
1
+ import './board-live-cards-public-BwZYGAsF.cjs';
2
+ export { A as ArtifactsStorePublic, a as createArtifactsStorePublic } from './artifacts-store-lib-public-Cz8-kdXG.cjs';
3
3
  import './storage-interface-B6ecOulj.cjs';
4
4
  import './execution-refs.cjs';
5
5
  import './types-BBhqYGhE.cjs';
@@ -1,5 +1,5 @@
1
- import './board-live-cards-public-BZaNb2mi.js';
2
- export { A as ArtifactsStorePublic, a as createArtifactsStorePublic } from './artifacts-store-lib-public-GD4H-fFp.js';
1
+ import './board-live-cards-public-DWpZVDXN.js';
2
+ export { A as ArtifactsStorePublic, a as createArtifactsStorePublic } from './artifacts-store-lib-public-ksGIExhc.js';
3
3
  import './storage-interface-B6ecOulj.js';
4
4
  import './execution-refs.js';
5
5
  import './types-BBhqYGhE.js';