yaml-flow 8.6.4 → 8.7.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/browser/adapters/firebase-storage.js +3 -0
- package/browser/adapters/firestore-storage.js +3 -0
- package/browser/adapters/localstorage-storage.js +4 -0
- package/browser/asset-integrity.json +22 -6
- package/browser/live-cards.schema.json +10 -1
- package/browser/server-runtime-controlface.js +8 -0
- package/examples/ARCHITECTURE.md +5 -32
- package/examples/board/demo-shell-with-server.html +2 -2
- package/examples/board/doc.html +2 -2
- package/examples/board/server/board-server.js +4 -2
- package/examples/board/test/server-http-test.js +73 -79
- package/examples/board-firestore/README.md +81 -0
- package/examples/board-firestore/browser/board-runtime.js +263 -0
- package/examples/board-firestore/firestore.indexes.json +29 -0
- package/examples/board-firestore/package.json +14 -0
- package/examples/board-firestore/server/adapters/firestore-archive-factory.js +59 -0
- package/examples/board-firestore/server/adapters/firestore-blob-storage.js +82 -0
- package/examples/board-firestore/server/adapters/firestore-board-adapter.js +127 -0
- package/examples/board-firestore/server/adapters/firestore-journal-storage.js +54 -0
- package/examples/board-firestore/server/adapters/firestore-kv-storage.js +47 -0
- package/examples/board-firestore/server/adapters/firestore-lock.js +62 -0
- package/examples/board-firestore/server/adapters/firestore-queue-storage.js +186 -0
- package/examples/board-firestore/server/adapters/firestore-scratch-storage.js +50 -0
- package/examples/board-firestore/server/worker.js +146 -0
- package/lib/{artifacts-store-lib-BR-Samty.d.cts → artifacts-store-lib-D9nMkVcE.d.cts} +1 -1
- package/lib/{artifacts-store-lib-DT7XlWUL.d.ts → artifacts-store-lib-DSSMqVL2.d.ts} +1 -1
- package/lib/artifacts-store-public.d.cts +2 -2
- package/lib/artifacts-store-public.d.ts +2 -2
- package/lib/board-live-cards-mcp.cjs +1 -1
- package/lib/board-live-cards-mcp.d.cts +51 -3
- package/lib/board-live-cards-mcp.d.ts +51 -3
- package/lib/board-live-cards-mcp.js +1 -1
- package/lib/board-live-cards-node.cjs +5 -5
- package/lib/board-live-cards-node.d.cts +16 -11
- package/lib/board-live-cards-node.d.ts +16 -11
- package/lib/board-live-cards-node.js +5 -5
- package/lib/{board-live-cards-public-BMUIPOrc.d.ts → board-live-cards-public-JNRKfBZy.d.ts} +1 -1
- package/lib/{board-live-cards-public-wkNmBIRC.d.cts → board-live-cards-public-LlVUQPL2.d.cts} +1 -1
- package/lib/board-live-cards-public-async-Di9QB141.d.cts +55 -0
- package/lib/board-live-cards-public-async-fwd1QI82.d.ts +55 -0
- package/lib/board-live-cards-public.cjs +1 -1
- package/lib/board-live-cards-public.d.cts +1 -1
- package/lib/board-live-cards-public.d.ts +1 -1
- package/lib/board-live-cards-public.js +1 -1
- package/lib/board-live-cards-server-runtime.cjs +1 -1
- package/lib/board-live-cards-server-runtime.d.cts +10 -6
- package/lib/board-live-cards-server-runtime.d.ts +10 -6
- package/lib/board-live-cards-server-runtime.js +1 -1
- package/lib/board-livegraph-runtime/index.cjs +1 -1
- package/lib/board-livegraph-runtime/index.js +1 -1
- package/lib/board-platform-adapter-async-BfHmHdx2.d.cts +129 -0
- package/lib/board-platform-adapter-async-DYahVzIK.d.ts +129 -0
- package/lib/board-worker-adapter.cjs +3 -3
- package/lib/board-worker-adapter.js +3 -3
- package/lib/card-compute/index.cjs +1 -1
- package/lib/card-compute/index.js +1 -1
- package/lib/card-store-public.d.cts +1 -1
- package/lib/card-store-public.d.ts +1 -1
- package/lib/card-validation.cjs +1 -1
- package/lib/card-validation.js +1 -1
- package/lib/{chat-storage-lib-BIUbE-fM.d.cts → chat-storage-lib-B9Q34Dyv.d.cts} +1 -1
- package/lib/{chat-storage-lib-BlG-sobS.d.ts → chat-storage-lib-DB9iSai2.d.ts} +1 -1
- package/lib/chat-store-public.d.cts +2 -2
- package/lib/chat-store-public.d.ts +2 -2
- package/lib/chunk-272IYUKT.cjs +2 -0
- package/lib/chunk-3KC6LBOG.js +3 -0
- package/lib/chunk-5XHOHTLZ.cjs +2 -0
- package/lib/chunk-6APH25VI.js +2 -0
- package/lib/chunk-76C7N4YT.js +3 -0
- package/lib/chunk-7FGPOGRV.cjs +2 -0
- package/lib/chunk-7ICPAABP.cjs +7 -0
- package/lib/chunk-ASR44K7H.cjs +3 -0
- package/lib/chunk-CPAXTVBQ.cjs +2 -0
- package/lib/chunk-EGRHWZRV.js +2 -0
- package/lib/chunk-EZENHAVZ.cjs +2 -0
- package/lib/chunk-FO4KNVU7.cjs +2 -0
- package/lib/chunk-GL2OHR2E.cjs +2 -0
- package/lib/chunk-HWYMZK3N.cjs +3 -0
- package/lib/chunk-IPLSRN6P.cjs +4 -0
- package/lib/{chunk-H5HBXPOI.cjs → chunk-J6EGN6S4.cjs} +3 -3
- package/lib/chunk-JH37NJGP.js +3 -0
- package/lib/chunk-JJL5VOQZ.cjs +3 -0
- package/lib/chunk-KAWQPLIE.cjs +2 -0
- package/lib/chunk-LPXVVMQT.cjs +2 -0
- package/lib/chunk-NJJ7WEDT.cjs +2 -0
- package/lib/chunk-NKIQRCOM.cjs +2 -0
- package/lib/chunk-NM6O35RY.cjs +2 -0
- package/lib/chunk-NTICU4OK.js +2 -0
- package/lib/chunk-O7NOHKVR.js +2 -0
- package/lib/chunk-PBOQ4HYB.cjs +2 -0
- package/lib/{chunk-VMW4Z6EF.js → chunk-PRKRXAVN.js} +3 -3
- package/lib/chunk-QJVR3FWQ.js +2 -0
- package/lib/chunk-S44QZUDX.js +2 -0
- package/lib/chunk-SGV7PU4H.js +2 -0
- package/lib/chunk-TSN3RTXT.js +4 -0
- package/lib/chunk-VXJHBWK3.js +2 -0
- package/lib/chunk-WHDEBJLT.js +7 -0
- package/lib/chunk-XYN5D3GL.js +2 -0
- package/lib/chunk-YBYXCFAI.js +2 -0
- package/lib/chunk-YGALANRO.js +2 -0
- package/lib/chunk-ZCNN6XPV.js +2 -0
- package/lib/chunk-ZJ5M5COT.js +2 -0
- package/lib/cloud-storage.cjs +1 -1
- package/lib/cloud-storage.d.cts +5 -3
- package/lib/cloud-storage.d.ts +5 -3
- package/lib/cloud-storage.js +1 -1
- package/lib/continuous-event-graph/index.cjs +1 -1
- package/lib/continuous-event-graph/index.js +1 -1
- package/lib/firebase-storage/index.cjs +3 -0
- package/lib/firebase-storage/index.d.cts +57 -0
- package/lib/firebase-storage/index.d.ts +57 -0
- package/lib/firebase-storage/index.js +3 -0
- package/lib/firestore-storage/index.cjs +3 -0
- package/lib/firestore-storage/index.d.cts +111 -0
- package/lib/firestore-storage/index.d.ts +111 -0
- package/lib/firestore-storage/index.js +3 -0
- package/lib/index.cjs +2 -2
- package/lib/index.js +1 -1
- package/lib/localstorage-storage/index.cjs +2 -0
- package/lib/localstorage-storage/index.d.cts +39 -0
- package/lib/localstorage-storage/index.d.ts +39 -0
- package/lib/localstorage-storage/index.js +2 -0
- package/lib/mcp-tool-registries-BBObLYga.d.ts +41 -0
- package/lib/mcp-tool-registries-W3TRj6O5.d.cts +41 -0
- package/lib/queue-lane-registry-PaZuFpwp.d.cts +30 -0
- package/lib/queue-lane-registry-PaZuFpwp.d.ts +30 -0
- package/lib/server-jobs-queue-runner/index.cjs +2 -0
- package/lib/server-jobs-queue-runner/index.d.cts +22 -0
- package/lib/server-jobs-queue-runner/index.d.ts +22 -0
- package/lib/server-jobs-queue-runner/index.js +2 -0
- package/lib/server-runtime/index.cjs +1 -1
- package/lib/server-runtime/index.d.cts +11 -17
- package/lib/server-runtime/index.d.ts +11 -17
- package/lib/server-runtime/index.js +1 -1
- package/lib/server-runtime-agentface/index.cjs +2 -0
- package/lib/server-runtime-agentface/index.d.cts +53 -0
- package/lib/server-runtime-agentface/index.d.ts +53 -0
- package/lib/server-runtime-agentface/index.js +2 -0
- package/lib/server-runtime-controlface/index.cjs +2 -0
- package/lib/server-runtime-controlface/index.d.cts +29 -0
- package/lib/server-runtime-controlface/index.d.ts +29 -0
- package/lib/server-runtime-controlface/index.js +2 -0
- package/lib/server-runtime-core/index.cjs +2 -0
- package/lib/server-runtime-core/index.d.cts +378 -0
- package/lib/server-runtime-core/index.d.ts +378 -0
- package/lib/server-runtime-core/index.js +2 -0
- package/lib/server-runtime-watchers/index.cjs +2 -0
- package/lib/server-runtime-watchers/index.d.cts +127 -0
- package/lib/server-runtime-watchers/index.d.ts +127 -0
- package/lib/server-runtime-watchers/index.js +2 -0
- package/lib/server-runtime-webhooks/index.cjs +2 -0
- package/lib/server-runtime-webhooks/index.d.cts +34 -0
- package/lib/server-runtime-webhooks/index.d.ts +34 -0
- package/lib/server-runtime-webhooks/index.js +2 -0
- package/lib/storage-async-interface-BRR4eBjx.d.cts +81 -0
- package/lib/storage-async-interface-DhlOVPSp.d.ts +81 -0
- package/lib/{queue-lane-registry-BPKWWgd4.d.cts → types-Ba8H5_Wo.d.cts} +10 -34
- package/lib/{queue-lane-registry-Be6c0ftj.d.ts → types-SO5OZm4s.d.ts} +10 -34
- package/package.json +46 -2
- package/schema/live-cards.schema.json +10 -1
- package/examples/board-local/demo-shell-localstorage.html +0 -843
- package/lib/board-live-cards-public-async-DKZqbJVU.d.ts +0 -256
- package/lib/board-live-cards-public-async-dMWNbWq6.d.cts +0 -256
- package/lib/chunk-KXWT3CY6.cjs +0 -8
- package/lib/chunk-MLVTJASJ.js +0 -2
- package/lib/chunk-N6P2JW4W.js +0 -3
- package/lib/chunk-NMZ6XNLB.cjs +0 -3
- package/lib/chunk-OEFTOO47.cjs +0 -3
- package/lib/chunk-OJLA6NLU.js +0 -8
- package/lib/chunk-R5L5WUKN.js +0 -2
- package/lib/chunk-VLBB3D6B.js +0 -3
- package/lib/chunk-WOALA3V5.cjs +0 -2
- package/lib/chunk-YEB5QHGE.cjs +0 -2
|
@@ -544,22 +544,13 @@ try {
|
|
|
544
544
|
console.log(`[T0.3] completed: ${JSON.stringify(t0Summary)}`);
|
|
545
545
|
|
|
546
546
|
console.log('\n=== T0 Step 4: board-status cross-check ===');
|
|
547
|
-
const statusRes = await httpGet(`${BASE}/board-status`);
|
|
548
|
-
assert(statusRes.status === 200, `board-status returned ${statusRes.status}`);
|
|
549
|
-
const httpSummary = statusRes.data?.statusSnapshot?.summary;
|
|
550
|
-
assert(httpSummary, 'statusSnapshot.summary missing from board-status');
|
|
551
547
|
const statusMcpRes = await httpMcp('inspect.board-runtime-status', {});
|
|
552
548
|
assert(statusMcpRes.status === 200, `inspect.board-runtime-status returned ${statusMcpRes.status}`);
|
|
553
549
|
assert(statusMcpRes.data?.status === 'success', `inspect.board-runtime-status failed: ${JSON.stringify(statusMcpRes.data)}`);
|
|
554
550
|
const mcpSummary = statusMcpRes.data?.data?.summary;
|
|
555
551
|
assert(mcpSummary, 'summary missing from inspect.board-runtime-status');
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const mcpComparableSummary = Object.fromEntries(comparableStatusKeys.map((key) => [key, mcpSummary[key]]));
|
|
559
|
-
assert(JSON.stringify(httpComparableSummary) === JSON.stringify(mcpComparableSummary),
|
|
560
|
-
`HTTP board-status summary mismatch vs MCP summary: http=${JSON.stringify(httpComparableSummary)} mcp=${JSON.stringify(mcpComparableSummary)}`);
|
|
561
|
-
assert(httpSummary.completed === httpSummary.card_count, `not all complete: ${JSON.stringify(httpSummary)}`);
|
|
562
|
-
console.log(`[T0.4] board-status: ${JSON.stringify(httpSummary)}`);
|
|
552
|
+
assert(mcpSummary.completed === mcpSummary.card_count, `not all complete: ${JSON.stringify(mcpSummary)}`);
|
|
553
|
+
console.log(`[T0.4] board-status: ${JSON.stringify(mcpSummary)}`);
|
|
563
554
|
|
|
564
555
|
// Verify computed_values arrived for portfolio-value card
|
|
565
556
|
const t0Positions = NS.computedValues['card-portfolio-value']?.positions;
|
|
@@ -634,30 +625,32 @@ try {
|
|
|
634
625
|
if (skipT2) {
|
|
635
626
|
console.log('\n=== T2: skipped (--skip-t2) ===');
|
|
636
627
|
} else {
|
|
637
|
-
console.log('\n=== T2:
|
|
638
|
-
const t2CardBefore = await
|
|
628
|
+
console.log('\n=== T2: MCP file upload -> card_data.files -> download ===');
|
|
629
|
+
const t2CardBefore = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
|
|
639
630
|
assert(t2CardBefore.status === 200, `T2 pre card read returned ${t2CardBefore.status}`);
|
|
640
|
-
const t2FilesBefore = Array.isArray(t2CardBefore.data?.card_data?.files)
|
|
641
|
-
? t2CardBefore.data.card_data.files
|
|
631
|
+
const t2FilesBefore = Array.isArray(t2CardBefore.data?.data?.[0]?.card_data?.files)
|
|
632
|
+
? t2CardBefore.data.data[0].card_data.files
|
|
642
633
|
: [];
|
|
643
634
|
const t2BeforeCount = t2FilesBefore.length;
|
|
644
635
|
|
|
645
636
|
const t2UploadText = `plain-file-upload-${Date.now()}`;
|
|
646
637
|
const t2UploadName = 't2-upload.txt';
|
|
647
|
-
const t2UploadRes = await
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
638
|
+
const t2UploadRes = await httpMcpControlplane('manage.upload-card-file', {
|
|
639
|
+
board_id: BOARD_ID,
|
|
640
|
+
card_id: T2_FILE_CARD_ID,
|
|
641
|
+
file_name: t2UploadName,
|
|
642
|
+
content_type: 'text/plain; charset=utf-8',
|
|
643
|
+
base64: Buffer.from(t2UploadText, 'utf-8').toString('base64'),
|
|
644
|
+
});
|
|
652
645
|
assert(t2UploadRes.status === 200, `T2 file upload returned ${t2UploadRes.status}`);
|
|
653
|
-
const t2UploadedFile = t2UploadRes.data?.file;
|
|
646
|
+
const t2UploadedFile = t2UploadRes.data?.data?.file;
|
|
654
647
|
assert(t2UploadedFile && typeof t2UploadedFile === 'object', 'T2 upload response missing file metadata');
|
|
655
648
|
assert(String(t2UploadedFile?.name || '') === t2UploadName, 'T2 uploaded file name mismatch');
|
|
656
649
|
|
|
657
|
-
const t2CardAfter = await
|
|
650
|
+
const t2CardAfter = await httpMcp('manage.read-card', { card_id: T2_FILE_CARD_ID });
|
|
658
651
|
assert(t2CardAfter.status === 200, `T2 post card read returned ${t2CardAfter.status}`);
|
|
659
|
-
const t2FilesAfter = Array.isArray(t2CardAfter.data?.card_data?.files)
|
|
660
|
-
? t2CardAfter.data.card_data.files
|
|
652
|
+
const t2FilesAfter = Array.isArray(t2CardAfter.data?.data?.[0]?.card_data?.files)
|
|
653
|
+
? t2CardAfter.data.data[0].card_data.files
|
|
661
654
|
: [];
|
|
662
655
|
assert(t2FilesAfter.length === t2BeforeCount + 1, `T2 expected files +1 (before=${t2BeforeCount}, after=${t2FilesAfter.length})`);
|
|
663
656
|
|
|
@@ -736,10 +729,10 @@ try {
|
|
|
736
729
|
assert(subRes.status === 200, `chat subscribe returned ${subRes.status}`);
|
|
737
730
|
|
|
738
731
|
t3Dbg('step 3: fetching pre-chat transcript');
|
|
739
|
-
const t2Before = await
|
|
732
|
+
const t2Before = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
740
733
|
t3Dbg(`step 3: pre-chat fetch returned status=${t2Before.status}`);
|
|
741
734
|
assert(t2Before.status === 200, `T3 pre chats returned ${t2Before.status}`);
|
|
742
|
-
const t2BeforeMessages = Array.isArray(t2Before.data?.messages) ? t2Before.data.messages : [];
|
|
735
|
+
const t2BeforeMessages = Array.isArray(t2Before.data?.data?.messages) ? t2Before.data.data.messages : [];
|
|
743
736
|
const t2BeforeCount = t2BeforeMessages.length;
|
|
744
737
|
const t2EventStart = NS.chatEvents.length;
|
|
745
738
|
const t2ProbePrompt = `Probe protocol validation ${Date.now()}`;
|
|
@@ -770,10 +763,10 @@ try {
|
|
|
770
763
|
assert(!!t2Lifecycle, 'T3 ordered lifecycle not observed');
|
|
771
764
|
|
|
772
765
|
t3Dbg('step 6: fetching post-chat transcript');
|
|
773
|
-
const t2After = await
|
|
766
|
+
const t2After = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
774
767
|
t3Dbg(`step 6: post-chat fetch returned status=${t2After.status}`);
|
|
775
768
|
assert(t2After.status === 200, `T3 post chats returned ${t2After.status}`);
|
|
776
|
-
const t2AfterMessages = Array.isArray(t2After.data?.messages) ? t2After.data.messages : [];
|
|
769
|
+
const t2AfterMessages = Array.isArray(t2After.data?.data?.messages) ? t2After.data.data.messages : [];
|
|
777
770
|
const t2NewMessages = t2AfterMessages.slice(t2BeforeCount);
|
|
778
771
|
t3Dbg(`step 6: validating ${t2NewMessages.length} new messages`);
|
|
779
772
|
assert(t2NewMessages.length >= 3, `T3 expected at least 3 new chat messages, got ${t2NewMessages.length}`);
|
|
@@ -815,10 +808,10 @@ try {
|
|
|
815
808
|
console.log('\n=== T3a: non-probe chat protocol (expect paris) ===');
|
|
816
809
|
const t3aDbg = (msg) => console.log(`[T3a.DBG ${new Date().toISOString()}] ${msg}`);
|
|
817
810
|
t3aDbg('step 1: fetching pre-chat transcript');
|
|
818
|
-
const t2aBefore = await
|
|
811
|
+
const t2aBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
819
812
|
t3aDbg(`step 1: pre-chat fetch returned status=${t2aBefore.status}`);
|
|
820
813
|
assert(t2aBefore.status === 200, `T3a pre chats returned ${t2aBefore.status}`);
|
|
821
|
-
const t2aBeforeMessages = Array.isArray(t2aBefore.data?.messages) ? t2aBefore.data.messages : [];
|
|
814
|
+
const t2aBeforeMessages = Array.isArray(t2aBefore.data?.data?.messages) ? t2aBefore.data.data.messages : [];
|
|
822
815
|
const t2aBeforeCount = t2aBeforeMessages.length;
|
|
823
816
|
const t2aPrompt = 'Just answer what is the capital of France. No Fluff. No COmmentary. No Markup Respond in lower case in one word.';
|
|
824
817
|
t3aDbg(`step 1: beforeCount=${t2aBeforeCount}`);
|
|
@@ -854,10 +847,10 @@ try {
|
|
|
854
847
|
t3aDbg(`step 3: assistant SSE text=${JSON.stringify(String(t2aSseLast?.text || '').slice(0, 400))}`);
|
|
855
848
|
|
|
856
849
|
t3aDbg('step 4: fetching post-chat transcript');
|
|
857
|
-
const t2aAfter = await
|
|
850
|
+
const t2aAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
858
851
|
t3aDbg(`step 4: post-chat fetch returned status=${t2aAfter.status}`);
|
|
859
852
|
assert(t2aAfter.status === 200, `T3a post chats returned ${t2aAfter.status}`);
|
|
860
|
-
const t2aAfterMessages = Array.isArray(t2aAfter.data?.messages) ? t2aAfter.data.messages : [];
|
|
853
|
+
const t2aAfterMessages = Array.isArray(t2aAfter.data?.data?.messages) ? t2aAfter.data.data.messages : [];
|
|
861
854
|
const t2aNewMessages = t2aAfterMessages.slice(t2aBeforeCount);
|
|
862
855
|
t3aDbg(`step 4: validating ${t2aNewMessages.length} new messages`);
|
|
863
856
|
assert(t2aNewMessages.length >= 2, `T3a expected at least 2 new chat messages, got ${t2aNewMessages.length}`);
|
|
@@ -873,33 +866,36 @@ try {
|
|
|
873
866
|
console.log('\n=== T3b: skipped (--skip-t3b) ===');
|
|
874
867
|
} else {
|
|
875
868
|
console.log('\n=== T3b: probe-echo chat with file upload protocol ===');
|
|
876
|
-
const t2bBefore = await
|
|
869
|
+
const t2bBefore = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
877
870
|
assert(t2bBefore.status === 200, `T3b pre chats returned ${t2bBefore.status}`);
|
|
878
|
-
const t2bBeforeMessages = Array.isArray(t2bBefore.data?.messages) ? t2bBefore.data.messages : [];
|
|
871
|
+
const t2bBeforeMessages = Array.isArray(t2bBefore.data?.data?.messages) ? t2bBefore.data.data.messages : [];
|
|
879
872
|
const t2bBeforeCount = t2bBeforeMessages.length;
|
|
880
873
|
|
|
881
874
|
const t3bTurnId = randomTurnId();
|
|
882
|
-
const t2bUploadRes = await
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
875
|
+
const t2bUploadRes = await httpMcpControlplane('manage.add-chat-attachment', {
|
|
876
|
+
board_id: BOARD_ID,
|
|
877
|
+
card_id: CHAT_CARD_ID,
|
|
878
|
+
turn_id: t3bTurnId,
|
|
879
|
+
file_name: 'q1.txt',
|
|
880
|
+
content_type: 'text/plain; charset=utf-8',
|
|
881
|
+
base64: Buffer.from('tokyo', 'utf-8').toString('base64'),
|
|
882
|
+
});
|
|
887
883
|
assert(t2bUploadRes.status === 200, `T3b file upload returned ${t2bUploadRes.status}`);
|
|
888
|
-
const uploadedFile = t2bUploadRes.data?.
|
|
884
|
+
const uploadedFile = t2bUploadRes.data?.data?.files?.[0];
|
|
889
885
|
assert(uploadedFile && typeof uploadedFile === 'object', 'T3b upload response missing file metadata');
|
|
890
886
|
|
|
891
|
-
const t2bAfterUpload = await
|
|
887
|
+
const t2bAfterUpload = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
892
888
|
assert(t2bAfterUpload.status === 200, `T3b chats after upload returned ${t2bAfterUpload.status}`);
|
|
893
|
-
const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.messages) ? t2bAfterUpload.data.messages : [];
|
|
889
|
+
const t2bUploadMessages = Array.isArray(t2bAfterUpload.data?.data?.messages) ? t2bAfterUpload.data.data.messages : [];
|
|
894
890
|
const t2bUploadNewMessages = t2bUploadMessages.slice(t2bBeforeCount);
|
|
895
891
|
const t2bUploadSystem = t2bUploadNewMessages.find((m) => m?.role === 'system');
|
|
896
892
|
assert(!!t2bUploadSystem, 'T3b upload protocol missing system chat file');
|
|
897
893
|
assert(String(t2bUploadSystem?.text || '').toLowerCase().includes('file uploaded:'), 'T3b upload system message does not describe uploaded file');
|
|
898
894
|
|
|
899
|
-
const t2bCardAfterUpload = await
|
|
895
|
+
const t2bCardAfterUpload = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
|
|
900
896
|
assert(t2bCardAfterUpload.status === 200, `T3b card read after upload returned ${t2bCardAfterUpload.status}`);
|
|
901
|
-
const t2bFilesAfterUpload = Array.isArray(t2bCardAfterUpload.data?.card_data?.files)
|
|
902
|
-
? t2bCardAfterUpload.data.card_data.files
|
|
897
|
+
const t2bFilesAfterUpload = Array.isArray(t2bCardAfterUpload.data?.data?.[0]?.card_data?.files)
|
|
898
|
+
? t2bCardAfterUpload.data.data[0].card_data.files
|
|
903
899
|
: [];
|
|
904
900
|
const t2bFileIndex = t2bFilesAfterUpload.findIndex((f) => String(f?.stored_name || '') === String(uploadedFile?.stored_name || ''));
|
|
905
901
|
assert(t2bFileIndex >= 0, 'T3b uploaded file metadata not found in card_data.files');
|
|
@@ -935,9 +931,9 @@ try {
|
|
|
935
931
|
}, 60_000, 'T3b ordered lifecycle');
|
|
936
932
|
assert(!!t2bLifecycle, 'T3b ordered lifecycle not observed');
|
|
937
933
|
|
|
938
|
-
const t2bAfter = await
|
|
934
|
+
const t2bAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
939
935
|
assert(t2bAfter.status === 200, `T3b post chats returned ${t2bAfter.status}`);
|
|
940
|
-
const t2bAfterMessages = Array.isArray(t2bAfter.data?.messages) ? t2bAfter.data.messages : [];
|
|
936
|
+
const t2bAfterMessages = Array.isArray(t2bAfter.data?.data?.messages) ? t2bAfter.data.data.messages : [];
|
|
941
937
|
const t2bNewMessages = t2bAfterMessages.slice(t2bSendBaseline);
|
|
942
938
|
assert(t2bNewMessages.length >= 3, `T3b expected at least 3 chat messages after send, got ${t2bNewMessages.length}`);
|
|
943
939
|
|
|
@@ -972,15 +968,15 @@ try {
|
|
|
972
968
|
t3dOwnedSseClient = true;
|
|
973
969
|
}
|
|
974
970
|
|
|
975
|
-
const t2dBeforeChats = await
|
|
971
|
+
const t2dBeforeChats = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
976
972
|
assert(t2dBeforeChats.status === 200, `T3d pre chats returned ${t2dBeforeChats.status}`);
|
|
977
|
-
const t2dBeforeMessages = Array.isArray(t2dBeforeChats.data?.messages) ? t2dBeforeChats.data.messages : [];
|
|
973
|
+
const t2dBeforeMessages = Array.isArray(t2dBeforeChats.data?.data?.messages) ? t2dBeforeChats.data.data.messages : [];
|
|
978
974
|
const t2dBeforeCount = t2dBeforeMessages.length;
|
|
979
975
|
|
|
980
|
-
const t2dBeforeCard = await
|
|
976
|
+
const t2dBeforeCard = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
|
|
981
977
|
assert(t2dBeforeCard.status === 200, `T3d pre card returned ${t2dBeforeCard.status}`);
|
|
982
|
-
const t2dBeforeFiles = Array.isArray(t2dBeforeCard.data?.card_data?.files)
|
|
983
|
-
? t2dBeforeCard.data.card_data.files
|
|
978
|
+
const t2dBeforeFiles = Array.isArray(t2dBeforeCard.data?.data?.[0]?.card_data?.files)
|
|
979
|
+
? t2dBeforeCard.data.data[0].card_data.files
|
|
984
980
|
: [];
|
|
985
981
|
|
|
986
982
|
const t3dTurnId = randomTurnId();
|
|
@@ -1006,9 +1002,9 @@ try {
|
|
|
1006
1002
|
}, 60_000, 'T3d ordered lifecycle');
|
|
1007
1003
|
assert(!!t2dLifecycle, 'T3d ordered lifecycle not observed');
|
|
1008
1004
|
|
|
1009
|
-
const t2dAfter = await
|
|
1005
|
+
const t2dAfter = await httpMcp('inspect.chat-messages-on-cards', { card_id: CHAT_CARD_ID, all_turns: true });
|
|
1010
1006
|
assert(t2dAfter.status === 200, `T3d post chats returned ${t2dAfter.status}`);
|
|
1011
|
-
const t2dAfterMessages = Array.isArray(t2dAfter.data?.messages) ? t2dAfter.data.messages : [];
|
|
1007
|
+
const t2dAfterMessages = Array.isArray(t2dAfter.data?.data?.messages) ? t2dAfter.data.data.messages : [];
|
|
1012
1008
|
const t2dNewMessages = t2dAfterMessages.slice(t2dBeforeCount);
|
|
1013
1009
|
assert(t2dNewMessages.length >= 4, `T3d expected at least 4 chat messages after send, got ${t2dNewMessages.length}`);
|
|
1014
1010
|
|
|
@@ -1031,10 +1027,10 @@ try {
|
|
|
1031
1027
|
const t2dFileIndex = Number.parseInt(t2dFileIndexMatch[1], 10);
|
|
1032
1028
|
assert(Number.isInteger(t2dFileIndex) && t2dFileIndex >= 0, 'T3d AI-generated message file index should be non-negative');
|
|
1033
1029
|
|
|
1034
|
-
const t2dAfterCard = await
|
|
1030
|
+
const t2dAfterCard = await httpMcp('manage.read-card', { card_id: CHAT_CARD_ID });
|
|
1035
1031
|
assert(t2dAfterCard.status === 200, `T3d post card returned ${t2dAfterCard.status}`);
|
|
1036
|
-
const t2dAfterFiles = Array.isArray(t2dAfterCard.data?.card_data?.files)
|
|
1037
|
-
? t2dAfterCard.data.card_data.files
|
|
1032
|
+
const t2dAfterFiles = Array.isArray(t2dAfterCard.data?.data?.[0]?.card_data?.files)
|
|
1033
|
+
? t2dAfterCard.data.data[0].card_data.files
|
|
1038
1034
|
: [];
|
|
1039
1035
|
assert(t2dAfterFiles.length === t2dBeforeFiles.length + 1, `T3d expected exactly one new stored file, got ${t2dAfterFiles.length - t2dBeforeFiles.length}`);
|
|
1040
1036
|
const t2dStoredFile = t2dAfterFiles[t2dFileIndex];
|
|
@@ -1551,28 +1547,28 @@ try {
|
|
|
1551
1547
|
|
|
1552
1548
|
const t5ThreadId = `thread-t5-${Date.now()}`;
|
|
1553
1549
|
expectMcpSuccess(
|
|
1554
|
-
await httpMcpControlplane('setstate.card-
|
|
1555
|
-
'T5 setstate.card-
|
|
1550
|
+
await httpMcpControlplane('setstate.card-private', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id', value: t5ThreadId }),
|
|
1551
|
+
'T5 setstate.card-private',
|
|
1556
1552
|
);
|
|
1557
1553
|
const t5GetMeta = expectMcpSuccess(
|
|
1558
|
-
await httpMcpControlplane('getstate.card-
|
|
1559
|
-
'T5 getstate.card-
|
|
1554
|
+
await httpMcpControlplane('getstate.card-private', { board_id: BOARD_ID, card_id: T5_CARD_ID, key: 'chat.foundry_thread_id' }),
|
|
1555
|
+
'T5 getstate.card-private',
|
|
1560
1556
|
);
|
|
1561
|
-
assert(t5GetMeta?.exists === true && t5GetMeta?.value === t5ThreadId, `T5 getstate.card-
|
|
1557
|
+
assert(t5GetMeta?.exists === true && t5GetMeta?.value === t5ThreadId, `T5 getstate.card-private mismatch: ${JSON.stringify(t5GetMeta)}`);
|
|
1562
1558
|
|
|
1563
1559
|
const t5ReadCards = expectMcpSuccess(
|
|
1564
1560
|
await httpMcp('manage.read-card', { card_id: T5_CARD_ID }),
|
|
1565
1561
|
'T5 manage.read-card meta-redaction',
|
|
1566
1562
|
);
|
|
1567
1563
|
const t5ReadCard = Array.isArray(t5ReadCards) ? t5ReadCards[0] : null;
|
|
1568
|
-
assert(t5ReadCard && t5ReadCard.
|
|
1564
|
+
assert(t5ReadCard && t5ReadCard.__private === undefined, 'T5 expected manage.read-card to redact __private');
|
|
1569
1565
|
|
|
1570
1566
|
const t5Inspect = expectMcpSuccess(
|
|
1571
1567
|
await httpMcp('inspect.card-definition-and-runtime', { card_id: T5_CARD_ID }),
|
|
1572
1568
|
'T5 inspect.card-definition-and-runtime meta-redaction',
|
|
1573
1569
|
);
|
|
1574
|
-
assert(t5Inspect?.card_definition_and_static_data?.
|
|
1575
|
-
console.log('[T5] ok: regular /mcp surfaces redact
|
|
1570
|
+
assert(t5Inspect?.card_definition_and_static_data?.__private === undefined, 'T5 expected inspect to redact card_definition_and_static_data.__private');
|
|
1571
|
+
console.log('[T5] ok: regular /mcp surfaces redact __private');
|
|
1576
1572
|
|
|
1577
1573
|
// ── T5: admin-only card round-trip ──────────────────────────────────────
|
|
1578
1574
|
// 1. Read the existing card definition via the normal read-card path.
|
|
@@ -1610,34 +1606,32 @@ try {
|
|
|
1610
1606
|
);
|
|
1611
1607
|
const t5AdminCards = Array.isArray(t5AdminRead?.cards) ? t5AdminRead.cards : [];
|
|
1612
1608
|
assert(t5AdminCards.length > 0, 'T5 expected admin-read-card to return the card');
|
|
1613
|
-
assert(t5AdminCards[0]?.
|
|
1614
|
-
console.log('[T5] ok: manage.admin-read-card returns card with
|
|
1609
|
+
assert(t5AdminCards[0]?.__private?.visible_controlplane_only === true, `T5 expected __private.visible_controlplane_only=true, got: ${JSON.stringify(t5AdminCards[0]?.__private)}`);
|
|
1610
|
+
console.log('[T5] ok: manage.admin-read-card returns card with __private.visible_controlplane_only=true');
|
|
1615
1611
|
|
|
1616
|
-
// 5. Guard: setstate.card-
|
|
1617
|
-
|
|
1618
|
-
// the reserved segment, so it reaches the guard.
|
|
1619
|
-
const t5MetaGuard = await httpMcpControlplane('setstate.card-meta', {
|
|
1612
|
+
// 5. Guard: setstate.card-private must block changing the flag to a different value.
|
|
1613
|
+
const t5MetaGuard = await httpMcpControlplane('setstate.card-private', {
|
|
1620
1614
|
board_id: BOARD_ID,
|
|
1621
1615
|
card_id: t5AdminCardId,
|
|
1622
|
-
key: 'chat.
|
|
1616
|
+
key: 'chat.visible_controlplane_only',
|
|
1623
1617
|
value: false, // differs from current flag value (true) → must be rejected
|
|
1624
1618
|
});
|
|
1625
1619
|
assert(t5MetaGuard?.status !== 200,
|
|
1626
|
-
`T5 expected setstate.card-
|
|
1627
|
-
console.log('[T5] ok: setstate.card-
|
|
1620
|
+
`T5 expected setstate.card-private to reject changing visible_controlplane_only, got: ${JSON.stringify(t5MetaGuard)}`);
|
|
1621
|
+
console.log('[T5] ok: setstate.card-private blocked flag mutation (false != true)');
|
|
1628
1622
|
|
|
1629
1623
|
// 6. Guard: same key with value matching the current flag (true) must pass (idempotent).
|
|
1630
1624
|
const t5MetaIdempotent = expectMcpSuccess(
|
|
1631
|
-
await httpMcpControlplane('setstate.card-
|
|
1625
|
+
await httpMcpControlplane('setstate.card-private', {
|
|
1632
1626
|
board_id: BOARD_ID,
|
|
1633
1627
|
card_id: t5AdminCardId,
|
|
1634
|
-
key: 'chat.
|
|
1628
|
+
key: 'chat.visible_controlplane_only',
|
|
1635
1629
|
value: true, // matches current flag value → idempotent, allowed
|
|
1636
1630
|
}),
|
|
1637
|
-
'T5 setstate.card-
|
|
1631
|
+
'T5 setstate.card-private idempotent same-value',
|
|
1638
1632
|
);
|
|
1639
|
-
assert(t5MetaIdempotent, 'T5 expected setstate.card-
|
|
1640
|
-
console.log('[T5] ok: setstate.card-
|
|
1633
|
+
assert(t5MetaIdempotent, 'T5 expected setstate.card-private to succeed with matching flag value');
|
|
1634
|
+
console.log('[T5] ok: setstate.card-private idempotent same-value allowed');
|
|
1641
1635
|
}
|
|
1642
1636
|
}
|
|
1643
1637
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# board-firestore
|
|
2
|
+
|
|
3
|
+
Example of a [yaml-flow](https://www.npmjs.com/package/yaml-flow) board backed by **Cloud Firestore** (Firebase).
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Browser SPA Node.js Worker (server/worker.js)
|
|
9
|
+
────────────── ──────────────────────────────────
|
|
10
|
+
Firebase JS SDK ──> Firestore <── Firebase Admin SDK
|
|
11
|
+
│
|
|
12
|
+
board cards,
|
|
13
|
+
queue messages,
|
|
14
|
+
blob storage
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **Worker** — runs queue lanes (board-worker, chat-agent, process-accumulated) and exposes an HTTP control-plane on port 7900 for the browser SPA.
|
|
18
|
+
- **Browser** — uses the `ServerRuntimeControlface` IIFE (`browser/server-runtime-controlface.js`) with a Firestore JS SDK adapter to read card state and trigger board operations.
|
|
19
|
+
|
|
20
|
+
## Firestore data layout
|
|
21
|
+
|
|
22
|
+
All data lives under `boards/{boardId}/`:
|
|
23
|
+
|
|
24
|
+
| Subcollection | Purpose |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `kv-{namespace}/` | `AsyncKVStorage` (card state, config, etc.) |
|
|
27
|
+
| `cards/` | Card store (KV, keyed by card ID) |
|
|
28
|
+
| `runtime-out/` | Computed outputs store |
|
|
29
|
+
| `journal/` | Append-only board journal |
|
|
30
|
+
| `worker-queue/` | Board worker task queue |
|
|
31
|
+
| `chat-queue/` | Chat agent dispatch queue |
|
|
32
|
+
| `process-queue/` | processAccumulated trigger queue |
|
|
33
|
+
| `blobs-{namespace}/` | Blob/artifact storage |
|
|
34
|
+
| `scratch/` | Ephemeral scratch storage |
|
|
35
|
+
| `archive-stream-{name}/` | Named archive streams |
|
|
36
|
+
| `archive-blob-{name}/` | Named archive blob collections |
|
|
37
|
+
| `locks/board-lock` | Distributed lock document |
|
|
38
|
+
|
|
39
|
+
> **Required Firestore composite index** for each queue collection:
|
|
40
|
+
> Fields: `dead` (ASC), `visibleAfter` (ASC)
|
|
41
|
+
|
|
42
|
+
## Setup
|
|
43
|
+
|
|
44
|
+
1. Create a Firebase project and enable Firestore.
|
|
45
|
+
2. Generate a service account key and save it as `server/service-account.json` (or set `GOOGLE_APPLICATION_CREDENTIALS` env var).
|
|
46
|
+
3. Install dependencies:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. Start the worker:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
FIREBASE_PROJECT_ID=your-project npm run worker
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The worker listens on `http://localhost:7900` by default.
|
|
59
|
+
|
|
60
|
+
## Browser usage
|
|
61
|
+
|
|
62
|
+
Include the IIFE bundle in your HTML:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<script src="/browser/server-runtime-controlface.js"></script>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then use `browser/board-runtime.js` as a reference for constructing the runtime with the Firebase JS SDK.
|
|
69
|
+
|
|
70
|
+
## Firestore security rules (development)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
rules_version = '2';
|
|
74
|
+
service cloud.firestore {
|
|
75
|
+
match /databases/{database}/documents {
|
|
76
|
+
match /boards/{boardId}/{document=**} {
|
|
77
|
+
allow read, write: if request.auth != null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|