@particle-academy/agent-integrations 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +45 -0
  2. package/dist/bridges-flow.js +340 -3
  3. package/dist/bridges-flow.js.map +1 -1
  4. package/dist/{chunk-E4AICMFZ.js → chunk-5XELJIJR.js} +3 -3
  5. package/dist/chunk-5XELJIJR.js.map +1 -0
  6. package/dist/{chunk-6LTKCNLF.js → chunk-AFUULW5E.js} +3 -34
  7. package/dist/chunk-AFUULW5E.js.map +1 -0
  8. package/dist/chunk-G6N2TQVO.js +34 -0
  9. package/dist/chunk-G6N2TQVO.js.map +1 -0
  10. package/dist/chunk-IJ6JX5VC.js +3 -0
  11. package/dist/chunk-IJ6JX5VC.js.map +1 -0
  12. package/dist/{chunk-JMYPUAFH.js → chunk-LVQXIUJH.js} +2 -2
  13. package/dist/{chunk-JMYPUAFH.js.map → chunk-LVQXIUJH.js.map} +1 -1
  14. package/dist/chunk-OIX2ANFS.js +386 -0
  15. package/dist/chunk-OIX2ANFS.js.map +1 -0
  16. package/dist/chunk-ZHAK2DQR.js +289 -0
  17. package/dist/chunk-ZHAK2DQR.js.map +1 -0
  18. package/dist/components/SharedWhiteboard/index.d.cts +55 -0
  19. package/dist/components/SharedWhiteboard/index.d.ts +55 -0
  20. package/dist/components-shared-whiteboard.cjs +1533 -0
  21. package/dist/components-shared-whiteboard.cjs.map +1 -0
  22. package/dist/components-shared-whiteboard.js +285 -0
  23. package/dist/components-shared-whiteboard.js.map +1 -0
  24. package/dist/index.cjs +249 -1287
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +4 -55
  27. package/dist/index.d.ts +4 -55
  28. package/dist/index.js +9 -563
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcp.js +2 -1
  31. package/dist/relay-server/index.d.cts +134 -0
  32. package/dist/relay-server/index.d.ts +134 -0
  33. package/dist/relay-server-cli.cjs +483 -0
  34. package/dist/relay-server-cli.cjs.map +1 -0
  35. package/dist/relay-server-cli.js +98 -0
  36. package/dist/relay-server-cli.js.map +1 -0
  37. package/dist/relay-server.cjs +389 -0
  38. package/dist/relay-server.cjs.map +1 -0
  39. package/dist/relay-server.js +3 -0
  40. package/dist/relay-server.js.map +1 -0
  41. package/dist/sharing/index.d.cts +2 -34
  42. package/dist/sharing/index.d.ts +2 -34
  43. package/dist/sharing.js +2 -1
  44. package/dist/sheets-adapter.cjs +1 -1
  45. package/dist/sheets-adapter.cjs.map +1 -1
  46. package/dist/sheets-adapter.d.cts +11 -7
  47. package/dist/sheets-adapter.d.ts +11 -7
  48. package/dist/sheets-adapter.js +1 -1
  49. package/dist/token-CrJF76oH.d.cts +34 -0
  50. package/dist/token-CrJF76oH.d.ts +34 -0
  51. package/docs/relay-server.md +126 -0
  52. package/package.json +66 -7
  53. package/dist/chunk-6LTKCNLF.js.map +0 -1
  54. package/dist/chunk-E4AICMFZ.js.map +0 -1
  55. package/dist/chunk-N3H4DXY5.js +0 -342
  56. package/dist/chunk-N3H4DXY5.js.map +0 -1
