@particle-academy/agent-integrations 0.33.0 → 0.35.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.
- package/README.md +9 -0
- package/dist/bridges/passkeys.d.cts +185 -0
- package/dist/bridges/passkeys.d.ts +185 -0
- package/dist/bridges-passkeys.cjs +314 -0
- package/dist/bridges-passkeys.cjs.map +1 -0
- package/dist/bridges-passkeys.js +7 -0
- package/dist/bridges-passkeys.js.map +1 -0
- package/dist/chunk-7THGQUX3.js +193 -0
- package/dist/chunk-7THGQUX3.js.map +1 -0
- package/dist/{chunk-SJTLTITO.js → chunk-ABP2THQV.js} +3 -3
- package/dist/{chunk-SJTLTITO.js.map → chunk-ABP2THQV.js.map} +1 -1
- package/dist/{chunk-N6RLSLOF.js → chunk-JDCB2NMM.js} +29 -2
- package/dist/chunk-JDCB2NMM.js.map +1 -0
- package/dist/components-shared-whiteboard.cjs +27 -0
- package/dist/components-shared-whiteboard.cjs.map +1 -1
- package/dist/components-shared-whiteboard.js +2 -2
- package/dist/index.cjs +266 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +54 -11
- package/dist/index.js.map +1 -1
- package/dist/sharing/index.d.cts +15 -0
- package/dist/sharing/index.d.ts +15 -0
- package/dist/sharing.cjs +27 -0
- package/dist/sharing.cjs.map +1 -1
- package/dist/sharing.js +1 -1
- package/package.json +11 -1
- package/dist/chunk-N6RLSLOF.js.map +0 -1
package/dist/sharing/index.d.cts
CHANGED
|
@@ -39,6 +39,7 @@ declare class SseRelayTransport implements Transport {
|
|
|
39
39
|
private state;
|
|
40
40
|
private expectedToken;
|
|
41
41
|
private peers;
|
|
42
|
+
private peerListeners;
|
|
42
43
|
constructor(options: SseRelayOptions);
|
|
43
44
|
bindServer(server: MicroMcpServer): void;
|
|
44
45
|
/** Open the receive channel. Idempotent. */
|
|
@@ -50,6 +51,20 @@ declare class SseRelayTransport implements Transport {
|
|
|
50
51
|
send(message: JsonRpcMessage): void;
|
|
51
52
|
close(): void;
|
|
52
53
|
onStateChange(listener: (state: RelayState) => void): () => void;
|
|
54
|
+
/** How many remote peers (agents) are currently attached to this session. */
|
|
55
|
+
peerCount(): number;
|
|
56
|
+
/**
|
|
57
|
+
* Subscribe to peer arrivals/departures. Fires immediately with the current
|
|
58
|
+
* count.
|
|
59
|
+
*
|
|
60
|
+
* This is the ONLY honest answer to "is an agent actually here?".
|
|
61
|
+
* `onStateChange` reports the BROWSER's own channel to the relay, which opens
|
|
62
|
+
* the instant sharing starts — so a UI keyed on it announces "Agent is
|
|
63
|
+
* driving" to a human who has not yet handed the link to anybody, and can
|
|
64
|
+
* never tell them when one really arrives.
|
|
65
|
+
*/
|
|
66
|
+
onPeersChange(listener: (count: number) => void): () => void;
|
|
67
|
+
private notifyPeers;
|
|
53
68
|
/**
|
|
54
69
|
* For relays that wrap each frame with auth metadata: hosts can call this
|
|
55
70
|
* directly when a frame arrives via a non-SSE path. The transport will
|
package/dist/sharing/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ declare class SseRelayTransport implements Transport {
|
|
|
39
39
|
private state;
|
|
40
40
|
private expectedToken;
|
|
41
41
|
private peers;
|
|
42
|
+
private peerListeners;
|
|
42
43
|
constructor(options: SseRelayOptions);
|
|
43
44
|
bindServer(server: MicroMcpServer): void;
|
|
44
45
|
/** Open the receive channel. Idempotent. */
|
|
@@ -50,6 +51,20 @@ declare class SseRelayTransport implements Transport {
|
|
|
50
51
|
send(message: JsonRpcMessage): void;
|
|
51
52
|
close(): void;
|
|
52
53
|
onStateChange(listener: (state: RelayState) => void): () => void;
|
|
54
|
+
/** How many remote peers (agents) are currently attached to this session. */
|
|
55
|
+
peerCount(): number;
|
|
56
|
+
/**
|
|
57
|
+
* Subscribe to peer arrivals/departures. Fires immediately with the current
|
|
58
|
+
* count.
|
|
59
|
+
*
|
|
60
|
+
* This is the ONLY honest answer to "is an agent actually here?".
|
|
61
|
+
* `onStateChange` reports the BROWSER's own channel to the relay, which opens
|
|
62
|
+
* the instant sharing starts — so a UI keyed on it announces "Agent is
|
|
63
|
+
* driving" to a human who has not yet handed the link to anybody, and can
|
|
64
|
+
* never tell them when one really arrives.
|
|
65
|
+
*/
|
|
66
|
+
onPeersChange(listener: (count: number) => void): () => void;
|
|
67
|
+
private notifyPeers;
|
|
53
68
|
/**
|
|
54
69
|
* For relays that wrap each frame with auth metadata: hosts can call this
|
|
55
70
|
* directly when a frame arrives via a non-SSE path. The transport will
|
package/dist/sharing.cjs
CHANGED
|
@@ -96,6 +96,7 @@ var SseRelayTransport = class {
|
|
|
96
96
|
this.listeners = /* @__PURE__ */ new Set();
|
|
97
97
|
this.state = "idle";
|
|
98
98
|
this.peers = /* @__PURE__ */ new Set();
|
|
99
|
+
this.peerListeners = /* @__PURE__ */ new Set();
|
|
99
100
|
this.opts = options;
|
|
100
101
|
this.expectedToken = options.token;
|
|
101
102
|
}
|
|
@@ -156,6 +157,8 @@ var SseRelayTransport = class {
|
|
|
156
157
|
this.es?.close();
|
|
157
158
|
this.es = void 0;
|
|
158
159
|
this.connected = false;
|
|
160
|
+
this.peers.clear();
|
|
161
|
+
this.notifyPeers();
|
|
159
162
|
this.setState("closed");
|
|
160
163
|
}
|
|
161
164
|
onStateChange(listener) {
|
|
@@ -163,6 +166,28 @@ var SseRelayTransport = class {
|
|
|
163
166
|
listener(this.state);
|
|
164
167
|
return () => this.listeners.delete(listener);
|
|
165
168
|
}
|
|
169
|
+
/** How many remote peers (agents) are currently attached to this session. */
|
|
170
|
+
peerCount() {
|
|
171
|
+
return this.peers.size;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Subscribe to peer arrivals/departures. Fires immediately with the current
|
|
175
|
+
* count.
|
|
176
|
+
*
|
|
177
|
+
* This is the ONLY honest answer to "is an agent actually here?".
|
|
178
|
+
* `onStateChange` reports the BROWSER's own channel to the relay, which opens
|
|
179
|
+
* the instant sharing starts — so a UI keyed on it announces "Agent is
|
|
180
|
+
* driving" to a human who has not yet handed the link to anybody, and can
|
|
181
|
+
* never tell them when one really arrives.
|
|
182
|
+
*/
|
|
183
|
+
onPeersChange(listener) {
|
|
184
|
+
this.peerListeners.add(listener);
|
|
185
|
+
listener(this.peers.size);
|
|
186
|
+
return () => this.peerListeners.delete(listener);
|
|
187
|
+
}
|
|
188
|
+
notifyPeers() {
|
|
189
|
+
for (const l of this.peerListeners) l(this.peers.size);
|
|
190
|
+
}
|
|
166
191
|
/**
|
|
167
192
|
* For relays that wrap each frame with auth metadata: hosts can call this
|
|
168
193
|
* directly when a frame arrives via a non-SSE path. The transport will
|
|
@@ -197,6 +222,7 @@ var SseRelayTransport = class {
|
|
|
197
222
|
if ("method" in message && message.method === "notifications/peer_joined") {
|
|
198
223
|
const subscriberId = String(message.params?.subscriberId ?? "peer");
|
|
199
224
|
this.peers.add(subscriberId);
|
|
225
|
+
this.notifyPeers();
|
|
200
226
|
fancyAutoCommon.emitActivity({
|
|
201
227
|
agentId: "agent",
|
|
202
228
|
agentName: "Agent",
|
|
@@ -211,6 +237,7 @@ var SseRelayTransport = class {
|
|
|
211
237
|
if ("method" in message && message.method === "notifications/peer_left") {
|
|
212
238
|
const subscriberId = String(message.params?.subscriberId ?? "peer");
|
|
213
239
|
this.peers.delete(subscriberId);
|
|
240
|
+
this.notifyPeers();
|
|
214
241
|
if (this.peers.size === 0) {
|
|
215
242
|
fancyAutoCommon.emitActivity({
|
|
216
243
|
agentId: "agent",
|
package/dist/sharing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/presence/registry.ts","../src/sharing/token.ts","../src/sharing/sse-relay.ts"],"names":["emitActivity","onActivity","readActivityHistory","resetActivityRegistry"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,IAAA,gBAAA,GAAA,EAAA;AAAA,QAAA,CAAA,gBAAA,EAAA;AAAA,EAAA,YAAA,EAAA,MAAAA,4BAAA;AAAA,EAAA,UAAA,EAAA,MAAAC,0BAAA;AAAA,EAAA,mBAAA,EAAA,MAAAC,mCAAA;AAAA,EAAA,qBAAA,EAAA,MAAAC;AAAA,CAAA,CAAA;AAAA,IAAA,aAAA,GAAA,KAAA,CAAA;AAAA,EAAA,0BAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACQA,IAAM,WAAA,GAAc,EAAA;AAWb,SAAS,uBAAA,GAA6C;AAC3D,EAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,EAAA,MAAM,QAAQ,WAAA,EAAY;AAC1B,EAAA,OAAO,EAAE,IAAI,KAAA,EAAO,OAAA,EAAS,MAAM,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAE;AACjD;AAEO,SAAS,eAAA,CAAgB,IAAY,KAAA,EAAkC;AAC5E,EAAA,OAAO,EAAE,IAAI,KAAA,EAAO,OAAA,EAAS,MAAM,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAE;AACjD;AAYO,SAAS,aAAA,CACd,UAAA,EACA,OAAA,GAAkB,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,IAAI,EAAA,EAC/E;AACR,EAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,OAAO,CAAA;AACzB,EAAA,CAAA,CAAE,IAAA,GAAO,IAAI,eAAA,CAAgB,EAAE,OAAA,EAAS,UAAA,CAAW,EAAA,EAAI,KAAA,EAAO,UAAA,CAAW,KAAA,EAAO,CAAA,CAAE,QAAA,EAAS;AAC3F,EAAA,OAAO,EAAE,QAAA,EAAS;AACpB;AAGO,SAAS,gBAAA,CAAiB,UAAA,EAA+B,SAAA,GAAY,mBAAA,EAAqB;AAC/F,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,CAAA,WAAA,EAAc,UAAA,CAAW,EAAE,CAAA,CAAA;AAAA,IACjC,SAAA;AAAA,IACA,SAAS,UAAA,CAAW,EAAA;AAAA,IACpB,OAAO,UAAA,CAAW,KAAA;AAAA,IAClB,OAAA,EAAS,CAAA,UAAA,EAAa,UAAA,CAAW,EAAE,CAAA,CAAA;AAAA,IACnC,gBAAA,EAAkB;AAAA,GACpB;AACF;AAOO,SAAS,kBAAA,GAA+C;AAC7D,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACxC,EAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB,GAAA,CAAI,KAAK,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAC,CAAA;AAC3D,EAAA,MAAM,EAAA,GAAK,KAAK,GAAA,CAAI,SAAS,KAAK,GAAA,CAAI,YAAA,CAAa,IAAI,SAAS,CAAA;AAChE,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,OAAO,KAAK,GAAA,CAAI,YAAA,CAAa,IAAI,OAAO,CAAA;AAC/D,EAAA,IAAI,CAAC,EAAA,IAAM,CAAC,KAAA,EAAO,OAAO,IAAA;AAC1B,EAAA,OAAO,eAAA,CAAgB,IAAI,KAAK,CAAA;AAClC;AAEA,SAAS,WAAA,GAAsB;AAC7B,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,WAAW,CAAA;AACxC,EAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAC5B,EAAA,OAAO,UAAU,KAAK,CAAA;AACxB;AAEA,SAAS,SAAS,GAAA,EAAqB;AACrC,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,IAAA,CAAK,KAAM,GAAA,GAAM,CAAA,GAAK,CAAC,CAAC,CAAA;AACrD,EAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAC5B,EAAA,OAAO,SAAA,CAAU,KAAK,CAAA,CAAE,KAAA,CAAM,GAAG,GAAG,CAAA;AACtC;AAEA,SAAS,UAAU,KAAA,EAA2B;AAC5C,EAAA,IAAI,CAAA,GAAI,EAAA;AACR,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,CAAA,IAAK,MAAA,CAAO,aAAa,CAAC,CAAA;AACjD,EAAA,OAAO,IAAA,CAAK,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC1E;AAGO,SAAS,iBAAA,CAAkB,GAAW,CAAA,EAAoB;AAC/D,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,IAAQ,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,GAAI,CAAA,CAAE,WAAW,CAAC,CAAA;AAC3E,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;;;AC9FA,aAAA,EAAA;AA2BO,IAAM,oBAAN,MAA6C;AAAA,EAalD,YAAY,OAAA,EAA0B;AATtC,IAAA,IAAA,CAAQ,QAAA,GAAW,KAAA;AAEnB,IAAA,IAAA,CAAQ,YAA8B,EAAC;AACvC,IAAA,IAAA,CAAQ,SAAA,GAAY,KAAA;AACpB,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAiC;AACzD,IAAA,IAAA,CAAQ,KAAA,GAAoB,MAAA;AAE5B,IAAA,IAAA,CAAQ,KAAA,uBAAY,GAAA,EAAY;AAG9B,IAAA,IAAA,CAAK,IAAA,GAAO,OAAA;AACZ,IAAA,IAAA,CAAK,gBAAgB,OAAA,CAAQ,KAAA;AAAA,EAC/B;AAAA,EAEA,WAAW,MAAA,EAA8B;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAI,KAAK,SAAA,IAAa,IAAA,CAAK,QAAA,IAAY,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,IAAA,CAAK,SAAS,YAAY,CAAA;AAK1B,IAAA,KAAK,OAAO,kCAAkC,CAAA,CAC3C,KAAK,CAAC,EAAE,oBAAmB,KAAM;AAChC,MAAA,IAAI,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,QAAA,EAAU;AACrC,MAAA,IAAA,CAAK,UAAU,kBAAA,CAAmB;AAAA,QAChC,OAAA,EAAS,KAAK,IAAA,CAAK,OAAA;AAAA,QACnB,OAAA,EAAS,KAAK,IAAA,CAAK,SAAA;AAAA,QACnB,KAAA,EAAO,KAAK,IAAA,CAAK,KAAA;AAAA,QACjB,SAAA,EAAW,MAAA;AAAA,QACX,gBAAA,EAAkB,SAAA;AAAA,QAClB,QAAA,EAAU,QAAA;AAAA,QACV,OAAA,EAAS,CAAC,GAAA,KAAQ,IAAA,CAAK,cAAc,GAAG,CAAA;AAAA,QACxC,MAAA,EAAQ,MAAM,IAAA,CAAK,QAAA,EAAS;AAAA,QAC5B,OAAA,EAAS,MAAM,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA;AAAA,QACpC,SAAA,EAAW,KAAK,IAAA,CAAK;AAAA,OACtB,CAAA;AACD,MAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,IACrB,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AACX,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,QAAA,EAAS;AAAA,IACpC,CAAC,CAAA;AAAA,EACL;AAAA;AAAA,EAGQ,QAAA,GAAiB;AACvB,IAAA,IAAI,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,QAAA,EAAU;AACrC,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,OAAO,IAAI,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,SAAS,CAAC,CAAA,cAAA,EAAiB,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,KAAK,CAAC,CAAA,CAAA;AAC/H,IAAA,MAAM,KAAK,IAAI,WAAA,CAAY,KAAK,EAAE,eAAA,EAAiB,OAAO,CAAA;AAC1D,IAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AACV,IAAA,EAAA,CAAG,gBAAA,CAAiB,MAAA,EAAQ,MAAM,IAAA,CAAK,UAAU,CAAA;AACjD,IAAA,EAAA,CAAG,gBAAA,CAAiB,OAAO,CAAC,EAAA,KAAqB,KAAK,aAAA,CAAc,EAAA,CAAG,IAAI,CAAC,CAAA;AAC5E,IAAA,EAAA,CAAG,iBAAiB,OAAA,EAAS,MAAM,IAAA,CAAK,QAAA,CAAS,OAAO,CAAC,CAAA;AAAA,EAC3D;AAAA;AAAA,EAGQ,QAAA,GAAiB;AACvB,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AACjB,IAAA,IAAA,CAAK,SAAS,MAAM,CAAA;AACpB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,CAAC,CAAA;AACtC,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAAA,EAC5C;AAAA,EAEA,KAAK,OAAA,EAA+B;AAClC,IAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACnB,MAAA,IAAA,CAAK,SAAA,CAAU,KAAK,OAAO,CAAA;AAC3B,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EACtB;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,SAAS,IAAA,EAAK;AACnB,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AACf,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AACf,IAAA,IAAA,CAAK,EAAA,GAAK,MAAA;AACV,IAAA,IAAA,CAAK,SAAA,GAAY,KAAA;AACjB,IAAA,IAAA,CAAK,SAAS,QAAQ,CAAA;AAAA,EACxB;AAAA,EAEA,cAAc,QAAA,EAAmD;AAC/D,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,IAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AACnB,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,iBAAA,CAAkB,OAAA,EAAkC,KAAA,EAA+B;AACvF,IAAA,IAAI,UAAU,MAAA,IAAa,CAAC,kBAAkB,KAAA,EAAO,IAAA,CAAK,aAAa,CAAA,EAAG;AAC1E,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,MAAM,IAAI,MAAM,uCAAuC,CAAA;AACzE,IAAA,MAAM,UAA0B,OAAO,OAAA,KAAY,WAAW,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,GAAI,OAAA;AACpF,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA;AAAA,EACzC;AAAA,EAEA,MAAc,QAAQ,OAAA,EAAwC;AAC5D,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,OAAO,IAAI,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,SAAS,CAAC,CAAA,cAAA,EAAiB,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,KAAK,CAAC,CAAA,CAAA;AAC/H,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,KAAA,IAAS,KAAA;AAC7B,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,GAAA,EAAK;AAAA,QACX,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAoB,UAAU,kBAAA,EAAmB;AAAA,QAC5E,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,OAC7B,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,GAAA,EAA4B;AACtD,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAClB,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,IAC1B,CAAA,CAAA,MAAQ;AACN,MAAA;AAAA,IACF;AACA,IAAA,IAAI,QAAA,IAAY,OAAA,IAAW,OAAA,CAAQ,MAAA,KAAW,2BAAA,EAA6B;AACzE,MAAA,MAAM,YAAA,GAAe,MAAA,CAAQ,OAAA,CAAQ,MAAA,EAAmD,gBAAgB,MAAM,CAAA;AAC9G,MAAA,IAAA,CAAK,KAAA,CAAM,IAAI,YAAY,CAAA;AAC3B,MAAAH,4BAAA,CAAa;AAAA,QACX,OAAA,EAAS,OAAA;AAAA,QACT,SAAA,EAAW,OAAA;AAAA,QACX,UAAA,EAAY,SAAA;AAAA,QACZ,MAAA,EAAQ,iBAAA;AAAA,QACR,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,QACpB,MAAA,EAAQ,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,iBAAA,EAAkB;AAAA,QACvD,IAAA,EAAM,EAAE,YAAA;AAAa,OACtB,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,IAAI,QAAA,IAAY,OAAA,IAAW,OAAA,CAAQ,MAAA,KAAW,yBAAA,EAA2B;AACvE,MAAA,MAAM,YAAA,GAAe,MAAA,CAAQ,OAAA,CAAQ,MAAA,EAAmD,gBAAgB,MAAM,CAAA;AAC9G,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,YAAY,CAAA;AAC9B,MAAA,IAAI,IAAA,CAAK,KAAA,CAAM,IAAA,KAAS,CAAA,EAAG;AACzB,QAAAA,4BAAA,CAAa;AAAA,UACX,OAAA,EAAS,OAAA;AAAA,UACT,SAAA,EAAW,OAAA;AAAA,UACX,UAAA,EAAY,SAAA;AAAA,UACZ,MAAA,EAAQ,oBAAA;AAAA,UACR,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB,MAAA,EAAQ,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,oBAAA,EAAqB;AAAA,UAC1D,IAAA,EAAM,EAAE,YAAA;AAAa,SACtB,CAAA;AAAA,MACH;AACA,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA;AAAA,EACzC;AAAA,EAEQ,SAAS,KAAA,EAAyB;AACxC,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,SAAA,EAAW,CAAA,CAAE,KAAK,CAAA;AAAA,EACzC;AACF;AAIO,SAAS,cAAA,CAAe,QAAwB,OAAA,EAA6C;AAClG,EAAA,MAAM,SAAA,GAAY,IAAI,iBAAA,CAAkB,OAAO,CAAA;AAC/C,EAAA,SAAA,CAAU,WAAW,MAAM,CAAA;AAC3B,EAAA,MAAA,CAAO,OAAO,SAAS,CAAA;AACvB,EAAA,SAAA,CAAU,KAAA,EAAM;AAMhB,EAAA,OAAA,CAAA,OAAA,EAAA,CAAA,IAAA,CAAA,OAAA,aAAA,EAAA,EAAA,gBAAA,CAAA,CAAA,CAA+B,IAAA,CAAK,CAAC,EAAE,UAAA,EAAAC,aAAW,KAAM;AACtD,IAAA,MAAM,GAAA,GAAMA,WAAAA,CAAW,CAAC,KAAA,KAAU;AAChC,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,OAAA,EAAS,KAAA;AAAA,QACT,MAAA,EAAQ,8BAAA;AAAA,QACR,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,MAAM,SAAA,GAAY,SAAA,CAAU,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA;AAChD,IAAA,SAAA,CAAU,QAAQ,MAAM;AACtB,MAAA,GAAA,EAAI;AACJ,MAAA,SAAA,EAAU;AAAA,IACZ,CAAA;AAAA,EACF,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,EAEf,CAAC,CAAA;AAED,EAAA,OAAO,SAAA;AACT","file":"sharing.cjs","sourcesContent":["/**\n * In-process registry of activity events. Now backed by the shared bus in\n * `@particle-academy/fancy-auto-common`, so agent activity (here) and flow\n * activity (fancy-flow's FlowRunnerUx) land in ONE stream — presence layers\n * render both. Re-exported under the same names for back-compat.\n */\nexport {\n emitActivity,\n onActivity,\n readActivityHistory,\n resetActivityRegistry,\n} from \"@particle-academy/fancy-auto-common\";\n","/**\n * Session-token utilities. The token is a high-entropy secret; possession\n * grants read/write on the session. We don't HMAC frames — frames carry\n * the token directly (which is fine for in-process / same-origin / TLS\n * transports). For lower-trust transports, host apps can layer signing\n * on top of the BroadcastChannelTransport.\n */\n\nconst TOKEN_BYTES = 24; // 192 bits, base64url-encoded → 32 chars\n\nexport type SessionDescriptor = {\n /** Stable session identifier. Channel name = `fai:share:${id}`. */\n id: string;\n /** Secret token. Treat as a password — anyone with it can read/write. */\n token: string;\n /** Pretty hash for display (first 8 chars of token). */\n display: string;\n};\n\nexport function createSessionDescriptor(): SessionDescriptor {\n const id = randomId(8);\n const token = randomToken();\n return { id, token, display: token.slice(0, 8) };\n}\n\nexport function describeSession(id: string, token: string): SessionDescriptor {\n return { id, token, display: token.slice(0, 8) };\n}\n\n/**\n * Build the shareable URL for the current page (preserves path, adds\n * session+token in the URL FRAGMENT).\n *\n * The token goes in the `#fragment`, never the `?query`: a fragment is never\n * transmitted to the server, so it can't leak through access/proxy logs or the\n * `Referer` header sent to third-party assets on the page. The recipient reads\n * it back with {@link readSessionFromUrl}. (The on-wire relay leg still sends\n * `?token=` today — moving that off the query is the larger transport change.)\n */\nexport function buildShareUrl(\n descriptor: SessionDescriptor,\n baseUrl: string = typeof window !== \"undefined\" ? window.location.href.split(\"?\")[0] : \"\",\n): string {\n const u = new URL(baseUrl);\n u.hash = new URLSearchParams({ session: descriptor.id, token: descriptor.token }).toString();\n return u.toString();\n}\n\n/** Build the JSON config form (suitable for Claude Desktop / Cline / etc.). */\nexport function buildShareConfig(descriptor: SessionDescriptor, transport = \"broadcast-channel\") {\n return {\n name: `whiteboard-${descriptor.id}`,\n transport,\n session: descriptor.id,\n token: descriptor.token,\n channel: `fai:share:${descriptor.id}`,\n protocol_version: \"2025-06-18\",\n };\n}\n\n/**\n * Read session descriptor from the current URL, or null if not a shared link.\n * Prefers the fragment (the non-leaking form {@link buildShareUrl} now emits),\n * falling back to the query string for links minted before that change.\n */\nexport function readSessionFromUrl(): SessionDescriptor | null {\n if (typeof window === \"undefined\") return null;\n const url = new URL(window.location.href);\n const frag = new URLSearchParams(url.hash.replace(/^#/, \"\"));\n const id = frag.get(\"session\") ?? url.searchParams.get(\"session\");\n const token = frag.get(\"token\") ?? url.searchParams.get(\"token\");\n if (!id || !token) return null;\n return describeSession(id, token);\n}\n\nfunction randomToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n crypto.getRandomValues(bytes);\n return base64Url(bytes);\n}\n\nfunction randomId(len: number): string {\n const bytes = new Uint8Array(Math.ceil((len * 3) / 4));\n crypto.getRandomValues(bytes);\n return base64Url(bytes).slice(0, len);\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Constant-time string compare so a mismatched token leaks no timing info. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n","import type { JsonRpcMessage } from \"../mcp/types\";\nimport type { Transport } from \"../mcp/server\";\nimport type { MicroMcpServer } from \"../mcp/server\";\nimport type { RelayChannelHandle } from \"@particle-academy/fancy-cf-relay\";\nimport { constantTimeEqual } from \"./token\";\nimport { emitActivity } from \"../presence/registry\";\n\n/**\n * SseRelayTransport — bridges the in-page MicroMcpServer to a host-app\n * relay broker over Server-Sent Events (inbound) + POST (outbound).\n *\n * Wire model:\n * - Browser opens an EventSource at `${baseUrl}/${sessionId}/events?token=…`.\n * Each `event: mcp` carries one JSON-RPC frame from a remote client.\n * - Browser POSTs JSON-RPC frames to `${baseUrl}/${sessionId}/outbox?token=…`\n * when the local server has a response/notification to send.\n *\n * The host provides the relay endpoint (any HTTP server). See the demo\n * `WhiteboardShareController` for the reference implementation.\n *\n * Token authentication is the host's job — this transport just carries the\n * token in the query string. For lower-trust deployments, layer signing on\n * top by wrapping `send` / `deliverFromRemote`.\n */\nexport type SseRelayOptions = {\n baseUrl: string;\n sessionId: string;\n token: string;\n /** Override fetch (testing / non-browser). Defaults to global fetch. */\n fetch?: typeof fetch;\n};\n\nexport class SseRelayTransport implements Transport {\n private server?: MicroMcpServer;\n private es?: EventSource;\n private channel?: RelayChannelHandle;\n private disposed = false;\n private opts: SseRelayOptions;\n private sendQueue: JsonRpcMessage[] = [];\n private connected = false;\n private listeners = new Set<(state: RelayState) => void>();\n private state: RelayState = \"idle\";\n private expectedToken: string;\n private peers = new Set<string>();\n\n constructor(options: SseRelayOptions) {\n this.opts = options;\n this.expectedToken = options.token;\n }\n\n bindServer(server: MicroMcpServer): void {\n this.server = server;\n }\n\n /** Open the receive channel. Idempotent. */\n start(): void {\n if (this.connected || this.disposed || typeof window === \"undefined\") return;\n this.setState(\"connecting\");\n // Prefer @particle-academy/fancy-cf-relay's adaptive channel (auto SSE↔\n // long-poll with Cloudflare detection) so the receive leg survives a\n // Cloudflare HTTP/3 (QUIC) edge that resets long-lived SSE streams. It's an\n // OPTIONAL peer: if it isn't installed, fall back to a plain EventSource.\n void import(\"@particle-academy/fancy-cf-relay\")\n .then(({ createRelayChannel }) => {\n if (this.connected || this.disposed) return;\n this.channel = createRelayChannel({\n baseUrl: this.opts.baseUrl,\n session: this.opts.sessionId,\n token: this.opts.token,\n transport: \"auto\",\n receiveDirection: \"inbound\",\n sendPath: \"outbox\",\n onFrame: (raw) => this.handleInbound(raw),\n onOpen: () => this.markOpen(),\n onError: () => this.setState(\"error\"),\n fetchImpl: this.opts.fetch,\n });\n this.channel.start();\n })\n .catch(() => {\n if (!this.disposed) this.startSse();\n });\n }\n\n /** Fallback receive leg: a plain EventSource (no fancy-cf-relay installed). */\n private startSse(): void {\n if (this.connected || this.disposed) return;\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;\n const es = new EventSource(url, { withCredentials: false });\n this.es = es;\n es.addEventListener(\"open\", () => this.markOpen());\n es.addEventListener(\"mcp\", (ev: MessageEvent) => this.handleInbound(ev.data));\n es.addEventListener(\"error\", () => this.setState(\"error\")); // EventSource auto-reconnects\n }\n\n /** Mark the channel live + flush any queued outbound frames. */\n private markOpen(): void {\n this.connected = true;\n this.setState(\"open\");\n const queued = this.sendQueue.splice(0);\n for (const msg of queued) this.postOut(msg);\n }\n\n send(message: JsonRpcMessage): void {\n if (!this.connected) {\n this.sendQueue.push(message);\n return;\n }\n this.postOut(message);\n }\n\n close(): void {\n this.disposed = true;\n this.channel?.stop();\n this.channel = undefined;\n this.es?.close();\n this.es = undefined;\n this.connected = false;\n this.setState(\"closed\");\n }\n\n onStateChange(listener: (state: RelayState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * For relays that wrap each frame with auth metadata: hosts can call this\n * directly when a frame arrives via a non-SSE path. The transport will\n * dispatch it to the bound server.\n */\n async deliverFromRemote(payload: JsonRpcMessage | string, token?: string): Promise<void> {\n if (token !== undefined && !constantTimeEqual(token, this.expectedToken)) return;\n if (!this.server) throw new Error(\"SseRelayTransport has no bound server\");\n const message: JsonRpcMessage = typeof payload === \"string\" ? JSON.parse(payload) : payload;\n await this.server.receive(this, message);\n }\n\n private async postOut(message: JsonRpcMessage): Promise<void> {\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;\n const f = this.opts.fetch ?? fetch;\n try {\n await f(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"accept\": \"application/json\" },\n body: JSON.stringify(message),\n });\n } catch {\n // Drop — relay errors are surfaced via state change separately.\n }\n }\n\n private async handleInbound(raw: string): Promise<void> {\n if (!this.server) return;\n let message: JsonRpcMessage;\n try {\n message = JSON.parse(raw);\n } catch {\n return;\n }\n if (\"method\" in message && message.method === \"notifications/peer_joined\") {\n const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n this.peers.add(subscriberId);\n emitActivity({\n agentId: \"agent\",\n agentName: \"Agent\",\n agentColor: \"#a855f7\",\n action: \"agent_connected\",\n timestamp: Date.now(),\n target: { kind: \"navigation\", label: \"Agent connected\" },\n meta: { subscriberId },\n });\n return;\n }\n if (\"method\" in message && message.method === \"notifications/peer_left\") {\n const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n this.peers.delete(subscriberId);\n if (this.peers.size === 0) {\n emitActivity({\n agentId: \"agent\",\n agentName: \"Agent\",\n agentColor: \"#a855f7\",\n action: \"agent_disconnected\",\n timestamp: Date.now(),\n target: { kind: \"navigation\", label: \"Agent disconnected\" },\n meta: { subscriberId },\n });\n }\n return;\n }\n await this.server.receive(this, message);\n }\n\n private setState(state: RelayState): void {\n this.state = state;\n for (const l of this.listeners) l(state);\n }\n}\n\nexport type RelayState = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport function attachSseRelay(server: MicroMcpServer, options: SseRelayOptions): SseRelayTransport {\n const transport = new SseRelayTransport(options);\n transport.bindServer(server);\n server.attach(transport);\n transport.start();\n\n // Forward in-process agent activity events out over the relay so external\n // subscribers can render presence indicators in real time. Uses a dynamic\n // import so the relay doesn't hard-depend on the presence module if it's\n // tree-shaken out.\n import(\"../presence/registry\").then(({ onActivity }) => {\n const off = onActivity((event) => {\n transport.send({\n jsonrpc: \"2.0\",\n method: \"notifications/agent_activity\",\n params: event as any,\n });\n });\n // Tear down the subscription when the transport closes.\n const origClose = transport.close.bind(transport);\n transport.close = () => {\n off();\n origClose();\n };\n }).catch(() => {\n // Presence module unavailable — silently no-op (relay still works).\n });\n\n return transport;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/presence/registry.ts","../src/sharing/token.ts","../src/sharing/sse-relay.ts"],"names":["emitActivity","onActivity","readActivityHistory","resetActivityRegistry"],"mappings":"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","file":"sharing.cjs","sourcesContent":["/**\n * In-process registry of activity events. Now backed by the shared bus in\n * `@particle-academy/fancy-auto-common`, so agent activity (here) and flow\n * activity (fancy-flow's FlowRunnerUx) land in ONE stream — presence layers\n * render both. Re-exported under the same names for back-compat.\n */\nexport {\n emitActivity,\n onActivity,\n readActivityHistory,\n resetActivityRegistry,\n} from \"@particle-academy/fancy-auto-common\";\n","/**\n * Session-token utilities. The token is a high-entropy secret; possession\n * grants read/write on the session. We don't HMAC frames — frames carry\n * the token directly (which is fine for in-process / same-origin / TLS\n * transports). For lower-trust transports, host apps can layer signing\n * on top of the BroadcastChannelTransport.\n */\n\nconst TOKEN_BYTES = 24; // 192 bits, base64url-encoded → 32 chars\n\nexport type SessionDescriptor = {\n /** Stable session identifier. Channel name = `fai:share:${id}`. */\n id: string;\n /** Secret token. Treat as a password — anyone with it can read/write. */\n token: string;\n /** Pretty hash for display (first 8 chars of token). */\n display: string;\n};\n\nexport function createSessionDescriptor(): SessionDescriptor {\n const id = randomId(8);\n const token = randomToken();\n return { id, token, display: token.slice(0, 8) };\n}\n\nexport function describeSession(id: string, token: string): SessionDescriptor {\n return { id, token, display: token.slice(0, 8) };\n}\n\n/**\n * Build the shareable URL for the current page (preserves path, adds\n * session+token in the URL FRAGMENT).\n *\n * The token goes in the `#fragment`, never the `?query`: a fragment is never\n * transmitted to the server, so it can't leak through access/proxy logs or the\n * `Referer` header sent to third-party assets on the page. The recipient reads\n * it back with {@link readSessionFromUrl}. (The on-wire relay leg still sends\n * `?token=` today — moving that off the query is the larger transport change.)\n */\nexport function buildShareUrl(\n descriptor: SessionDescriptor,\n baseUrl: string = typeof window !== \"undefined\" ? window.location.href.split(\"?\")[0] : \"\",\n): string {\n const u = new URL(baseUrl);\n u.hash = new URLSearchParams({ session: descriptor.id, token: descriptor.token }).toString();\n return u.toString();\n}\n\n/** Build the JSON config form (suitable for Claude Desktop / Cline / etc.). */\nexport function buildShareConfig(descriptor: SessionDescriptor, transport = \"broadcast-channel\") {\n return {\n name: `whiteboard-${descriptor.id}`,\n transport,\n session: descriptor.id,\n token: descriptor.token,\n channel: `fai:share:${descriptor.id}`,\n protocol_version: \"2025-06-18\",\n };\n}\n\n/**\n * Read session descriptor from the current URL, or null if not a shared link.\n * Prefers the fragment (the non-leaking form {@link buildShareUrl} now emits),\n * falling back to the query string for links minted before that change.\n */\nexport function readSessionFromUrl(): SessionDescriptor | null {\n if (typeof window === \"undefined\") return null;\n const url = new URL(window.location.href);\n const frag = new URLSearchParams(url.hash.replace(/^#/, \"\"));\n const id = frag.get(\"session\") ?? url.searchParams.get(\"session\");\n const token = frag.get(\"token\") ?? url.searchParams.get(\"token\");\n if (!id || !token) return null;\n return describeSession(id, token);\n}\n\nfunction randomToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n crypto.getRandomValues(bytes);\n return base64Url(bytes);\n}\n\nfunction randomId(len: number): string {\n const bytes = new Uint8Array(Math.ceil((len * 3) / 4));\n crypto.getRandomValues(bytes);\n return base64Url(bytes).slice(0, len);\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Constant-time string compare so a mismatched token leaks no timing info. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n","import type { JsonRpcMessage } from \"../mcp/types\";\nimport type { Transport } from \"../mcp/server\";\nimport type { MicroMcpServer } from \"../mcp/server\";\nimport type { RelayChannelHandle } from \"@particle-academy/fancy-cf-relay\";\nimport { constantTimeEqual } from \"./token\";\nimport { emitActivity } from \"../presence/registry\";\n\n/**\n * SseRelayTransport — bridges the in-page MicroMcpServer to a host-app\n * relay broker over Server-Sent Events (inbound) + POST (outbound).\n *\n * Wire model:\n * - Browser opens an EventSource at `${baseUrl}/${sessionId}/events?token=…`.\n * Each `event: mcp` carries one JSON-RPC frame from a remote client.\n * - Browser POSTs JSON-RPC frames to `${baseUrl}/${sessionId}/outbox?token=…`\n * when the local server has a response/notification to send.\n *\n * The host provides the relay endpoint (any HTTP server). See the demo\n * `WhiteboardShareController` for the reference implementation.\n *\n * Token authentication is the host's job — this transport just carries the\n * token in the query string. For lower-trust deployments, layer signing on\n * top by wrapping `send` / `deliverFromRemote`.\n */\nexport type SseRelayOptions = {\n baseUrl: string;\n sessionId: string;\n token: string;\n /** Override fetch (testing / non-browser). Defaults to global fetch. */\n fetch?: typeof fetch;\n};\n\nexport class SseRelayTransport implements Transport {\n private server?: MicroMcpServer;\n private es?: EventSource;\n private channel?: RelayChannelHandle;\n private disposed = false;\n private opts: SseRelayOptions;\n private sendQueue: JsonRpcMessage[] = [];\n private connected = false;\n private listeners = new Set<(state: RelayState) => void>();\n private state: RelayState = \"idle\";\n private expectedToken: string;\n private peers = new Set<string>();\n private peerListeners = new Set<(count: number) => void>();\n\n constructor(options: SseRelayOptions) {\n this.opts = options;\n this.expectedToken = options.token;\n }\n\n bindServer(server: MicroMcpServer): void {\n this.server = server;\n }\n\n /** Open the receive channel. Idempotent. */\n start(): void {\n if (this.connected || this.disposed || typeof window === \"undefined\") return;\n this.setState(\"connecting\");\n // Prefer @particle-academy/fancy-cf-relay's adaptive channel (auto SSE↔\n // long-poll with Cloudflare detection) so the receive leg survives a\n // Cloudflare HTTP/3 (QUIC) edge that resets long-lived SSE streams. It's an\n // OPTIONAL peer: if it isn't installed, fall back to a plain EventSource.\n void import(\"@particle-academy/fancy-cf-relay\")\n .then(({ createRelayChannel }) => {\n if (this.connected || this.disposed) return;\n this.channel = createRelayChannel({\n baseUrl: this.opts.baseUrl,\n session: this.opts.sessionId,\n token: this.opts.token,\n transport: \"auto\",\n receiveDirection: \"inbound\",\n sendPath: \"outbox\",\n onFrame: (raw) => this.handleInbound(raw),\n onOpen: () => this.markOpen(),\n onError: () => this.setState(\"error\"),\n fetchImpl: this.opts.fetch,\n });\n this.channel.start();\n })\n .catch(() => {\n if (!this.disposed) this.startSse();\n });\n }\n\n /** Fallback receive leg: a plain EventSource (no fancy-cf-relay installed). */\n private startSse(): void {\n if (this.connected || this.disposed) return;\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;\n const es = new EventSource(url, { withCredentials: false });\n this.es = es;\n es.addEventListener(\"open\", () => this.markOpen());\n es.addEventListener(\"mcp\", (ev: MessageEvent) => this.handleInbound(ev.data));\n es.addEventListener(\"error\", () => this.setState(\"error\")); // EventSource auto-reconnects\n }\n\n /** Mark the channel live + flush any queued outbound frames. */\n private markOpen(): void {\n this.connected = true;\n this.setState(\"open\");\n const queued = this.sendQueue.splice(0);\n for (const msg of queued) this.postOut(msg);\n }\n\n send(message: JsonRpcMessage): void {\n if (!this.connected) {\n this.sendQueue.push(message);\n return;\n }\n this.postOut(message);\n }\n\n close(): void {\n this.disposed = true;\n this.channel?.stop();\n this.channel = undefined;\n this.es?.close();\n this.es = undefined;\n this.connected = false;\n this.peers.clear();\n this.notifyPeers();\n this.setState(\"closed\");\n }\n\n onStateChange(listener: (state: RelayState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => this.listeners.delete(listener);\n }\n\n /** How many remote peers (agents) are currently attached to this session. */\n peerCount(): number {\n return this.peers.size;\n }\n\n /**\n * Subscribe to peer arrivals/departures. Fires immediately with the current\n * count.\n *\n * This is the ONLY honest answer to \"is an agent actually here?\".\n * `onStateChange` reports the BROWSER's own channel to the relay, which opens\n * the instant sharing starts — so a UI keyed on it announces \"Agent is\n * driving\" to a human who has not yet handed the link to anybody, and can\n * never tell them when one really arrives.\n */\n onPeersChange(listener: (count: number) => void): () => void {\n this.peerListeners.add(listener);\n listener(this.peers.size);\n return () => this.peerListeners.delete(listener);\n }\n\n private notifyPeers(): void {\n for (const l of this.peerListeners) l(this.peers.size);\n }\n\n /**\n * For relays that wrap each frame with auth metadata: hosts can call this\n * directly when a frame arrives via a non-SSE path. The transport will\n * dispatch it to the bound server.\n */\n async deliverFromRemote(payload: JsonRpcMessage | string, token?: string): Promise<void> {\n if (token !== undefined && !constantTimeEqual(token, this.expectedToken)) return;\n if (!this.server) throw new Error(\"SseRelayTransport has no bound server\");\n const message: JsonRpcMessage = typeof payload === \"string\" ? JSON.parse(payload) : payload;\n await this.server.receive(this, message);\n }\n\n private async postOut(message: JsonRpcMessage): Promise<void> {\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;\n const f = this.opts.fetch ?? fetch;\n try {\n await f(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"accept\": \"application/json\" },\n body: JSON.stringify(message),\n });\n } catch {\n // Drop — relay errors are surfaced via state change separately.\n }\n }\n\n private async handleInbound(raw: string): Promise<void> {\n if (!this.server) return;\n let message: JsonRpcMessage;\n try {\n message = JSON.parse(raw);\n } catch {\n return;\n }\n if (\"method\" in message && message.method === \"notifications/peer_joined\") {\n const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n this.peers.add(subscriberId);\n this.notifyPeers();\n emitActivity({\n agentId: \"agent\",\n agentName: \"Agent\",\n agentColor: \"#a855f7\",\n action: \"agent_connected\",\n timestamp: Date.now(),\n target: { kind: \"navigation\", label: \"Agent connected\" },\n meta: { subscriberId },\n });\n return;\n }\n if (\"method\" in message && message.method === \"notifications/peer_left\") {\n const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n this.peers.delete(subscriberId);\n this.notifyPeers();\n if (this.peers.size === 0) {\n emitActivity({\n agentId: \"agent\",\n agentName: \"Agent\",\n agentColor: \"#a855f7\",\n action: \"agent_disconnected\",\n timestamp: Date.now(),\n target: { kind: \"navigation\", label: \"Agent disconnected\" },\n meta: { subscriberId },\n });\n }\n return;\n }\n await this.server.receive(this, message);\n }\n\n private setState(state: RelayState): void {\n this.state = state;\n for (const l of this.listeners) l(state);\n }\n}\n\nexport type RelayState = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport function attachSseRelay(server: MicroMcpServer, options: SseRelayOptions): SseRelayTransport {\n const transport = new SseRelayTransport(options);\n transport.bindServer(server);\n server.attach(transport);\n transport.start();\n\n // Forward in-process agent activity events out over the relay so external\n // subscribers can render presence indicators in real time. Uses a dynamic\n // import so the relay doesn't hard-depend on the presence module if it's\n // tree-shaken out.\n import(\"../presence/registry\").then(({ onActivity }) => {\n const off = onActivity((event) => {\n transport.send({\n jsonrpc: \"2.0\",\n method: \"notifications/agent_activity\",\n params: event as any,\n });\n });\n // Tear down the subscription when the transport closes.\n const origClose = transport.close.bind(transport);\n transport.close = () => {\n off();\n origClose();\n };\n }).catch(() => {\n // Presence module unavailable — silently no-op (relay still works).\n });\n\n return transport;\n}\n"]}
|
package/dist/sharing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './chunk-IJ6JX5VC.js';
|
|
2
|
-
export { SseRelayTransport, attachSseRelay, buildShareConfig, buildShareUrl, constantTimeEqual, createSessionDescriptor, describeSession, readSessionFromUrl } from './chunk-
|
|
2
|
+
export { SseRelayTransport, attachSseRelay, buildShareConfig, buildShareUrl, constantTimeEqual, createSessionDescriptor, describeSession, readSessionFromUrl } from './chunk-JDCB2NMM.js';
|
|
3
3
|
import './chunk-C3TYI5TJ.js';
|
|
4
4
|
//# sourceMappingURL=sharing.js.map
|
|
5
5
|
//# sourceMappingURL=sharing.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/agent-integrations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "MCP-driven agent presence in collab sessions: per-session micro-MCP server, pluggable bridges to fancy-* packages, and agent UX components (panel + on-canvas cursor).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -197,6 +197,16 @@
|
|
|
197
197
|
"default": "./dist/bridges-features.cjs"
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
|
+
"./bridges/passkeys": {
|
|
201
|
+
"import": {
|
|
202
|
+
"types": "./dist/bridges/passkeys.d.ts",
|
|
203
|
+
"default": "./dist/bridges-passkeys.js"
|
|
204
|
+
},
|
|
205
|
+
"require": {
|
|
206
|
+
"types": "./dist/bridges/passkeys.d.cts",
|
|
207
|
+
"default": "./dist/bridges-passkeys.cjs"
|
|
208
|
+
}
|
|
209
|
+
},
|
|
200
210
|
"./bridges/doc": {
|
|
201
211
|
"import": {
|
|
202
212
|
"types": "./dist/bridges/doc.d.ts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sharing/token.ts","../src/sharing/sse-relay.ts"],"names":[],"mappings":"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","file":"chunk-N6RLSLOF.js","sourcesContent":["/**\n * Session-token utilities. The token is a high-entropy secret; possession\n * grants read/write on the session. We don't HMAC frames — frames carry\n * the token directly (which is fine for in-process / same-origin / TLS\n * transports). For lower-trust transports, host apps can layer signing\n * on top of the BroadcastChannelTransport.\n */\n\nconst TOKEN_BYTES = 24; // 192 bits, base64url-encoded → 32 chars\n\nexport type SessionDescriptor = {\n /** Stable session identifier. Channel name = `fai:share:${id}`. */\n id: string;\n /** Secret token. Treat as a password — anyone with it can read/write. */\n token: string;\n /** Pretty hash for display (first 8 chars of token). */\n display: string;\n};\n\nexport function createSessionDescriptor(): SessionDescriptor {\n const id = randomId(8);\n const token = randomToken();\n return { id, token, display: token.slice(0, 8) };\n}\n\nexport function describeSession(id: string, token: string): SessionDescriptor {\n return { id, token, display: token.slice(0, 8) };\n}\n\n/**\n * Build the shareable URL for the current page (preserves path, adds\n * session+token in the URL FRAGMENT).\n *\n * The token goes in the `#fragment`, never the `?query`: a fragment is never\n * transmitted to the server, so it can't leak through access/proxy logs or the\n * `Referer` header sent to third-party assets on the page. The recipient reads\n * it back with {@link readSessionFromUrl}. (The on-wire relay leg still sends\n * `?token=` today — moving that off the query is the larger transport change.)\n */\nexport function buildShareUrl(\n descriptor: SessionDescriptor,\n baseUrl: string = typeof window !== \"undefined\" ? window.location.href.split(\"?\")[0] : \"\",\n): string {\n const u = new URL(baseUrl);\n u.hash = new URLSearchParams({ session: descriptor.id, token: descriptor.token }).toString();\n return u.toString();\n}\n\n/** Build the JSON config form (suitable for Claude Desktop / Cline / etc.). */\nexport function buildShareConfig(descriptor: SessionDescriptor, transport = \"broadcast-channel\") {\n return {\n name: `whiteboard-${descriptor.id}`,\n transport,\n session: descriptor.id,\n token: descriptor.token,\n channel: `fai:share:${descriptor.id}`,\n protocol_version: \"2025-06-18\",\n };\n}\n\n/**\n * Read session descriptor from the current URL, or null if not a shared link.\n * Prefers the fragment (the non-leaking form {@link buildShareUrl} now emits),\n * falling back to the query string for links minted before that change.\n */\nexport function readSessionFromUrl(): SessionDescriptor | null {\n if (typeof window === \"undefined\") return null;\n const url = new URL(window.location.href);\n const frag = new URLSearchParams(url.hash.replace(/^#/, \"\"));\n const id = frag.get(\"session\") ?? url.searchParams.get(\"session\");\n const token = frag.get(\"token\") ?? url.searchParams.get(\"token\");\n if (!id || !token) return null;\n return describeSession(id, token);\n}\n\nfunction randomToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n crypto.getRandomValues(bytes);\n return base64Url(bytes);\n}\n\nfunction randomId(len: number): string {\n const bytes = new Uint8Array(Math.ceil((len * 3) / 4));\n crypto.getRandomValues(bytes);\n return base64Url(bytes).slice(0, len);\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Constant-time string compare so a mismatched token leaks no timing info. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n","import type { JsonRpcMessage } from \"../mcp/types\";\nimport type { Transport } from \"../mcp/server\";\nimport type { MicroMcpServer } from \"../mcp/server\";\nimport type { RelayChannelHandle } from \"@particle-academy/fancy-cf-relay\";\nimport { constantTimeEqual } from \"./token\";\nimport { emitActivity } from \"../presence/registry\";\n\n/**\n * SseRelayTransport — bridges the in-page MicroMcpServer to a host-app\n * relay broker over Server-Sent Events (inbound) + POST (outbound).\n *\n * Wire model:\n * - Browser opens an EventSource at `${baseUrl}/${sessionId}/events?token=…`.\n * Each `event: mcp` carries one JSON-RPC frame from a remote client.\n * - Browser POSTs JSON-RPC frames to `${baseUrl}/${sessionId}/outbox?token=…`\n * when the local server has a response/notification to send.\n *\n * The host provides the relay endpoint (any HTTP server). See the demo\n * `WhiteboardShareController` for the reference implementation.\n *\n * Token authentication is the host's job — this transport just carries the\n * token in the query string. For lower-trust deployments, layer signing on\n * top by wrapping `send` / `deliverFromRemote`.\n */\nexport type SseRelayOptions = {\n baseUrl: string;\n sessionId: string;\n token: string;\n /** Override fetch (testing / non-browser). Defaults to global fetch. */\n fetch?: typeof fetch;\n};\n\nexport class SseRelayTransport implements Transport {\n private server?: MicroMcpServer;\n private es?: EventSource;\n private channel?: RelayChannelHandle;\n private disposed = false;\n private opts: SseRelayOptions;\n private sendQueue: JsonRpcMessage[] = [];\n private connected = false;\n private listeners = new Set<(state: RelayState) => void>();\n private state: RelayState = \"idle\";\n private expectedToken: string;\n private peers = new Set<string>();\n\n constructor(options: SseRelayOptions) {\n this.opts = options;\n this.expectedToken = options.token;\n }\n\n bindServer(server: MicroMcpServer): void {\n this.server = server;\n }\n\n /** Open the receive channel. Idempotent. */\n start(): void {\n if (this.connected || this.disposed || typeof window === \"undefined\") return;\n this.setState(\"connecting\");\n // Prefer @particle-academy/fancy-cf-relay's adaptive channel (auto SSE↔\n // long-poll with Cloudflare detection) so the receive leg survives a\n // Cloudflare HTTP/3 (QUIC) edge that resets long-lived SSE streams. It's an\n // OPTIONAL peer: if it isn't installed, fall back to a plain EventSource.\n void import(\"@particle-academy/fancy-cf-relay\")\n .then(({ createRelayChannel }) => {\n if (this.connected || this.disposed) return;\n this.channel = createRelayChannel({\n baseUrl: this.opts.baseUrl,\n session: this.opts.sessionId,\n token: this.opts.token,\n transport: \"auto\",\n receiveDirection: \"inbound\",\n sendPath: \"outbox\",\n onFrame: (raw) => this.handleInbound(raw),\n onOpen: () => this.markOpen(),\n onError: () => this.setState(\"error\"),\n fetchImpl: this.opts.fetch,\n });\n this.channel.start();\n })\n .catch(() => {\n if (!this.disposed) this.startSse();\n });\n }\n\n /** Fallback receive leg: a plain EventSource (no fancy-cf-relay installed). */\n private startSse(): void {\n if (this.connected || this.disposed) return;\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;\n const es = new EventSource(url, { withCredentials: false });\n this.es = es;\n es.addEventListener(\"open\", () => this.markOpen());\n es.addEventListener(\"mcp\", (ev: MessageEvent) => this.handleInbound(ev.data));\n es.addEventListener(\"error\", () => this.setState(\"error\")); // EventSource auto-reconnects\n }\n\n /** Mark the channel live + flush any queued outbound frames. */\n private markOpen(): void {\n this.connected = true;\n this.setState(\"open\");\n const queued = this.sendQueue.splice(0);\n for (const msg of queued) this.postOut(msg);\n }\n\n send(message: JsonRpcMessage): void {\n if (!this.connected) {\n this.sendQueue.push(message);\n return;\n }\n this.postOut(message);\n }\n\n close(): void {\n this.disposed = true;\n this.channel?.stop();\n this.channel = undefined;\n this.es?.close();\n this.es = undefined;\n this.connected = false;\n this.setState(\"closed\");\n }\n\n onStateChange(listener: (state: RelayState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * For relays that wrap each frame with auth metadata: hosts can call this\n * directly when a frame arrives via a non-SSE path. The transport will\n * dispatch it to the bound server.\n */\n async deliverFromRemote(payload: JsonRpcMessage | string, token?: string): Promise<void> {\n if (token !== undefined && !constantTimeEqual(token, this.expectedToken)) return;\n if (!this.server) throw new Error(\"SseRelayTransport has no bound server\");\n const message: JsonRpcMessage = typeof payload === \"string\" ? JSON.parse(payload) : payload;\n await this.server.receive(this, message);\n }\n\n private async postOut(message: JsonRpcMessage): Promise<void> {\n const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;\n const f = this.opts.fetch ?? fetch;\n try {\n await f(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"accept\": \"application/json\" },\n body: JSON.stringify(message),\n });\n } catch {\n // Drop — relay errors are surfaced via state change separately.\n }\n }\n\n private async handleInbound(raw: string): Promise<void> {\n if (!this.server) return;\n let message: JsonRpcMessage;\n try {\n message = JSON.parse(raw);\n } catch {\n return;\n }\n if (\"method\" in message && message.method === \"notifications/peer_joined\") {\n const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n this.peers.add(subscriberId);\n emitActivity({\n agentId: \"agent\",\n agentName: \"Agent\",\n agentColor: \"#a855f7\",\n action: \"agent_connected\",\n timestamp: Date.now(),\n target: { kind: \"navigation\", label: \"Agent connected\" },\n meta: { subscriberId },\n });\n return;\n }\n if (\"method\" in message && message.method === \"notifications/peer_left\") {\n const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n this.peers.delete(subscriberId);\n if (this.peers.size === 0) {\n emitActivity({\n agentId: \"agent\",\n agentName: \"Agent\",\n agentColor: \"#a855f7\",\n action: \"agent_disconnected\",\n timestamp: Date.now(),\n target: { kind: \"navigation\", label: \"Agent disconnected\" },\n meta: { subscriberId },\n });\n }\n return;\n }\n await this.server.receive(this, message);\n }\n\n private setState(state: RelayState): void {\n this.state = state;\n for (const l of this.listeners) l(state);\n }\n}\n\nexport type RelayState = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport function attachSseRelay(server: MicroMcpServer, options: SseRelayOptions): SseRelayTransport {\n const transport = new SseRelayTransport(options);\n transport.bindServer(server);\n server.attach(transport);\n transport.start();\n\n // Forward in-process agent activity events out over the relay so external\n // subscribers can render presence indicators in real time. Uses a dynamic\n // import so the relay doesn't hard-depend on the presence module if it's\n // tree-shaken out.\n import(\"../presence/registry\").then(({ onActivity }) => {\n const off = onActivity((event) => {\n transport.send({\n jsonrpc: \"2.0\",\n method: \"notifications/agent_activity\",\n params: event as any,\n });\n });\n // Tear down the subscription when the transport closes.\n const origClose = transport.close.bind(transport);\n transport.close = () => {\n off();\n origClose();\n };\n }).catch(() => {\n // Presence module unavailable — silently no-op (relay still works).\n });\n\n return transport;\n}\n"]}
|