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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* firestore-lock.js
|
|
3
|
+
*
|
|
4
|
+
* AsyncAtomicRelayLock backed by a Firestore document + runTransaction.
|
|
5
|
+
*
|
|
6
|
+
* Document shape: { held, holderId, expiresAt, acquiredAt }
|
|
7
|
+
* Guarantees at-most-one holder via Firestore transaction atomicity.
|
|
8
|
+
*
|
|
9
|
+
* @param {import('@google-cloud/firestore').DocumentReference} lockDoc
|
|
10
|
+
* @param {{ holderId?: string, ttlMs?: number }} [options]
|
|
11
|
+
* @returns {import('yaml-flow/cloud-storage').AsyncAtomicRelayLock}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
15
|
+
|
|
16
|
+
export function createFirestoreLock(lockDoc, options = {}) {
|
|
17
|
+
const holderId = options.holderId ?? randomUUID();
|
|
18
|
+
const ttlMs = options.ttlMs ?? 30_000;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
async tryAcquire() {
|
|
22
|
+
let released = false;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await lockDoc.firestore.runTransaction(async (tx) => {
|
|
26
|
+
const snap = await tx.get(lockDoc);
|
|
27
|
+
const now = new Date();
|
|
28
|
+
if (snap.exists) {
|
|
29
|
+
const d = snap.data();
|
|
30
|
+
if (d.held && d.expiresAt > now.toISOString()) {
|
|
31
|
+
throw Object.assign(new Error('locked'), { code: 'locked' });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
tx.set(lockDoc, {
|
|
35
|
+
held: true,
|
|
36
|
+
holderId,
|
|
37
|
+
expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
|
|
38
|
+
acquiredAt: now.toISOString(),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err?.code === 'locked') return null;
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return async () => {
|
|
47
|
+
if (released) return;
|
|
48
|
+
released = true;
|
|
49
|
+
try {
|
|
50
|
+
await lockDoc.firestore.runTransaction(async (tx) => {
|
|
51
|
+
const snap = await tx.get(lockDoc);
|
|
52
|
+
if (snap.exists && snap.data().holderId === holderId) {
|
|
53
|
+
tx.update(lockDoc, { held: false, holderId: null });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
// Best-effort release — lock will expire via TTL
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* firestore-queue-storage.js
|
|
3
|
+
*
|
|
4
|
+
* AsyncQueueStorage backed by Firestore with visibility-timeout leasing.
|
|
5
|
+
*
|
|
6
|
+
* Document shape:
|
|
7
|
+
* { id, body, enqueuedAt, attempt, visibleAfter, leaseToken, leaseExpiresAt,
|
|
8
|
+
* dead, deadReason, dedupKey? }
|
|
9
|
+
*
|
|
10
|
+
* Leasing is done via Firestore transactions: find visible messages,
|
|
11
|
+
* then atomically claim each one (optimistic concurrency — skip on race).
|
|
12
|
+
*
|
|
13
|
+
* Required Firestore composite index for the lease query:
|
|
14
|
+
* Collection group: <your collection>
|
|
15
|
+
* Fields: dead ASC, visibleAfter ASC
|
|
16
|
+
*
|
|
17
|
+
* @param {import('@google-cloud/firestore').CollectionReference} col
|
|
18
|
+
* @param {{ defaultVisibilityMs?: number }} [options]
|
|
19
|
+
* @returns {import('yaml-flow/cloud-storage').AsyncQueueStorage}
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { randomUUID } from 'node:crypto';
|
|
23
|
+
|
|
24
|
+
function lexicalId() {
|
|
25
|
+
const ts = String(Date.now()).padStart(13, '0');
|
|
26
|
+
const rand = Math.random().toString(36).slice(2, 10).padEnd(8, '0');
|
|
27
|
+
return `${ts}-${rand}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createFirestoreQueueStorage(col, options = {}) {
|
|
31
|
+
const defaultVisibilityMs = options.defaultVisibilityMs ?? 30_000;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
async enqueue(body) {
|
|
35
|
+
const id = lexicalId();
|
|
36
|
+
const now = new Date().toISOString();
|
|
37
|
+
await col.doc(id).set({
|
|
38
|
+
id,
|
|
39
|
+
body,
|
|
40
|
+
enqueuedAt: now,
|
|
41
|
+
attempt: 0,
|
|
42
|
+
visibleAfter: now,
|
|
43
|
+
leaseToken: null,
|
|
44
|
+
leaseExpiresAt: null,
|
|
45
|
+
dead: false,
|
|
46
|
+
deadReason: null,
|
|
47
|
+
});
|
|
48
|
+
return { id, body, enqueuedAt: now };
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async enqueueIfAbsent(body, dedupKey) {
|
|
52
|
+
const existing = await col
|
|
53
|
+
.where('dedupKey', '==', dedupKey)
|
|
54
|
+
.where('dead', '==', false)
|
|
55
|
+
.limit(1)
|
|
56
|
+
.get();
|
|
57
|
+
if (!existing.empty) return null;
|
|
58
|
+
const id = lexicalId();
|
|
59
|
+
const now = new Date().toISOString();
|
|
60
|
+
await col.doc(id).set({
|
|
61
|
+
id,
|
|
62
|
+
body,
|
|
63
|
+
enqueuedAt: now,
|
|
64
|
+
attempt: 0,
|
|
65
|
+
visibleAfter: now,
|
|
66
|
+
leaseToken: null,
|
|
67
|
+
leaseExpiresAt: null,
|
|
68
|
+
dead: false,
|
|
69
|
+
deadReason: null,
|
|
70
|
+
dedupKey,
|
|
71
|
+
});
|
|
72
|
+
return { id, body, enqueuedAt: now };
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async lease(opts = {}) {
|
|
76
|
+
const max = opts.max ?? 1;
|
|
77
|
+
const visibilityMs = opts.visibilityMs ?? defaultVisibilityMs;
|
|
78
|
+
const nowIso = new Date().toISOString();
|
|
79
|
+
|
|
80
|
+
// Find visible, non-dead messages — fetch extra to tolerate races
|
|
81
|
+
const snap = await col
|
|
82
|
+
.where('dead', '==', false)
|
|
83
|
+
.where('visibleAfter', '<=', nowIso)
|
|
84
|
+
.orderBy('visibleAfter')
|
|
85
|
+
.limit(max * 4)
|
|
86
|
+
.get();
|
|
87
|
+
|
|
88
|
+
const leased = [];
|
|
89
|
+
for (const doc of snap.docs) {
|
|
90
|
+
if (leased.length >= max) break;
|
|
91
|
+
|
|
92
|
+
const data = doc.data();
|
|
93
|
+
// Skip if currently leased and lease hasn't expired
|
|
94
|
+
if (data.leaseToken && data.leaseExpiresAt > nowIso) continue;
|
|
95
|
+
|
|
96
|
+
const leaseToken = randomUUID();
|
|
97
|
+
const leaseExpiresAt = new Date(Date.now() + visibilityMs).toISOString();
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
let claimedAttempt = 0;
|
|
101
|
+
await col.firestore.runTransaction(async (tx) => {
|
|
102
|
+
const fresh = await tx.get(doc.ref);
|
|
103
|
+
if (!fresh.exists) throw Object.assign(new Error('gone'), { code: 'gone' });
|
|
104
|
+
const d = fresh.data();
|
|
105
|
+
if (d.dead) throw Object.assign(new Error('dead'), { code: 'dead' });
|
|
106
|
+
if (d.leaseToken && d.leaseExpiresAt > new Date().toISOString()) {
|
|
107
|
+
throw Object.assign(new Error('taken'), { code: 'taken' });
|
|
108
|
+
}
|
|
109
|
+
claimedAttempt = (d.attempt ?? 0) + 1;
|
|
110
|
+
tx.update(doc.ref, { leaseToken, leaseExpiresAt, attempt: claimedAttempt });
|
|
111
|
+
});
|
|
112
|
+
leased.push({
|
|
113
|
+
id: data.id,
|
|
114
|
+
body: data.body,
|
|
115
|
+
enqueuedAt: data.enqueuedAt,
|
|
116
|
+
attempt: claimedAttempt,
|
|
117
|
+
leaseToken,
|
|
118
|
+
leaseExpiresAt,
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
// Race condition — skip this message
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return leased;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
async ack(messageId, leaseToken) {
|
|
128
|
+
try {
|
|
129
|
+
await col.firestore.runTransaction(async (tx) => {
|
|
130
|
+
const ref = col.doc(messageId);
|
|
131
|
+
const snap = await tx.get(ref);
|
|
132
|
+
if (!snap.exists) return;
|
|
133
|
+
if (snap.data().leaseToken !== leaseToken) throw new Error('token mismatch');
|
|
134
|
+
tx.delete(ref);
|
|
135
|
+
});
|
|
136
|
+
return true;
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
async nack(messageId, leaseToken, opts = {}) {
|
|
143
|
+
try {
|
|
144
|
+
await col.firestore.runTransaction(async (tx) => {
|
|
145
|
+
const ref = col.doc(messageId);
|
|
146
|
+
const snap = await tx.get(ref);
|
|
147
|
+
if (!snap.exists) return;
|
|
148
|
+
if (snap.data().leaseToken !== leaseToken) throw new Error('token mismatch');
|
|
149
|
+
if (opts.dead) {
|
|
150
|
+
tx.update(ref, {
|
|
151
|
+
dead: true,
|
|
152
|
+
deadReason: opts.reason ?? 'nacked',
|
|
153
|
+
leaseToken: null,
|
|
154
|
+
leaseExpiresAt: null,
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
tx.update(ref, {
|
|
158
|
+
leaseToken: null,
|
|
159
|
+
leaseExpiresAt: null,
|
|
160
|
+
visibleAfter: new Date().toISOString(),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return true;
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async peekActive(prefix) {
|
|
171
|
+
const snap = await col.where('dead', '==', false).orderBy('enqueuedAt').get();
|
|
172
|
+
return snap.docs
|
|
173
|
+
.map((d) => d.data())
|
|
174
|
+
.filter((d) => !prefix || String(d.id).startsWith(prefix))
|
|
175
|
+
.map((d) => ({ id: d.id, body: d.body, enqueuedAt: d.enqueuedAt }));
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
async peekDeadLetter(prefix) {
|
|
179
|
+
const snap = await col.where('dead', '==', true).orderBy('enqueuedAt').get();
|
|
180
|
+
return snap.docs
|
|
181
|
+
.map((d) => d.data())
|
|
182
|
+
.filter((d) => !prefix || String(d.id).startsWith(prefix))
|
|
183
|
+
.map((d) => ({ id: d.id, body: d.body, enqueuedAt: d.enqueuedAt, reason: d.deadReason }));
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* firestore-scratch-storage.js
|
|
3
|
+
*
|
|
4
|
+
* AsyncScratchStorage backed by Firestore.
|
|
5
|
+
* Extends AsyncBlobStorage with ephemeral-key creation and a config sub-map.
|
|
6
|
+
*
|
|
7
|
+
* @param {import('@google-cloud/firestore').CollectionReference} col
|
|
8
|
+
* @returns {import('yaml-flow/cloud-storage').AsyncScratchStorage}
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createFirestoreBlobStorage } from './firestore-blob-storage.js';
|
|
12
|
+
|
|
13
|
+
function lexicalId() {
|
|
14
|
+
const ts = String(Date.now()).padStart(13, '0');
|
|
15
|
+
const rand = Math.random().toString(36).slice(2, 10).padEnd(8, '0');
|
|
16
|
+
return `${ts}-${rand}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createFirestoreScratchStorage(col) {
|
|
20
|
+
const blob = createFirestoreBlobStorage(col);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
...blob,
|
|
24
|
+
|
|
25
|
+
async getUniqueKey(prefix = 'scratch-', suffix = '') {
|
|
26
|
+
return `${prefix}${lexicalId()}${suffix}`;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async create(data, prefix = 'scratch-', suffix = '') {
|
|
30
|
+
const key = `${prefix}${lexicalId()}${suffix}`;
|
|
31
|
+
await blob.write(key, data);
|
|
32
|
+
return key;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
keyRef(key) {
|
|
36
|
+
return { kind: 'firestore-blob', value: `${col.path}/${Buffer.from(key).toString('base64url')}` };
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
config: {
|
|
40
|
+
async get(k) {
|
|
41
|
+
const content = await blob.read(`__config__/${k}`);
|
|
42
|
+
if (content == null) return null;
|
|
43
|
+
try { return JSON.parse(content); } catch { return content; }
|
|
44
|
+
},
|
|
45
|
+
async set(k, v) {
|
|
46
|
+
await blob.write(`__config__/${k}`, JSON.stringify(v));
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* board-firestore/server/worker.js
|
|
4
|
+
*
|
|
5
|
+
* Long-running worker server for the Firestore-backed board.
|
|
6
|
+
* Runs three things in one Node.js process:
|
|
7
|
+
*
|
|
8
|
+
* 1. Queue lane runners — drain worker-queue, chat-queue, process-queue
|
|
9
|
+
* 2. Board control-plane HTTP — GET/POST /api/board/* for browser SPA
|
|
10
|
+
* 3. Callback endpoint — POST /api/board/callback/* for execution webhooks
|
|
11
|
+
*
|
|
12
|
+
* Environment variables:
|
|
13
|
+
* GOOGLE_APPLICATION_CREDENTIALS Path to Firebase service account JSON key
|
|
14
|
+
* FIREBASE_PROJECT_ID Firebase project ID
|
|
15
|
+
* BOARD_ID Firestore board document ID (default: "default")
|
|
16
|
+
* WORKER_PORT HTTP listen port (default: 7900)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import http from 'node:http';
|
|
20
|
+
import { Buffer } from 'node:buffer';
|
|
21
|
+
import { createSingleBoardServerRuntime } from 'yaml-flow/board-live-cards-server-runtime';
|
|
22
|
+
import { createHostedBoardQueueLaneRegistry } from 'yaml-flow/server-jobs-queue-runner';
|
|
23
|
+
import { startQueueLaneRunners } from 'yaml-flow/board-live-cards-node';
|
|
24
|
+
import { initializeApp, cert, getApps } from 'firebase-admin/app';
|
|
25
|
+
import { getFirestore } from 'firebase-admin/firestore';
|
|
26
|
+
import { createFirestoreBoardAdapter } from './adapters/firestore-board-adapter.js';
|
|
27
|
+
|
|
28
|
+
// ── Firebase Admin SDK init ────────────────────────────────────────────────────
|
|
29
|
+
if (getApps().length === 0) {
|
|
30
|
+
initializeApp({
|
|
31
|
+
credential: cert(process.env.GOOGLE_APPLICATION_CREDENTIALS ?? './service-account.json'),
|
|
32
|
+
projectId: process.env.FIREBASE_PROJECT_ID,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const db = getFirestore();
|
|
37
|
+
|
|
38
|
+
// ── Config ─────────────────────────────────────────────────────────────────────
|
|
39
|
+
const BOARD_ID = process.env.BOARD_ID ?? 'default';
|
|
40
|
+
const PORT = Number(process.env.WORKER_PORT ?? 7900);
|
|
41
|
+
|
|
42
|
+
// ── Helper: build a b64 ref string (matches serializeRef in yaml-flow) ──────────
|
|
43
|
+
function makeRef(kind, value) {
|
|
44
|
+
return `b64:${Buffer.from(JSON.stringify({ kind, value })).toString('base64url')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Firestore board adapter ────────────────────────────────────────────────────
|
|
48
|
+
const boardAdapter = createFirestoreBoardAdapter(db, BOARD_ID);
|
|
49
|
+
|
|
50
|
+
// ── Single-board runtime ───────────────────────────────────────────────────────
|
|
51
|
+
const runtime = createSingleBoardServerRuntime({
|
|
52
|
+
boardId: BOARD_ID,
|
|
53
|
+
boards: [
|
|
54
|
+
{
|
|
55
|
+
label: `Board — ${BOARD_ID}`,
|
|
56
|
+
boardAdapter,
|
|
57
|
+
baseRef: { kind: 'firestore', value: `boards/${BOARD_ID}` },
|
|
58
|
+
cardStoreRef: makeRef('firestore', `boards/${BOARD_ID}/cards`),
|
|
59
|
+
outputsStoreRef: makeRef('firestore', `boards/${BOARD_ID}/runtime-out`),
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
invocationAdapter: {
|
|
63
|
+
/**
|
|
64
|
+
* Route execution requests to the appropriate handler.
|
|
65
|
+
* For worker queue dispatch, yaml-flow uses boardAdapter.dispatchExecution internally.
|
|
66
|
+
* This adapter is the last-resort fallback for refs not handled by boardAdapter.
|
|
67
|
+
*/
|
|
68
|
+
async invoke(ref, _args) {
|
|
69
|
+
return { dispatched: false, error: `No invocation handler for ${ref?.howToRun ?? '?'}` };
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
logger: {
|
|
73
|
+
info: (msg, ...args) => console.log(`[worker:${BOARD_ID}] ${msg}`, ...args),
|
|
74
|
+
warn: (msg, ...args) => console.warn(`[worker:${BOARD_ID}] ${msg}`, ...args),
|
|
75
|
+
error: (msg, ...args) => console.error(`[worker:${BOARD_ID}] ${msg}`, ...args),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── Queue lane runners ─────────────────────────────────────────────────────────
|
|
80
|
+
const stopRunners = startQueueLaneRunners(
|
|
81
|
+
createHostedBoardQueueLaneRegistry({
|
|
82
|
+
boardId: BOARD_ID,
|
|
83
|
+
runtime,
|
|
84
|
+
boardAdapter,
|
|
85
|
+
logger: {
|
|
86
|
+
info: (msg, ...args) => console.log(`[queue:${BOARD_ID}] ${msg}`, ...args),
|
|
87
|
+
warn: (msg, ...args) => console.warn(`[queue:${BOARD_ID}] ${msg}`, ...args),
|
|
88
|
+
error: (msg, ...args) => console.error(`[queue:${BOARD_ID}] ${msg}`, ...args),
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// ── HTTP server ────────────────────────────────────────────────────────────────
|
|
94
|
+
function sendJson(res, status, payload) {
|
|
95
|
+
const body = JSON.stringify(payload);
|
|
96
|
+
res.writeHead(status, {
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
'Content-Length': Buffer.byteLength(body),
|
|
99
|
+
'Access-Control-Allow-Origin': '*',
|
|
100
|
+
});
|
|
101
|
+
res.end(body);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const server = http.createServer(async (req, res) => {
|
|
105
|
+
// Handle CORS preflight
|
|
106
|
+
if (req.method === 'OPTIONS') {
|
|
107
|
+
res.writeHead(204, {
|
|
108
|
+
'Access-Control-Allow-Origin': '*',
|
|
109
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
110
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
111
|
+
});
|
|
112
|
+
res.end();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const url = new URL(req.url ?? '/', `http://localhost:${PORT}`);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const handled = await runtime.handleRuntimeApi(req, res, url);
|
|
120
|
+
if (!handled) sendJson(res, 404, { error: 'not found', path: url.pathname });
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(`[worker:${BOARD_ID}] unhandled error for ${req.method} ${req.url}:`, err);
|
|
123
|
+
if (!res.headersSent) sendJson(res, 500, { error: 'internal error' });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
server.listen(PORT, () => {
|
|
128
|
+
console.log(`[board-firestore/worker] Listening on http://localhost:${PORT}`);
|
|
129
|
+
console.log(`[board-firestore/worker] Board ID : ${BOARD_ID}`);
|
|
130
|
+
console.log(`[board-firestore/worker] Firebase project: ${process.env.FIREBASE_PROJECT_ID ?? '(auto-detect)'}`);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── Graceful shutdown ───────────────────────────────────────────────────────────
|
|
134
|
+
function shutdown() {
|
|
135
|
+
console.log('[board-firestore/worker] Shutting down...');
|
|
136
|
+
stopRunners();
|
|
137
|
+
server.close(() => {
|
|
138
|
+
console.log('[board-firestore/worker] HTTP server closed.');
|
|
139
|
+
process.exit(0);
|
|
140
|
+
});
|
|
141
|
+
// Force exit after 10 s if connections linger
|
|
142
|
+
setTimeout(() => process.exit(0), 10_000).unref();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
process.on('SIGTERM', shutdown);
|
|
146
|
+
process.on('SIGINT', shutdown);
|
|
@@ -58,4 +58,4 @@ declare function createArtifactsStore(blob: BlobStorage): ArtifactsStore;
|
|
|
58
58
|
declare function createFileArtifactsStore(store: ArtifactsStore): FileArtifactsStore;
|
|
59
59
|
declare function createCardFileMetadataStore(): CardFileMetadataStore;
|
|
60
60
|
|
|
61
|
-
export { type ArtifactInfo as A, type ArtifactsStore as a, createCardFileMetadataStore as b, createArtifactsStore as c, createFileArtifactsStore as d };
|
|
61
|
+
export { type ArtifactInfo as A, type CardFileMetadataStore as C, type ArtifactsStore as a, createCardFileMetadataStore as b, createArtifactsStore as c, createFileArtifactsStore as d };
|
|
@@ -58,4 +58,4 @@ declare function createArtifactsStore(blob: BlobStorage): ArtifactsStore;
|
|
|
58
58
|
declare function createFileArtifactsStore(store: ArtifactsStore): FileArtifactsStore;
|
|
59
59
|
declare function createCardFileMetadataStore(): CardFileMetadataStore;
|
|
60
60
|
|
|
61
|
-
export { type ArtifactInfo as A, type ArtifactsStore as a, createCardFileMetadataStore as b, createArtifactsStore as c, createFileArtifactsStore as d };
|
|
61
|
+
export { type ArtifactInfo as A, type CardFileMetadataStore as C, type ArtifactsStore as a, createCardFileMetadataStore as b, createArtifactsStore as c, createFileArtifactsStore as d };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as ArtifactInfo, a as ArtifactsStore } from './artifacts-store-lib-
|
|
1
|
+
import { C as CommandInput, a as CommandResult } from './board-live-cards-public-LlVUQPL2.cjs';
|
|
2
|
+
import { A as ArtifactInfo, a as ArtifactsStore } from './artifacts-store-lib-D9nMkVcE.cjs';
|
|
3
3
|
import './storage-interface-BFiD3kyB.cjs';
|
|
4
4
|
import './execution-refs.cjs';
|
|
5
5
|
import './types-BBhqYGhE.cjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as ArtifactInfo, a as ArtifactsStore } from './artifacts-store-lib-
|
|
1
|
+
import { C as CommandInput, a as CommandResult } from './board-live-cards-public-JNRKfBZy.js';
|
|
2
|
+
import { A as ArtifactInfo, a as ArtifactsStore } from './artifacts-store-lib-DSSMqVL2.js';
|
|
3
3
|
import './storage-interface-BFiD3kyB.js';
|
|
4
4
|
import './execution-refs.js';
|
|
5
5
|
import './types-BBhqYGhE.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkPBOQ4HYB_cjs=require('./chunk-PBOQ4HYB.cjs');require('./chunk-IXZG74EW.cjs'),require('./chunk-HWYMZK3N.cjs'),require('./chunk-UJ7ZTV4J.cjs'),require('./chunk-GNFE24S7.cjs');Object.defineProperty(exports,"createBoardLiveCardsMcp",{enumerable:true,get:function(){return chunkPBOQ4HYB_cjs.a}});//# sourceMappingURL=board-live-cards-mcp.cjs.map
|
|
2
2
|
//# sourceMappingURL=board-live-cards-mcp.cjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as LiveCard, a as CommandResult } from './board-live-cards-public-LlVUQPL2.cjs';
|
|
2
2
|
import { ChatStorePublic } from './chat-store-public.cjs';
|
|
3
|
-
import {
|
|
3
|
+
import { a as ChatRecord } from './chat-storage-lib-B9Q34Dyv.cjs';
|
|
4
4
|
import './storage-interface-BFiD3kyB.cjs';
|
|
5
5
|
import './execution-refs.cjs';
|
|
6
6
|
import './types-BBhqYGhE.cjs';
|
|
@@ -114,6 +114,14 @@ interface BoardLiveCardsMcpManageAddChatEntryAndAnyAttachmentsResult {
|
|
|
114
114
|
files: Array<Record<string, unknown>>;
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
+
interface BoardLiveCardsMcpManageAddChatAttachmentResult {
|
|
118
|
+
status: 'success';
|
|
119
|
+
data: {
|
|
120
|
+
cardId: string;
|
|
121
|
+
turn: string;
|
|
122
|
+
files: Array<Record<string, unknown>>;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
117
125
|
interface BoardLiveCardsMcpPreflightRunOneCycleResult {
|
|
118
126
|
status: 'success';
|
|
119
127
|
data: {
|
|
@@ -242,11 +250,21 @@ interface BoardLiveCardsMcpDeps {
|
|
|
242
250
|
nonCore: BoardLiveCardsMcpNonCoreDeps;
|
|
243
251
|
cardStore: BoardLiveCardsMcpCardStoreDeps;
|
|
244
252
|
chatStore: ChatStorePublic;
|
|
253
|
+
processAccumulated?: () => Awaitable<CommandResult>;
|
|
254
|
+
sourceFetchDone?: (args: {
|
|
255
|
+
token: string;
|
|
256
|
+
ref: string;
|
|
257
|
+
}) => Awaitable<CommandResult>;
|
|
258
|
+
sourceFetchFailed?: (args: {
|
|
259
|
+
token: string;
|
|
260
|
+
reason: string;
|
|
261
|
+
}) => Awaitable<CommandResult>;
|
|
245
262
|
uploadCardFile(args: {
|
|
246
263
|
cardId: string;
|
|
247
264
|
fileName: string;
|
|
248
265
|
contentType: string;
|
|
249
266
|
bytes: Uint8Array;
|
|
267
|
+
suppressChatRecordWrite?: boolean;
|
|
250
268
|
}): Awaitable<{
|
|
251
269
|
ok: true;
|
|
252
270
|
file: Record<string, unknown>;
|
|
@@ -263,6 +281,7 @@ interface BoardLiveCardsMcpDeps {
|
|
|
263
281
|
}): unknown | null;
|
|
264
282
|
}
|
|
265
283
|
interface BoardLiveCardsMcp {
|
|
284
|
+
listRuntimeCards(): Promise<LiveCard[]>;
|
|
266
285
|
discoverSourceKinds(): Promise<BoardLiveCardsMcpDiscoverSourceKindsResult>;
|
|
267
286
|
inspectBoardRuntimeStatus(): Promise<BoardLiveCardsMcpBoardStatusResult>;
|
|
268
287
|
inspectCardDefinitionAndRuntime(args: {
|
|
@@ -310,6 +329,12 @@ interface BoardLiveCardsMcp {
|
|
|
310
329
|
manageReadCard(args: {
|
|
311
330
|
cardId: string;
|
|
312
331
|
}): Promise<LiveCard[]>;
|
|
332
|
+
manageAddChatAttachment(args: {
|
|
333
|
+
cardId: string;
|
|
334
|
+
role?: string;
|
|
335
|
+
turn?: string;
|
|
336
|
+
files?: unknown[];
|
|
337
|
+
}): Promise<BoardLiveCardsMcpManageAddChatAttachmentResult>;
|
|
313
338
|
manageAddChatEntryAndAnyAttachments(args: {
|
|
314
339
|
cardId: string;
|
|
315
340
|
role: string;
|
|
@@ -317,6 +342,10 @@ interface BoardLiveCardsMcp {
|
|
|
317
342
|
turn?: string;
|
|
318
343
|
files?: unknown[];
|
|
319
344
|
}): Promise<BoardLiveCardsMcpManageAddChatEntryAndAnyAttachmentsResult>;
|
|
345
|
+
managePatchCard(args: {
|
|
346
|
+
cardId: string;
|
|
347
|
+
patch: UnknownRecord;
|
|
348
|
+
}): Promise<BoardLiveCardsMcpManageUpsertCardResult>;
|
|
320
349
|
manageUpsertCard(args: {
|
|
321
350
|
cardId: string;
|
|
322
351
|
candidateCardContent: UnknownRecord;
|
|
@@ -344,7 +373,26 @@ interface BoardLiveCardsMcp {
|
|
|
344
373
|
cardId: string;
|
|
345
374
|
active: boolean;
|
|
346
375
|
};
|
|
376
|
+
webhookProcessAccumulated(): Promise<CommandResult<{
|
|
377
|
+
runtime_result: unknown;
|
|
378
|
+
}>>;
|
|
379
|
+
webhookSourceFetchDone(args: {
|
|
380
|
+
token: string;
|
|
381
|
+
ref: string;
|
|
382
|
+
}): Promise<CommandResult<{
|
|
383
|
+
token: string;
|
|
384
|
+
ref: string;
|
|
385
|
+
runtime_result: unknown;
|
|
386
|
+
}>>;
|
|
387
|
+
webhookSourceFetchFailed(args: {
|
|
388
|
+
token: string;
|
|
389
|
+
reason: string;
|
|
390
|
+
}): Promise<CommandResult<{
|
|
391
|
+
token: string;
|
|
392
|
+
reason: string;
|
|
393
|
+
runtime_result: unknown;
|
|
394
|
+
}>>;
|
|
347
395
|
}
|
|
348
396
|
declare function createBoardLiveCardsMcp(deps: BoardLiveCardsMcpDeps): BoardLiveCardsMcp;
|
|
349
397
|
|
|
350
|
-
export { type BoardLiveCardsMcp, type BoardLiveCardsMcpBoardDeps, type BoardLiveCardsMcpBoardStatusCard, type BoardLiveCardsMcpBoardStatusResult, type BoardLiveCardsMcpCardStoreDeps, type BoardLiveCardsMcpDeps, type BoardLiveCardsMcpDiscoverSourceKindsResult, type BoardLiveCardsMcpFileDownloadDescriptor, type BoardLiveCardsMcpInspectCardDefinitionAndRuntimeResult, type BoardLiveCardsMcpInspectChatMessagesResult, type BoardLiveCardsMcpManageAddChatEntryAndAnyAttachmentsResult, type BoardLiveCardsMcpManageUpsertCardFailureResult, type BoardLiveCardsMcpManageUpsertCardResult, type BoardLiveCardsMcpManageUpsertCardSuccessResult, type BoardLiveCardsMcpNonCoreDeps, type BoardLiveCardsMcpPreflightMaterializeResult, type BoardLiveCardsMcpPreflightRunOneCycleResult, type BoardLiveCardsMcpRenderedView, type BoardLiveCardsMcpRenderedViewElement, createBoardLiveCardsMcp };
|
|
398
|
+
export { type BoardLiveCardsMcp, type BoardLiveCardsMcpBoardDeps, type BoardLiveCardsMcpBoardStatusCard, type BoardLiveCardsMcpBoardStatusResult, type BoardLiveCardsMcpCardStoreDeps, type BoardLiveCardsMcpDeps, type BoardLiveCardsMcpDiscoverSourceKindsResult, type BoardLiveCardsMcpFileDownloadDescriptor, type BoardLiveCardsMcpInspectCardDefinitionAndRuntimeResult, type BoardLiveCardsMcpInspectChatMessagesResult, type BoardLiveCardsMcpManageAddChatAttachmentResult, type BoardLiveCardsMcpManageAddChatEntryAndAnyAttachmentsResult, type BoardLiveCardsMcpManageUpsertCardFailureResult, type BoardLiveCardsMcpManageUpsertCardResult, type BoardLiveCardsMcpManageUpsertCardSuccessResult, type BoardLiveCardsMcpNonCoreDeps, type BoardLiveCardsMcpPreflightMaterializeResult, type BoardLiveCardsMcpPreflightRunOneCycleResult, type BoardLiveCardsMcpRenderedView, type BoardLiveCardsMcpRenderedViewElement, createBoardLiveCardsMcp };
|