@@ -0,0 +1,285 @@
1
+ import { ShareControls, AgentCursor, AgentActivityHighlight, AgentPanel } from './chunk-ZHAK2DQR.js';
2
+ import { createSessionDescriptor, attachSseRelay } from './chunk-LVQXIUJH.js';
3
+ import { attachInProcess } from './chunk-AFUULW5E.js';
4
+ import { registerWhiteboardBridge } from './chunk-3KSZNGNW.js';
5
+ import './chunk-GQ7XXK7G.js';
6
+ import './chunk-52S7XYZK.js';
7
+ import './chunk-JU2N4KK6.js';
8
+ import { MicroMcpServer } from './chunk-4KAIV6OD.js';
9
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
10
+ import { Board, Connector, Shape, StickyNote, CursorLayer } from '@particle-academy/fancy-whiteboard';
11
+ import { jsxs, jsx } from 'react/jsx-runtime';
12
+
13
+ var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
14
+ function SharedWhiteboard({
15
+ initialNotes = [],
16
+ initialShapes = [],
17
+ initialConnectors = [],
18
+ initialStrokes = [],
19
+ initialViewport = { x: 0, y: 0, zoom: 1 },
20
+ agent = DEFAULT_AGENT,
21
+ shareBaseUrl = "/whiteboard-share",
22
+ onRegisterSession,
23
+ showAgentPanel = true,
24
+ showShareControls = true,
25
+ broadcastEdits = true,
26
+ height = 640,
27
+ header,
28
+ className,
29
+ style
30
+ }) {
31
+ const [notes, setNotes] = useState(initialNotes);
32
+ const [shapes, setShapes] = useState(initialShapes);
33
+ const [connectors, setConnectors] = useState(initialConnectors);
34
+ const [strokes, setStrokes] = useState(initialStrokes);
35
+ const [viewport, setViewport] = useState(initialViewport);
36
+ const [agentCursor, setAgentCursor] = useState(null);
37
+ const [activity, setActivity] = useState([]);
38
+ const [highlight, setHighlight] = useState(null);
39
+ const stateRefs = useRef({ notes, shapes, connectors, strokes, viewport });
40
+ useEffect(() => {
41
+ stateRefs.current = { notes, shapes, connectors, strokes, viewport };
42
+ }, [notes, shapes, connectors, strokes, viewport]);
43
+ const serverRef = useRef(null);
44
+ const inProcRef = useRef(null);
45
+ const bridgeRef = useRef(null);
46
+ useEffect(() => {
47
+ const server = new MicroMcpServer({
48
+ info: { name: "shared-whiteboard", version: "0.2.0" },
49
+ instructions: "Collaborative whiteboard. Use whiteboard_* tools to read or modify the board."
50
+ });
51
+ bridgeRef.current = registerWhiteboardBridge(server, {
52
+ adapter: {
53
+ getNotes: () => stateRefs.current.notes,
54
+ setNotes: (next) => setNotes(typeof next === "function" ? next : () => next),
55
+ getShapes: () => stateRefs.current.shapes,
56
+ setShapes: (next) => setShapes(typeof next === "function" ? next : () => next),
57
+ getConnectors: () => stateRefs.current.connectors,
58
+ setConnectors: (next) => setConnectors(typeof next === "function" ? next : () => next),
59
+ getStrokes: () => stateRefs.current.strokes,
60
+ setStrokes: (next) => setStrokes(typeof next === "function" ? next : () => next),
61
+ getViewport: () => stateRefs.current.viewport,
62
+ setViewport,
63
+ setAgentCursor
64
+ },
65
+ agent
66
+ });
67
+ inProcRef.current = attachInProcess(server);
68
+ serverRef.current = server;
69
+ const off = inProcRef.current.onServerMessage((msg) => {
70
+ if (msg?.id !== void 0 && "result" in msg && msg.result?.structuredContent?.id) {
71
+ const id = msg.result.structuredContent.id;
72
+ requestAnimationFrame(() => pulseFor(id));
73
+ }
74
+ });
75
+ return () => {
76
+ off();
77
+ bridgeRef.current?.dispose();
78
+ bridgeRef.current = null;
79
+ if (inProcRef.current) server.detach(inProcRef.current);
80
+ };
81
+ }, []);
82
+ const pulseFor = (id) => {
83
+ const n = stateRefs.current.notes.find((x) => x.id === id);
84
+ if (n) return setHighlight({ pulseKey: Date.now(), bounds: { x: n.x, y: n.y, width: n.width, height: n.height } });
85
+ const s = stateRefs.current.shapes.find((x) => x.id === id);
86
+ if (s) return setHighlight({ pulseKey: Date.now(), bounds: { x: s.x, y: s.y, width: s.width, height: s.height } });
87
+ };
88
+ const log = useCallback((entry) => {
89
+ setActivity((all) => [...all.slice(-200), { id: `a_${Date.now()}_${all.length}`, at: Date.now(), ...entry }]);
90
+ }, []);
91
+ const [session, setSession] = useState(null);
92
+ const [relayState, setRelayState] = useState("idle");
93
+ const sseRef = useRef(null);
94
+ const logEsRef = useRef(null);
95
+ const startShare = async () => {
96
+ if (session || !serverRef.current || !shareBaseUrl) return;
97
+ const desc = createSessionDescriptor();
98
+ try {
99
+ if (onRegisterSession) {
100
+ await onRegisterSession(desc);
101
+ } else {
102
+ const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? "";
103
+ const reg = await fetch(`${shareBaseUrl}/register`, {
104
+ method: "POST",
105
+ headers: { "content-type": "application/json", "x-csrf-token": csrf, accept: "application/json" },
106
+ body: JSON.stringify({ session: desc.id, token: desc.token })
107
+ });
108
+ if (!reg.ok) throw new Error(`registration failed (HTTP ${reg.status})`);
109
+ }
110
+ } catch (e) {
111
+ log({ kind: "error", source: "share", text: e instanceof Error ? e.message : String(e) });
112
+ return;
113
+ }
114
+ const relay = attachSseRelay(serverRef.current, {
115
+ baseUrl: shareBaseUrl,
116
+ sessionId: desc.id,
117
+ token: desc.token
118
+ });
119
+ sseRef.current = relay;
120
+ relay.onStateChange(setRelayState);
121
+ const es = new EventSource(`${shareBaseUrl}/${desc.id}/events?token=${desc.token}&direction=inbound`);
122
+ es.addEventListener("mcp", (ev) => {
123
+ try {
124
+ const frame = JSON.parse(ev.data);
125
+ if (frame.method === "notifications/peer_joined") {
126
+ setAgentCursor((c) => c ?? { userId: agent.id, name: agent.name, color: agent.color, x: 60, y: 60 });
127
+ log({ kind: "info", source: "presence", text: `${agent.name ?? "Agent"} connected` });
128
+ return;
129
+ }
130
+ if (frame.method === "notifications/peer_left") {
131
+ setAgentCursor(null);
132
+ log({ kind: "info", source: "presence", text: `${agent.name ?? "Agent"} disconnected` });
133
+ return;
134
+ }
135
+ if (frame.method === "notifications/agent_message") {
136
+ log({ kind: "message", source: agent.name ?? "Agent", text: String(frame.params?.text ?? "") });
137
+ } else if (frame.method === "notifications/agent_status") {
138
+ log({ kind: "info", source: agent.name ?? "Agent", text: String(frame.params?.text ?? "") });
139
+ } else if (frame.method?.startsWith("notifications/")) {
140
+ } else {
141
+ log({ kind: "tool", source: "remote", text: `\u2190 ${frame.method ?? `id:${frame.id}`}`, detail: frame });
142
+ }
143
+ } catch {
144
+ }
145
+ });
146
+ logEsRef.current = es;
147
+ setSession(desc);
148
+ log({ kind: "info", source: "share", text: `Sharing started \xB7 session ${desc.id}` });
149
+ };
150
+ const stopShare = async () => {
151
+ if (!session) return;
152
+ const desc = session;
153
+ setSession(null);
154
+ logEsRef.current?.close();
155
+ logEsRef.current = null;
156
+ if (sseRef.current && serverRef.current) serverRef.current.detach(sseRef.current);
157
+ sseRef.current = null;
158
+ setRelayState("closed");
159
+ if (shareBaseUrl) {
160
+ const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? "";
161
+ await fetch(`${shareBaseUrl}/${desc.id}/unregister?token=${encodeURIComponent(desc.token)}`, {
162
+ method: "POST",
163
+ headers: { "x-csrf-token": csrf, accept: "application/json" }
164
+ }).catch(() => {
165
+ });
166
+ }
167
+ log({ kind: "info", source: "share", text: "Sharing stopped." });
168
+ };
169
+ const lastBroadcastRef = useRef(0);
170
+ useEffect(() => {
171
+ if (!broadcastEdits || !sseRef.current || !session) return;
172
+ const now = Date.now();
173
+ if (now - lastBroadcastRef.current < 80) return;
174
+ lastBroadcastRef.current = now;
175
+ sseRef.current.send({
176
+ jsonrpc: "2.0",
177
+ method: "notifications/state_update",
178
+ params: { notes, shapes, connectors, viewport, ts: now }
179
+ });
180
+ }, [notes, shapes, connectors, viewport, session, broadcastEdits]);
181
+ const handleSubmit = (text) => {
182
+ if (!sseRef.current) {
183
+ log({ kind: "error", source: "you", text: "Start a shared session first." });
184
+ return;
185
+ }
186
+ sseRef.current.send({
187
+ jsonrpc: "2.0",
188
+ method: "notifications/user_message",
189
+ params: { text, ts: Date.now() }
190
+ });
191
+ log({ kind: "message", source: "You", text });
192
+ };
193
+ const cursors = useMemo(() => [], []);
194
+ const statusText = (() => {
195
+ switch (relayState) {
196
+ case "open":
197
+ return "live";
198
+ case "connecting":
199
+ return "connecting\u2026";
200
+ case "error":
201
+ return "error";
202
+ case "closed":
203
+ return "closed";
204
+ default:
205
+ return void 0;
206
+ }
207
+ })();
208
+ return /* @__PURE__ */ jsxs("div", { className: ["fai-shared-whiteboard", className ?? ""].filter(Boolean).join(" "), style, children: [
209
+ header,
210
+ showShareControls && shareBaseUrl !== null && /* @__PURE__ */ jsx("div", { className: "fai-shared-whiteboard__controls", children: /* @__PURE__ */ jsx(ShareControls, { session, onStart: startShare, onStop: stopShare, status: statusText }) }),
211
+ /* @__PURE__ */ jsxs(
212
+ "div",
213
+ {
214
+ className: "fai-shared-whiteboard__layout",
215
+ style: {
216
+ display: "grid",
217
+ gap: 16,
218
+ gridTemplateColumns: showAgentPanel ? "1fr 360px" : "1fr"
219
+ },
220
+ children: [
221
+ /* @__PURE__ */ jsx(
222
+ "div",
223
+ {
224
+ className: "fai-shared-whiteboard__board",
225
+ style: {
226
+ position: "relative",
227
+ overflow: "hidden",
228
+ borderRadius: 12,
229
+ border: "1px solid #e4e4e7",
230
+ background: "radial-gradient(circle at 1px 1px, rgba(0,0,0,0.07) 1px, transparent 0)",
231
+ backgroundSize: "20px 20px",
232
+ height
233
+ },
234
+ children: /* @__PURE__ */ jsxs(Board, { viewport, onViewportChange: setViewport, style: { width: "100%", height: "100%" }, children: [
235
+ connectors.map((c) => {
236
+ const a = resolveCenter(c.from, notes, shapes);
237
+ const b = resolveCenter(c.to, notes, shapes);
238
+ if (!a || !b) return null;
239
+ return /* @__PURE__ */ jsx(Connector, { from: a, to: b, color: c.color ?? "#64748b" }, c.id);
240
+ }),
241
+ shapes.map((s) => /* @__PURE__ */ jsx(Shape, { item: s, onChange: (next) => setShapes((all) => all.map((x) => x.id === next.id ? next : x)) }, s.id)),
242
+ notes.map((n) => /* @__PURE__ */ jsx(StickyNote, { item: n, onChange: (next) => setNotes((all) => all.map((x) => x.id === next.id ? next : x)) }, n.id)),
243
+ /* @__PURE__ */ jsx(CursorLayer, { cursors }),
244
+ agentCursor && /* @__PURE__ */ jsx(AgentCursor, { x: agentCursor.x, y: agentCursor.y, name: agentCursor.name, color: agentCursor.color }),
245
+ highlight && /* @__PURE__ */ jsx(
246
+ AgentActivityHighlight,
247
+ {
248
+ x: highlight.bounds.x,
249
+ y: highlight.bounds.y,
250
+ width: highlight.bounds.width,
251
+ height: highlight.bounds.height,
252
+ color: agent.color ?? "#a855f7",
253
+ pulseKey: highlight.pulseKey
254
+ }
255
+ )
256
+ ] })
257
+ }
258
+ ),
259
+ showAgentPanel && /* @__PURE__ */ jsx("div", { style: { height }, children: /* @__PURE__ */ jsx(
260
+ AgentPanel,
261
+ {
262
+ agent,
263
+ activity,
264
+ onSubmit: handleSubmit
265
+ }
266
+ ) })
267
+ ]
268
+ }
269
+ )
270
+ ] });
271
+ }
272
+ function resolveCenter(ref, notes, shapes) {
273
+ if (typeof ref === "string") {
274
+ const n = notes.find((x) => x.id === ref);
275
+ if (n) return { x: n.x + n.width / 2, y: n.y + n.height / 2 };
276
+ const s = shapes.find((x) => x.id === ref);
277
+ if (s) return { x: s.x + s.width / 2, y: s.y + s.height / 2 };
278
+ return null;
279
+ }
280
+ return ref;
281
+ }
282
+
283
+ export { SharedWhiteboard };
284
+ //# sourceMappingURL=components-shared-whiteboard.js.map
285
+ //# sourceMappingURL=components-shared-whiteboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/SharedWhiteboard/SharedWhiteboard.tsx"],"names":[],"mappings":"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","file":"components-shared-whiteboard.js","sourcesContent":["import { type CSSProperties, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n Board,\n StickyNote,\n Connector,\n Shape,\n CursorLayer,\n type StickyNoteItem,\n type ShapeItem,\n type ConnectorItem,\n type Stroke,\n type RemoteCursor,\n type Viewport,\n} from \"@particle-academy/fancy-whiteboard\";\nimport { MicroMcpServer, type Transport } from \"../../mcp/server\";\nimport { attachInProcess, type InProcessTransport } from \"../../mcp/transports/in-process\";\nimport { attachSseRelay, type RelayState, type SseRelayTransport } from \"../../sharing/sse-relay\";\nimport { createSessionDescriptor, type SessionDescriptor } from \"../../sharing/token\";\nimport { registerWhiteboardBridge } from \"../../bridges/whiteboard\";\nimport type { Bridge } from \"../../bridges/types\";\nimport { ShareControls } from \"../ShareControls\";\nimport { AgentPanel, type AgentActivity } from \"../AgentPanel\";\nimport { AgentCursor } from \"../AgentCursor\";\nimport { AgentActivityHighlight } from \"../AgentActivityHighlight\";\n\nexport type SharedWhiteboardProps = {\n /** Initial board contents. */\n initialNotes?: StickyNoteItem[];\n initialShapes?: ShapeItem[];\n initialConnectors?: ConnectorItem[];\n initialStrokes?: Stroke[];\n initialViewport?: Viewport;\n\n /** Agent identity displayed in the panel + cursor. */\n agent?: { id: string; name?: string; color?: string };\n\n /**\n * Where the relay HTTP endpoints live. The host app implements these (see\n * docs/relay-protocol.md). Pass `null` to disable sharing — the board\n * still works locally with the in-process MCP server.\n */\n shareBaseUrl?: string | null;\n\n /**\n * Optional callback to register a new session token with the host's\n * relay broker. Receives `{ session, token }` and should return after\n * registration. Defaults to POSTing JSON to `${shareBaseUrl}/register`.\n */\n onRegisterSession?: (descriptor: SessionDescriptor) => Promise<void>;\n\n /** Show the agent panel. Default true. */\n showAgentPanel?: boolean;\n\n /** Show share controls. Default true. */\n showShareControls?: boolean;\n\n /** Auto-broadcast local edits as `notifications/state_update`. Default true. */\n broadcastEdits?: boolean;\n\n /** Pixel height of the board area. Default 640. */\n height?: number;\n\n /** Header content rendered above the board. */\n header?: ReactNode;\n\n className?: string;\n style?: CSSProperties;\n};\n\nconst DEFAULT_AGENT = { id: \"agent\", name: \"Agent\", color: \"#a855f7\" };\n\n/**\n * SharedWhiteboard — drop-in component that bundles every piece of the\n * \"agent-collaborative whiteboard\" UX: board with all primitives, in-page\n * MCP server, share controls, agent panel, presence cursor, activity\n * highlight, and outbound state broadcast.\n *\n * Most apps only need this one component. For deeper customization, swap\n * it for the lower-level primitives (Board, MicroMcpServer, ShareControls).\n */\nexport function SharedWhiteboard({\n initialNotes = [],\n initialShapes = [],\n initialConnectors = [],\n initialStrokes = [],\n initialViewport = { x: 0, y: 0, zoom: 1 },\n agent = DEFAULT_AGENT,\n shareBaseUrl = \"/whiteboard-share\",\n onRegisterSession,\n showAgentPanel = true,\n showShareControls = true,\n broadcastEdits = true,\n height = 640,\n header,\n className,\n style,\n}: SharedWhiteboardProps) {\n // Board state\n const [notes, setNotes] = useState<StickyNoteItem[]>(initialNotes);\n const [shapes, setShapes] = useState<ShapeItem[]>(initialShapes);\n const [connectors, setConnectors] = useState<ConnectorItem[]>(initialConnectors);\n const [strokes, setStrokes] = useState<Stroke[]>(initialStrokes);\n const [viewport, setViewport] = useState<Viewport>(initialViewport);\n const [agentCursor, setAgentCursor] = useState<RemoteCursor | null>(null);\n\n // Agent UX state\n const [activity, setActivity] = useState<AgentActivity[]>([]);\n const [highlight, setHighlight] = useState<{ pulseKey: number; bounds: { x: number; y: number; width: number; height: number } } | null>(null);\n\n const stateRefs = useRef({ notes, shapes, connectors, strokes, viewport });\n useEffect(() => { stateRefs.current = { notes, shapes, connectors, strokes, viewport }; }, [notes, shapes, connectors, strokes, viewport]);\n\n // MCP server + bridge\n const serverRef = useRef<MicroMcpServer | null>(null);\n const inProcRef = useRef<InProcessTransport | null>(null);\n const bridgeRef = useRef<Bridge | null>(null);\n\n useEffect(() => {\n const server = new MicroMcpServer({\n info: { name: \"shared-whiteboard\", version: \"0.2.0\" },\n instructions: \"Collaborative whiteboard. Use whiteboard_* tools to read or modify the board.\",\n });\n bridgeRef.current = registerWhiteboardBridge(server, {\n adapter: {\n getNotes: () => stateRefs.current.notes,\n setNotes: (next) => setNotes(typeof next === \"function\" ? next : () => next),\n getShapes: () => stateRefs.current.shapes,\n setShapes: (next) => setShapes(typeof next === \"function\" ? next : () => next),\n getConnectors: () => stateRefs.current.connectors,\n setConnectors: (next) => setConnectors(typeof next === \"function\" ? next : () => next),\n getStrokes: () => stateRefs.current.strokes,\n setStrokes: (next) => setStrokes(typeof next === \"function\" ? next : () => next),\n getViewport: () => stateRefs.current.viewport,\n setViewport,\n setAgentCursor,\n },\n agent,\n });\n inProcRef.current = attachInProcess(server);\n serverRef.current = server;\n\n // Pulse a highlight whenever a tool call returns a structured id.\n const off = inProcRef.current.onServerMessage((msg: any) => {\n if (msg?.id !== undefined && \"result\" in msg && msg.result?.structuredContent?.id) {\n const id = msg.result.structuredContent.id;\n requestAnimationFrame(() => pulseFor(id));\n }\n });\n return () => {\n off();\n bridgeRef.current?.dispose();\n bridgeRef.current = null;\n if (inProcRef.current) server.detach(inProcRef.current);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const pulseFor = (id: string) => {\n const n = stateRefs.current.notes.find((x) => x.id === id);\n if (n) return setHighlight({ pulseKey: Date.now(), bounds: { x: n.x, y: n.y, width: n.width, height: n.height } });\n const s = stateRefs.current.shapes.find((x) => x.id === id);\n if (s) return setHighlight({ pulseKey: Date.now(), bounds: { x: s.x, y: s.y, width: s.width, height: s.height } });\n };\n\n const log = useCallback((entry: Omit<AgentActivity, \"id\" | \"at\">) => {\n setActivity((all) => [...all.slice(-200), { id: `a_${Date.now()}_${all.length}`, at: Date.now(), ...entry }]);\n }, []);\n\n // Sharing\n const [session, setSession] = useState<SessionDescriptor | null>(null);\n const [relayState, setRelayState] = useState<RelayState>(\"idle\");\n const sseRef = useRef<SseRelayTransport | null>(null);\n const logEsRef = useRef<EventSource | null>(null);\n\n const startShare = async () => {\n if (session || !serverRef.current || !shareBaseUrl) return;\n const desc = createSessionDescriptor();\n\n try {\n if (onRegisterSession) {\n await onRegisterSession(desc);\n } else {\n const csrf = (document.querySelector('meta[name=\"csrf-token\"]') as HTMLMetaElement | null)?.content ?? \"\";\n const reg = await fetch(`${shareBaseUrl}/register`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-csrf-token\": csrf, accept: \"application/json\" },\n body: JSON.stringify({ session: desc.id, token: desc.token }),\n });\n if (!reg.ok) throw new Error(`registration failed (HTTP ${reg.status})`);\n }\n } catch (e) {\n log({ kind: \"error\", source: \"share\", text: e instanceof Error ? e.message : String(e) });\n return;\n }\n\n const relay = attachSseRelay(serverRef.current, {\n baseUrl: shareBaseUrl,\n sessionId: desc.id,\n token: desc.token,\n });\n sseRef.current = relay;\n relay.onStateChange(setRelayState);\n\n // Activity log mirror — does NOT call deliverFromRemote (relay does it).\n const es = new EventSource(`${shareBaseUrl}/${desc.id}/events?token=${desc.token}&direction=inbound`);\n es.addEventListener(\"mcp\", (ev: MessageEvent) => {\n try {\n const frame = JSON.parse(ev.data);\n if (frame.method === \"notifications/peer_joined\") {\n // External agent just opened an outbound subscription. Surface it\n // so the human can see \"an agent is connecting\" before any tools\n // arrive. The agent itself should follow up with set_agent_cursor\n // immediately, but in case it doesn't, drop a placeholder cursor\n // at the canvas origin so presence is visible right away.\n setAgentCursor((c) => c ?? { userId: agent.id, name: agent.name, color: agent.color, x: 60, y: 60 });\n log({ kind: \"info\", source: \"presence\", text: `${agent.name ?? \"Agent\"} connected` });\n return;\n }\n if (frame.method === \"notifications/peer_left\") {\n setAgentCursor(null);\n log({ kind: \"info\", source: \"presence\", text: `${agent.name ?? \"Agent\"} disconnected` });\n return;\n }\n if (frame.method === \"notifications/agent_message\") {\n log({ kind: \"message\", source: agent.name ?? \"Agent\", text: String(frame.params?.text ?? \"\") });\n } else if (frame.method === \"notifications/agent_status\") {\n log({ kind: \"info\", source: agent.name ?? \"Agent\", text: String(frame.params?.text ?? \"\") });\n } else if (frame.method?.startsWith(\"notifications/\")) {\n // ignore other notifications in the feed\n } else {\n log({ kind: \"tool\", source: \"remote\", text: `← ${frame.method ?? `id:${frame.id}`}`, detail: frame });\n }\n } catch {\n /* noop */\n }\n });\n logEsRef.current = es;\n\n setSession(desc);\n log({ kind: \"info\", source: \"share\", text: `Sharing started · session ${desc.id}` });\n };\n\n const stopShare = async () => {\n if (!session) return;\n const desc = session;\n setSession(null);\n logEsRef.current?.close();\n logEsRef.current = null;\n if (sseRef.current && serverRef.current) serverRef.current.detach(sseRef.current);\n sseRef.current = null;\n setRelayState(\"closed\");\n if (shareBaseUrl) {\n const csrf = (document.querySelector('meta[name=\"csrf-token\"]') as HTMLMetaElement | null)?.content ?? \"\";\n await fetch(`${shareBaseUrl}/${desc.id}/unregister?token=${encodeURIComponent(desc.token)}`, {\n method: \"POST\",\n headers: { \"x-csrf-token\": csrf, accept: \"application/json\" },\n }).catch(() => {});\n }\n log({ kind: \"info\", source: \"share\", text: \"Sharing stopped.\" });\n };\n\n // Outbound state broadcast — every state change pushes a notification on\n // the relay (capped to ~12 Hz) so external agents see human edits live.\n const lastBroadcastRef = useRef(0);\n useEffect(() => {\n if (!broadcastEdits || !sseRef.current || !session) return;\n const now = Date.now();\n if (now - lastBroadcastRef.current < 80) return;\n lastBroadcastRef.current = now;\n sseRef.current.send({\n jsonrpc: \"2.0\",\n method: \"notifications/state_update\",\n params: { notes, shapes, connectors, viewport, ts: now },\n });\n }, [notes, shapes, connectors, viewport, session, broadcastEdits]);\n\n const handleSubmit = (text: string) => {\n if (!sseRef.current) {\n log({ kind: \"error\", source: \"you\", text: \"Start a shared session first.\" });\n return;\n }\n sseRef.current.send({\n jsonrpc: \"2.0\",\n method: \"notifications/user_message\",\n params: { text, ts: Date.now() },\n });\n log({ kind: \"message\", source: \"You\", text });\n };\n\n // The agent cursor is rendered by <AgentCursor> below — keep it OUT of\n // the cursors array so we don't double-render it via CursorLayer.\n // (CursorLayer is reserved for human participants once relay sync lands.)\n const cursors: RemoteCursor[] = useMemo(() => [], []);\n const statusText = (() => {\n switch (relayState) {\n case \"open\": return \"live\";\n case \"connecting\": return \"connecting…\";\n case \"error\": return \"error\";\n case \"closed\": return \"closed\";\n default: return undefined;\n }\n })();\n\n return (\n <div className={[\"fai-shared-whiteboard\", className ?? \"\"].filter(Boolean).join(\" \")} style={style}>\n {header}\n {showShareControls && shareBaseUrl !== null && (\n <div className=\"fai-shared-whiteboard__controls\">\n <ShareControls session={session} onStart={startShare} onStop={stopShare} status={statusText} />\n </div>\n )}\n <div\n className=\"fai-shared-whiteboard__layout\"\n style={{\n display: \"grid\",\n gap: 16,\n gridTemplateColumns: showAgentPanel ? \"1fr 360px\" : \"1fr\",\n }}\n >\n <div\n className=\"fai-shared-whiteboard__board\"\n style={{\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 12,\n border: \"1px solid #e4e4e7\",\n background:\n \"radial-gradient(circle at 1px 1px, rgba(0,0,0,0.07) 1px, transparent 0)\",\n backgroundSize: \"20px 20px\",\n height,\n }}\n >\n <Board viewport={viewport} onViewportChange={setViewport} style={{ width: \"100%\", height: \"100%\" }}>\n {connectors.map((c) => {\n const a = resolveCenter(c.from, notes, shapes);\n const b = resolveCenter(c.to, notes, shapes);\n if (!a || !b) return null;\n return <Connector key={c.id} from={a} to={b} color={c.color ?? \"#64748b\"} />;\n })}\n {shapes.map((s) => (\n <Shape key={s.id} item={s} onChange={(next) => setShapes((all) => all.map((x) => (x.id === next.id ? next : x)))} />\n ))}\n {notes.map((n) => (\n <StickyNote key={n.id} item={n} onChange={(next) => setNotes((all) => all.map((x) => (x.id === next.id ? next : x)))} />\n ))}\n <CursorLayer cursors={cursors} />\n {agentCursor && (\n <AgentCursor x={agentCursor.x} y={agentCursor.y} name={agentCursor.name} color={agentCursor.color} />\n )}\n {highlight && (\n <AgentActivityHighlight\n x={highlight.bounds.x}\n y={highlight.bounds.y}\n width={highlight.bounds.width}\n height={highlight.bounds.height}\n color={agent.color ?? \"#a855f7\"}\n pulseKey={highlight.pulseKey}\n />\n )}\n </Board>\n </div>\n {showAgentPanel && (\n <div style={{ height }}>\n <AgentPanel\n agent={agent}\n activity={activity}\n onSubmit={handleSubmit}\n />\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction resolveCenter(\n ref: ConnectorItem[\"from\"],\n notes: StickyNoteItem[],\n shapes: ShapeItem[],\n): { x: number; y: number } | null {\n if (typeof ref === \"string\") {\n const n = notes.find((x) => x.id === ref);\n if (n) return { x: n.x + n.width / 2, y: n.y + n.height / 2 };\n const s = shapes.find((x) => x.id === ref);\n if (s) return { x: s.x + s.width / 2, y: s.y + s.height / 2 };\n return null;\n }\n return ref;\n}\n\n// Internal-import safety\ntype _UseTransport = Transport;\n"]}