@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
package/dist/index.cjs CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- var fancyWhiteboard = require('@particle-academy/fancy-whiteboard');
6
5
 
7
6
  var __defProp = Object.defineProperty;
8
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -309,895 +308,11 @@ function extractMeta(result) {
309
308
  return void 0;
310
309
  }
311
310
 
312
- // src/undo/undo-stack.ts
313
- var stacks = /* @__PURE__ */ new Map();
314
- var CAP = 200;
315
- function getStack(agentId) {
316
- let s = stacks.get(agentId);
317
- if (!s) {
318
- s = { past: [], future: [] };
319
- stacks.set(agentId, s);
320
- }
321
- return s;
322
- }
323
- function pushUndoEntry(agentId, entry) {
324
- const s = getStack(agentId);
325
- s.past.push(entry);
326
- if (s.past.length > CAP) s.past.splice(0, s.past.length - CAP);
327
- s.future.length = 0;
328
- }
329
- async function undoOne(agentId) {
330
- const s = getStack(agentId);
331
- const entry = s.past.pop();
332
- if (!entry) return null;
333
- await entry.undo();
334
- s.future.push(entry);
335
- return entry;
336
- }
337
- async function redoOne(agentId) {
338
- const s = getStack(agentId);
339
- const entry = s.future.pop();
340
- if (!entry) return null;
341
- await entry.redo();
342
- s.past.push(entry);
343
- return entry;
344
- }
345
- function readHistory(agentId) {
346
- return getStack(agentId).past.slice();
347
- }
348
- function clearStack(agentId) {
349
- stacks.delete(agentId);
350
- }
351
- function resetAllUndoStacks() {
352
- stacks.clear();
353
- }
354
-
355
- // src/undo/undo-tools.ts
356
- var installedHosts = /* @__PURE__ */ new WeakSet();
357
- function ensureUndoToolsRegistered(host, options = {}) {
358
- if (installedHosts.has(host)) return;
359
- installedHosts.add(host);
360
- registerUndoTools(host, options);
361
- }
362
- function registerUndoTools(host, options = {}) {
363
- const defaultAgent = options.defaultAgentId ?? "agent";
364
- const disposers = [];
365
- const agentOf = (args) => typeof args?.agentId === "string" ? args.agentId : defaultAgent;
366
- disposers.push(
367
- host.registerTool(
368
- {
369
- name: "agent_undo",
370
- description: "Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.",
371
- inputSchema: {
372
- type: "object",
373
- properties: { agentId: { type: "string" } },
374
- additionalProperties: false
375
- }
376
- },
377
- async (args) => {
378
- const entry = await undoOne(agentOf(args));
379
- if (!entry) return errorResult("Nothing to undo.");
380
- return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });
381
- }
382
- )
383
- );
384
- disposers.push(
385
- host.registerTool(
386
- {
387
- name: "agent_redo",
388
- description: "Redo the most recently undone action.",
389
- inputSchema: {
390
- type: "object",
391
- properties: { agentId: { type: "string" } },
392
- additionalProperties: false
393
- }
394
- },
395
- async (args) => {
396
- const entry = await redoOne(agentOf(args));
397
- if (!entry) return errorResult("Nothing to redo.");
398
- return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });
399
- }
400
- )
401
- );
402
- disposers.push(
403
- host.registerTool(
404
- {
405
- name: "agent_history",
406
- description: "List the agent's undo stack (oldest first). Useful for understanding what's reversible.",
407
- inputSchema: {
408
- type: "object",
409
- properties: { agentId: { type: "string" } },
410
- additionalProperties: false
411
- }
412
- },
413
- async (args) => {
414
- const history2 = readHistory(agentOf(args)).map(serialize);
415
- const text = history2.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join("\n");
416
- return textResult(text || "(empty)", history2);
417
- }
418
- )
419
- );
420
- return () => disposers.forEach((d) => d());
421
- }
422
- function serialize(entry) {
423
- return {
424
- timestamp: entry.timestamp,
425
- bridgeId: entry.bridgeId,
426
- action: entry.action,
427
- label: entry.label
428
- };
429
- }
430
-
431
- // src/bridges/whiteboard.ts
432
- var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
433
- var VALID_SHAPES = ["rect", "rounded-rect", "ellipse", "diamond", "triangle", "line", "arrow", "text"];
434
- var num = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback ?? 0;
435
- var str = (v, fallback = "") => typeof v === "string" ? v : fallback;
436
- var bool = (v, fallback = false) => typeof v === "boolean" ? v : fallback;
437
- var newId = (prefix) => `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
438
- function registerWhiteboardBridge(host, options) {
439
- const { adapter } = options;
440
- const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
441
- const disposers = [];
442
- ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
443
- const wbTarget = (args, result) => ({
444
- kind: "whiteboard",
445
- elementId: result?.structuredContent?.id ?? args?.id
446
- });
447
- const reg = (name, description, inputProperties, required, handler, resolveTarget) => {
448
- const wrapped = async (args) => {
449
- try {
450
- return await handler(args);
451
- } catch (e) {
452
- return errorResult(e instanceof Error ? e.message : String(e));
453
- }
454
- };
455
- const final = resolveTarget ? wrapToolWithActivity(wrapped, {
456
- toolName: name,
457
- agent: { id: agent.id, name: agent.name, color: agent.color },
458
- kind: "whiteboard",
459
- resolveTarget: ({ args, result }) => resolveTarget(args, result)
460
- }) : wrapped;
461
- disposers.push(
462
- host.registerTool(
463
- {
464
- name,
465
- description,
466
- inputSchema: {
467
- type: "object",
468
- properties: inputProperties,
469
- required,
470
- additionalProperties: false
471
- }
472
- },
473
- final
474
- )
475
- );
476
- };
477
- reg("whiteboard_get_state", "Get the full board state: viewport, all items, strokes.", {}, [], () => {
478
- const state = {
479
- viewport: adapter.getViewport(),
480
- notes: adapter.getNotes(),
481
- shapes: adapter.getShapes(),
482
- connectors: adapter.getConnectors(),
483
- strokes: adapter.getStrokes()
484
- };
485
- return textResult(JSON.stringify(state, null, 2), state);
486
- });
487
- reg("whiteboard_list_items", "List notes, shapes, and connectors with id, kind, and bounds.", {}, [], () => {
488
- const items = [];
489
- for (const n of adapter.getNotes()) {
490
- items.push({
491
- id: n.id,
492
- kind: "sticky",
493
- summary: `"${(n.text ?? "").slice(0, 40)}" @(${Math.round(n.x)},${Math.round(n.y)}) ${n.width}\xD7${n.height}`
494
- });
495
- }
496
- for (const s of adapter.getShapes()) {
497
- items.push({
498
- id: s.id,
499
- kind: `shape:${s.shape}`,
500
- summary: `${s.text ? `"${s.text}" ` : ""}@(${Math.round(s.x)},${Math.round(s.y)}) ${s.width}\xD7${s.height}`
501
- });
502
- }
503
- for (const c of adapter.getConnectors()) {
504
- items.push({ id: c.id, kind: "connector", summary: `from=${JSON.stringify(c.from)} to=${JSON.stringify(c.to)}` });
505
- }
506
- return textResult(items.map((i) => `${i.kind} ${i.id}: ${i.summary}`).join("\n") || "(empty board)", items);
507
- });
508
- reg(
509
- "whiteboard_get_item",
510
- "Get a single item (sticky / shape / connector) by id.",
511
- { id: { type: "string" } },
512
- ["id"],
513
- (args) => {
514
- const id = str(args.id);
515
- const all = [...adapter.getNotes(), ...adapter.getShapes(), ...adapter.getConnectors()];
516
- const found = all.find((x) => x.id === id);
517
- if (!found) return errorResult(`No item with id ${id}`);
518
- return textResult(JSON.stringify(found, null, 2), found);
519
- }
520
- );
521
- reg(
522
- "whiteboard_add_sticky",
523
- "Add a sticky note. Position is in world coordinates.",
524
- {
525
- x: { type: "number" },
526
- y: { type: "number" },
527
- text: { type: "string" },
528
- width: { type: "number" },
529
- height: { type: "number" },
530
- color: { type: "string", description: "CSS color, e.g. #fde68a" }
531
- },
532
- ["x", "y"],
533
- async (args) => {
534
- const x = num(args.x);
535
- const y = num(args.y);
536
- const width = num(args.width, 180);
537
- const height = num(args.height, 140);
538
- const note = {
539
- id: newId("n"),
540
- kind: "sticky",
541
- x,
542
- y,
543
- width,
544
- height,
545
- text: str(args.text),
546
- color: typeof args.color === "string" ? args.color : "#fde68a",
547
- authorId: agent.id
548
- };
549
- adapter.setNotes((all) => [...all, note]);
550
- pushUndoEntry(agent.id, {
551
- timestamp: Date.now(),
552
- bridgeId: "whiteboard",
553
- action: "whiteboard_add_sticky",
554
- label: `Added sticky ${note.id}`,
555
- undo: () => adapter.setNotes((all) => all.filter((n) => n.id !== note.id)),
556
- redo: () => adapter.setNotes((all) => [...all, note])
557
- });
558
- return textResult(`Added sticky ${note.id}`, note);
559
- },
560
- wbTarget
561
- );
562
- reg(
563
- "whiteboard_stream_text",
564
- "Type text into a sticky note character-by-character so the human can read it forming. The tool returns once streaming finishes.",
565
- {
566
- id: { type: "string" },
567
- text: { type: "string" },
568
- cps: { type: "number", description: "Characters per second. Default 25." },
569
- append: { type: "boolean", description: "Append to existing text instead of replacing. Default false." }
570
- },
571
- ["id", "text"],
572
- async (args) => {
573
- const id = str(args.id);
574
- const target = str(args.text);
575
- const cps = Math.max(1, num(args.cps, 25));
576
- const append = bool(args.append);
577
- const startNote = adapter.getNotes().find((n) => n.id === id);
578
- if (!startNote) return errorResult(`No sticky with id ${id}`);
579
- const base = append ? startNote.text ?? "" : "";
580
- const interval = Math.max(8, Math.round(1e3 / cps));
581
- for (let i = 0; i <= target.length; i++) {
582
- const nextText = base + target.slice(0, i);
583
- adapter.setNotes((all) => all.map((n) => n.id === id ? { ...n, text: nextText } : n));
584
- if (i < target.length) await new Promise((r) => setTimeout(r, interval));
585
- }
586
- return textResult(`Streamed ${target.length} chars to ${id}`, { id, text: base + target });
587
- }
588
- );
589
- reg(
590
- "whiteboard_update_sticky",
591
- "Update fields on a sticky note. Only provided fields are changed.",
592
- {
593
- id: { type: "string" },
594
- x: { type: "number" },
595
- y: { type: "number" },
596
- width: { type: "number" },
597
- height: { type: "number" },
598
- text: { type: "string" },
599
- color: { type: "string" }
600
- },
601
- ["id"],
602
- async (args) => {
603
- const id = str(args.id);
604
- const existing = adapter.getNotes().find((n) => n.id === id);
605
- if (!existing) return errorResult(`No sticky with id ${id}`);
606
- const nextX = args.x !== void 0 ? num(args.x) : existing.x;
607
- const nextY = args.y !== void 0 ? num(args.y) : existing.y;
608
- const nextW = args.width !== void 0 ? num(args.width) : existing.width;
609
- const nextH = args.height !== void 0 ? num(args.height) : existing.height;
610
- let updated = null;
611
- adapter.setNotes(
612
- (all) => all.map((n) => {
613
- if (n.id !== id) return n;
614
- updated = {
615
- ...n,
616
- x: nextX,
617
- y: nextY,
618
- width: nextW,
619
- height: nextH,
620
- ...args.text !== void 0 ? { text: str(args.text) } : {},
621
- ...args.color !== void 0 ? { color: str(args.color) } : {}
622
- };
623
- return updated;
624
- })
625
- );
626
- return textResult(`Updated sticky ${id}`, updated);
627
- },
628
- wbTarget
629
- );
630
- reg(
631
- "whiteboard_add_shape",
632
- `Add a shape. Kind must be one of: ${VALID_SHAPES.join(", ")}.`,
633
- {
634
- shape: { type: "string", enum: VALID_SHAPES },
635
- x: { type: "number" },
636
- y: { type: "number" },
637
- width: { type: "number" },
638
- height: { type: "number" },
639
- text: { type: "string" },
640
- fill: { type: "string" },
641
- stroke: { type: "string" },
642
- flipX: { type: "boolean" },
643
- flipY: { type: "boolean" }
644
- },
645
- ["shape", "x", "y", "width", "height"],
646
- async (args) => {
647
- const kind = str(args.shape);
648
- if (!VALID_SHAPES.includes(kind)) return errorResult(`Invalid shape kind: ${kind}`);
649
- const x = num(args.x);
650
- const y = num(args.y);
651
- const width = num(args.width);
652
- const height = num(args.height);
653
- const shape = {
654
- id: newId("s"),
655
- kind: "shape",
656
- shape: kind,
657
- x,
658
- y,
659
- width,
660
- height,
661
- ...args.text !== void 0 ? { text: str(args.text) } : {},
662
- ...args.fill !== void 0 ? { fill: str(args.fill) } : {},
663
- ...args.stroke !== void 0 ? { stroke: str(args.stroke) } : {},
664
- ...args.flipX !== void 0 ? { flipX: bool(args.flipX) } : {},
665
- ...args.flipY !== void 0 ? { flipY: bool(args.flipY) } : {}
666
- };
667
- adapter.setShapes((all) => [...all, shape]);
668
- return textResult(`Added ${kind} ${shape.id}`, shape);
669
- },
670
- wbTarget
671
- );
672
- reg(
673
- "whiteboard_update_shape",
674
- "Update fields on a shape.",
675
- {
676
- id: { type: "string" },
677
- x: { type: "number" },
678
- y: { type: "number" },
679
- width: { type: "number" },
680
- height: { type: "number" },
681
- text: { type: "string" },
682
- fill: { type: "string" },
683
- stroke: { type: "string" }
684
- },
685
- ["id"],
686
- async (args) => {
687
- const id = str(args.id);
688
- const existing = adapter.getShapes().find((s) => s.id === id);
689
- if (!existing) return errorResult(`No shape with id ${id}`);
690
- const nextX = args.x !== void 0 ? num(args.x) : existing.x;
691
- const nextY = args.y !== void 0 ? num(args.y) : existing.y;
692
- const nextW = args.width !== void 0 ? num(args.width) : existing.width;
693
- const nextH = args.height !== void 0 ? num(args.height) : existing.height;
694
- let updated = null;
695
- adapter.setShapes(
696
- (all) => all.map((s) => {
697
- if (s.id !== id) return s;
698
- updated = {
699
- ...s,
700
- x: nextX,
701
- y: nextY,
702
- width: nextW,
703
- height: nextH,
704
- ...args.text !== void 0 ? { text: str(args.text) } : {},
705
- ...args.fill !== void 0 ? { fill: str(args.fill) } : {},
706
- ...args.stroke !== void 0 ? { stroke: str(args.stroke) } : {}
707
- };
708
- return updated;
709
- })
710
- );
711
- return textResult(`Updated shape ${id}`, updated);
712
- },
713
- wbTarget
714
- );
715
- reg(
716
- "whiteboard_add_connector",
717
- "Connect two items by id, or specify explicit world-space points.",
718
- {
719
- from: { description: "Item id (string) or {x,y}" },
720
- to: { description: "Item id (string) or {x,y}" },
721
- color: { type: "string" }
722
- },
723
- ["from", "to"],
724
- (args) => {
725
- const c = {
726
- id: newId("c"),
727
- kind: "connector",
728
- from: args.from,
729
- to: args.to,
730
- ...args.color !== void 0 ? { color: str(args.color) } : {}
731
- };
732
- adapter.setConnectors((all) => [...all, c]);
733
- return textResult(`Added connector ${c.id}`, c);
734
- },
735
- wbTarget
736
- );
737
- reg(
738
- "whiteboard_add_stroke",
739
- "Add a freeform pen stroke. Points are absolute screen coords (matching the Drawing layer).",
740
- {
741
- points: {
742
- type: "array",
743
- description: "Array of {x,y} points"
744
- },
745
- color: { type: "string" },
746
- size: { type: "number" }
747
- },
748
- ["points"],
749
- (args) => {
750
- const points = (Array.isArray(args.points) ? args.points : []).map((p) => ({
751
- x: num(p?.x),
752
- y: num(p?.y)
753
- }));
754
- if (!points.length) return errorResult("Stroke requires at least one point");
755
- const stroke = {
756
- id: newId("st"),
757
- points,
758
- color: typeof args.color === "string" ? args.color : "#0f172a",
759
- size: typeof args.size === "number" ? args.size : 2,
760
- authorId: agent.id
761
- };
762
- adapter.setStrokes((all) => [...all, stroke]);
763
- return textResult(`Added stroke ${stroke.id} (${points.length} points)`, stroke);
764
- },
765
- wbTarget
766
- );
767
- reg(
768
- "whiteboard_delete_item",
769
- "Remove any item by id (sticky / shape / connector / stroke).",
770
- { id: { type: "string" } },
771
- ["id"],
772
- (args) => {
773
- const id = str(args.id);
774
- const removedNotes = adapter.getNotes().filter((x) => x.id === id);
775
- const removedShapes = adapter.getShapes().filter((x) => x.id === id);
776
- const removedConnectors = adapter.getConnectors().filter((x) => x.id === id);
777
- const removedStrokes = adapter.getStrokes().filter((x) => x.id === id);
778
- const removed = removedNotes.length + removedShapes.length + removedConnectors.length + removedStrokes.length > 0;
779
- if (!removed) return errorResult(`No item with id ${id}`);
780
- adapter.setNotes((all) => all.filter((x) => x.id !== id));
781
- adapter.setShapes((all) => all.filter((x) => x.id !== id));
782
- adapter.setConnectors((all) => all.filter((x) => x.id !== id));
783
- adapter.setStrokes((all) => all.filter((x) => x.id !== id));
784
- pushUndoEntry(agent.id, {
785
- timestamp: Date.now(),
786
- bridgeId: "whiteboard",
787
- action: "whiteboard_delete_item",
788
- label: `Deleted ${id}`,
789
- undo: () => {
790
- if (removedNotes.length) adapter.setNotes((all) => [...all, ...removedNotes]);
791
- if (removedShapes.length) adapter.setShapes((all) => [...all, ...removedShapes]);
792
- if (removedConnectors.length) adapter.setConnectors((all) => [...all, ...removedConnectors]);
793
- if (removedStrokes.length) adapter.setStrokes((all) => [...all, ...removedStrokes]);
794
- },
795
- redo: () => {
796
- adapter.setNotes((all) => all.filter((x) => x.id !== id));
797
- adapter.setShapes((all) => all.filter((x) => x.id !== id));
798
- adapter.setConnectors((all) => all.filter((x) => x.id !== id));
799
- adapter.setStrokes((all) => all.filter((x) => x.id !== id));
800
- }
801
- });
802
- return textResult(`Deleted ${id}`);
803
- },
804
- wbTarget
805
- );
806
- reg(
807
- "whiteboard_set_viewport",
808
- "Pan / zoom the viewport.",
809
- { x: { type: "number" }, y: { type: "number" }, zoom: { type: "number" } },
810
- [],
811
- (args) => {
812
- const v = adapter.getViewport();
813
- const next = {
814
- x: args.x !== void 0 ? num(args.x) : v.x,
815
- y: args.y !== void 0 ? num(args.y) : v.y,
816
- zoom: args.zoom !== void 0 ? num(args.zoom) : v.zoom
817
- };
818
- adapter.setViewport(next);
819
- return textResult(`Viewport \u2192 ${JSON.stringify(next)}`, next);
820
- },
821
- wbTarget
822
- );
823
- reg(
824
- "whiteboard_set_agent_cursor",
825
- "Move the agent's presence cursor (or pass null to hide it).",
826
- {
827
- x: { type: "number" },
828
- y: { type: "number" },
829
- hide: { type: "boolean" }
830
- },
831
- [],
832
- (args) => {
833
- if (!adapter.setAgentCursor) return errorResult("Host did not provide setAgentCursor");
834
- if (bool(args.hide)) {
835
- adapter.setAgentCursor(null);
836
- return textResult("Agent cursor hidden");
837
- }
838
- const cursor = {
839
- userId: agent.id,
840
- name: agent.name,
841
- color: agent.color,
842
- x: num(args.x),
843
- y: num(args.y)
844
- };
845
- adapter.setAgentCursor(cursor);
846
- return textResult(`Cursor \u2192 (${cursor.x}, ${cursor.y})`, cursor);
847
- },
848
- wbTarget
849
- );
850
- return {
851
- id: "whiteboard",
852
- title: "Whiteboard",
853
- dispose: () => {
854
- for (const d of disposers) d();
855
- adapter.setAgentCursor?.(null);
856
- }
857
- };
858
- }
859
-
860
- // src/bridges/flow.ts
861
- var DEFAULT_AGENT2 = { id: "agent", name: "Agent", color: "#a855f7" };
862
- var num2 = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : 0;
863
- var str2 = (v, fallback = "") => typeof v === "string" ? v : fallback;
864
- var newId2 = (prefix) => `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
865
- function registerFlowBridge(host, options) {
866
- const { adapter } = options;
867
- const agent = { ...DEFAULT_AGENT2, ...options.agent ?? {} };
868
- const disposers = [];
869
- const flTarget = (args, result) => ({
870
- kind: "flow",
871
- elementId: result?.structuredContent?.id ?? args?.id
872
- });
873
- const reg = (name, description, properties, required, handler, resolveTarget) => {
874
- const wrapped = async (args) => {
875
- try {
876
- return await handler(args);
877
- } catch (e) {
878
- return errorResult(e instanceof Error ? e.message : String(e));
879
- }
880
- };
881
- const final = resolveTarget ? wrapToolWithActivity(wrapped, {
882
- toolName: name,
883
- agent: { id: agent.id, name: agent.name, color: agent.color },
884
- kind: "flow",
885
- resolveTarget: ({ args, result }) => resolveTarget(args, result)
886
- }) : wrapped;
887
- disposers.push(
888
- host.registerTool(
889
- {
890
- name,
891
- description,
892
- inputSchema: { type: "object", properties, required, additionalProperties: false }
893
- },
894
- final
895
- )
896
- );
897
- };
898
- reg("flow_get_state", "Get the full graph: nodes + edges.", {}, [], () => {
899
- const state = { nodes: adapter.getNodes(), edges: adapter.getEdges() };
900
- return textResult(JSON.stringify(state, null, 2), state);
901
- });
902
- reg("flow_list_nodes", "Summarise every node: id, kind, label, position, status.", {}, [], () => {
903
- const items = adapter.getNodes().map((n) => ({
904
- id: n.id,
905
- kind: n.type,
906
- label: n.data?.label,
907
- x: Math.round(n.position.x),
908
- y: Math.round(n.position.y),
909
- status: n.data?.status ?? "idle"
910
- }));
911
- const text = items.map((i) => `${i.kind} ${i.id}: "${i.label}" @(${i.x},${i.y}) [${i.status}]`).join("\n") || "(empty graph)";
912
- return textResult(text, items);
913
- });
914
- reg(
915
- "flow_get_node",
916
- "Get a single node's full record by id.",
917
- { id: { type: "string" } },
918
- ["id"],
919
- (args) => {
920
- const id = str2(args.id);
921
- const node = adapter.getNodes().find((n) => n.id === id);
922
- if (!node) return errorResult(`No node with id ${id}`);
923
- return textResult(JSON.stringify(node, null, 2), node);
924
- }
925
- );
926
- reg(
927
- "flow_list_node_kinds",
928
- "List every node kind registered in fancy-flow's registry. Use this to discover what's authorable before adding nodes.",
929
- { category: { type: "string", description: "Optional category filter: trigger | logic | data | ai | io | human | output | custom." } },
930
- [],
931
- async () => {
932
- try {
933
- const { listNodeKinds } = await import('@particle-academy/fancy-flow');
934
- const cat = adapter ? void 0 : void 0;
935
- const all = (cat ? listNodeKinds(cat) : listNodeKinds()).map((k) => ({
936
- name: k.name,
937
- category: k.category,
938
- label: k.label,
939
- description: k.description,
940
- icon: k.icon,
941
- accent: k.accent,
942
- inputs: k.inputs ?? [],
943
- outputs: k.outputs ?? [],
944
- configFields: (k.configSchema ?? []).map((f) => ({ key: f.key, type: f.type, label: f.label, required: !!f.required }))
945
- }));
946
- const text = all.map((k) => `${k.category}/${k.name}: ${k.label}${k.description ? " \u2014 " + k.description : ""}`).join("\n");
947
- return textResult(text || "(no kinds registered)", all);
948
- } catch (e) {
949
- return errorResult(`fancy-flow registry not available: ${e instanceof Error ? e.message : String(e)}`);
950
- }
951
- }
952
- );
953
- reg(
954
- "flow_get_node_schema",
955
- "Get the full configSchema + ports for a node kind. Use to know exactly what fields a kind accepts before calling flow_add_node.",
956
- { name: { type: "string" } },
957
- ["name"],
958
- async (args) => {
959
- try {
960
- const { getNodeKind } = await import('@particle-academy/fancy-flow');
961
- const k = getNodeKind(str2(args.name));
962
- if (!k) return errorResult(`No kind registered: ${args.name}`);
963
- const summary = {
964
- name: k.name,
965
- category: k.category,
966
- label: k.label,
967
- description: k.description,
968
- inputs: k.inputs ?? [],
969
- outputs: k.outputs ?? [],
970
- configSchema: k.configSchema ?? [],
971
- defaultConfig: k.defaultConfig ?? null
972
- };
973
- return textResult(JSON.stringify(summary, null, 2), summary);
974
- } catch (e) {
975
- return errorResult(`fancy-flow registry not available: ${e instanceof Error ? e.message : String(e)}`);
976
- }
977
- }
978
- );
979
- reg("flow_list_edges", "Summarise every edge.", {}, [], () => {
980
- const items = adapter.getEdges().map((e) => ({
981
- id: e.id,
982
- from: `${e.source}${e.sourceHandle ? `:${e.sourceHandle}` : ""}`,
983
- to: `${e.target}${e.targetHandle ? `:${e.targetHandle}` : ""}`
984
- }));
985
- return textResult(items.map((i) => `${i.id}: ${i.from} \u2192 ${i.to}`).join("\n") || "(no edges)", items);
986
- });
987
- reg(
988
- "flow_add_node",
989
- "Add a node of any kind registered in fancy-flow's registry. Call flow_list_node_kinds first to discover what's available.",
990
- {
991
- kind: { type: "string", description: "Registry kind name (e.g. memory_store, llm_call, branch)." },
992
- label: { type: "string" },
993
- x: { type: "number" },
994
- y: { type: "number" },
995
- description: { type: "string" },
996
- config: { type: "object", description: "Config fields per the kind's configSchema." },
997
- body: { type: "string", description: "Note kinds only \u2014 body text." }
998
- },
999
- ["kind", "label", "x", "y"],
1000
- async (args) => {
1001
- const kindName = str2(args.kind);
1002
- let kindDef = null;
1003
- try {
1004
- const { getNodeKind, defaultConfigFor } = await import('@particle-academy/fancy-flow');
1005
- kindDef = getNodeKind(kindName);
1006
- var defaults = kindDef ? defaultConfigFor(kindDef) : {};
1007
- } catch {
1008
- var defaults = {};
1009
- }
1010
- const isLegacy = ["trigger", "action", "decision", "output", "note", "subgraph"].includes(kindName);
1011
- if (!kindDef && !isLegacy) {
1012
- return errorResult(`Unknown kind: ${kindName} \u2014 call flow_list_node_kinds for the registry.`);
1013
- }
1014
- const id = newId2("n");
1015
- const config = { ...defaults, ...args.config && typeof args.config === "object" ? args.config : {} };
1016
- const node = {
1017
- id,
1018
- type: kindName,
1019
- position: { x: num2(args.x), y: num2(args.y) },
1020
- data: {
1021
- kind: kindName,
1022
- label: str2(args.label),
1023
- ...args.description ? { description: str2(args.description) } : {},
1024
- config,
1025
- ...kindName === "note" && args.body ? { body: str2(args.body) } : {}
1026
- }
1027
- };
1028
- adapter.setNodes((all) => [...all, node]);
1029
- return textResult(`Added ${kindName} ${id} ("${str2(args.label)}")`, node);
1030
- },
1031
- flTarget
1032
- );
1033
- reg(
1034
- "flow_update_node",
1035
- "Update fields on a node. Only provided fields change.",
1036
- {
1037
- id: { type: "string" },
1038
- label: { type: "string" },
1039
- x: { type: "number" },
1040
- y: { type: "number" },
1041
- description: { type: "string" },
1042
- config: { type: "object" }
1043
- },
1044
- ["id"],
1045
- (args) => {
1046
- const id = str2(args.id);
1047
- let updated = null;
1048
- adapter.setNodes(
1049
- (all) => all.map((n) => {
1050
- if (n.id !== id) return n;
1051
- updated = {
1052
- ...n,
1053
- position: {
1054
- x: args.x !== void 0 ? num2(args.x) : n.position.x,
1055
- y: args.y !== void 0 ? num2(args.y) : n.position.y
1056
- },
1057
- data: {
1058
- ...n.data,
1059
- ...args.label !== void 0 ? { label: str2(args.label) } : {},
1060
- ...args.description !== void 0 ? { description: str2(args.description) } : {},
1061
- ...args.config && typeof args.config === "object" ? { config: { ...n.data.config ?? {}, ...args.config } } : {}
1062
- }
1063
- };
1064
- return updated;
1065
- })
1066
- );
1067
- if (!updated) return errorResult(`No node with id ${id}`);
1068
- return textResult(`Updated node ${id}`, updated);
1069
- },
1070
- flTarget
1071
- );
1072
- reg(
1073
- "flow_delete_node",
1074
- "Remove a node by id (also removes any connected edges).",
1075
- { id: { type: "string" } },
1076
- ["id"],
1077
- (args) => {
1078
- const id = str2(args.id);
1079
- if (!adapter.getNodes().some((n) => n.id === id)) {
1080
- return errorResult(`No node with id ${id}`);
1081
- }
1082
- adapter.setNodes((all) => all.filter((n) => n.id !== id));
1083
- adapter.setEdges((all) => all.filter((e) => e.source !== id && e.target !== id));
1084
- return textResult(`Deleted node ${id}`);
1085
- },
1086
- flTarget
1087
- );
1088
- reg(
1089
- "flow_connect",
1090
- "Create an edge between two nodes (optionally specifying handle ids).",
1091
- {
1092
- source: { type: "string" },
1093
- target: { type: "string" },
1094
- sourceHandle: { type: "string" },
1095
- targetHandle: { type: "string" },
1096
- label: { type: "string" }
1097
- },
1098
- ["source", "target"],
1099
- (args) => {
1100
- const source = str2(args.source);
1101
- const target = str2(args.target);
1102
- const all = adapter.getNodes();
1103
- if (!all.find((n) => n.id === source)) return errorResult(`No source node ${source}`);
1104
- if (!all.find((n) => n.id === target)) return errorResult(`No target node ${target}`);
1105
- const edge = {
1106
- id: newId2("e"),
1107
- source,
1108
- target,
1109
- ...args.sourceHandle ? { sourceHandle: str2(args.sourceHandle) } : {},
1110
- ...args.targetHandle ? { targetHandle: str2(args.targetHandle) } : {},
1111
- ...args.label ? { label: str2(args.label) } : {}
1112
- };
1113
- adapter.setEdges((existing) => [...existing, edge]);
1114
- return textResult(`Connected ${source}${edge.sourceHandle ? `:${edge.sourceHandle}` : ""} \u2192 ${target}${edge.targetHandle ? `:${edge.targetHandle}` : ""}`, edge);
1115
- },
1116
- flTarget
1117
- );
1118
- reg(
1119
- "flow_disconnect",
1120
- "Remove an edge by id.",
1121
- { id: { type: "string" } },
1122
- ["id"],
1123
- (args) => {
1124
- const id = str2(args.id);
1125
- if (!adapter.getEdges().some((e) => e.id === id)) {
1126
- return errorResult(`No edge ${id}`);
1127
- }
1128
- adapter.setEdges((all) => all.filter((e) => e.id !== id));
1129
- return textResult(`Disconnected ${id}`);
1130
- },
1131
- flTarget
1132
- );
1133
- reg(
1134
- "flow_set_node_status",
1135
- "Manually set a node's status badge (idle | queued | running | done | error) and optional text. Useful for narration outside a run.",
1136
- {
1137
- id: { type: "string" },
1138
- status: { type: "string", enum: ["idle", "queued", "running", "done", "error"] },
1139
- text: { type: "string" }
1140
- },
1141
- ["id", "status"],
1142
- (args) => {
1143
- const id = str2(args.id);
1144
- const status = str2(args.status);
1145
- const text = args.text !== void 0 ? str2(args.text) : void 0;
1146
- if (adapter.setNodeStatus) {
1147
- adapter.setNodeStatus(id, status, text);
1148
- } else {
1149
- let found = false;
1150
- adapter.setNodes(
1151
- (all) => all.map((n) => {
1152
- if (n.id !== id) return n;
1153
- found = true;
1154
- return { ...n, data: { ...n.data, status, statusText: text } };
1155
- })
1156
- );
1157
- if (!found) return errorResult(`No node with id ${id}`);
1158
- }
1159
- return textResult(`${id} \u2192 ${status}${text ? ` (${text})` : ""}`);
1160
- },
1161
- flTarget
1162
- );
1163
- reg(
1164
- "flow_run",
1165
- "Trigger a run of the current graph. Returns the topological result. Requires the host to have wired `run` into the adapter.",
1166
- {},
1167
- [],
1168
- async () => {
1169
- if (!adapter.run) return errorResult("Host did not provide a run handler.");
1170
- const result = await adapter.run();
1171
- return textResult(result.ok ? "Run complete" : `Run failed: ${result.error ?? "unknown"}`, result);
1172
- },
1173
- flTarget
1174
- );
1175
- reg(
1176
- "flow_cancel",
1177
- "Cancel an in-flight run.",
1178
- {},
1179
- [],
1180
- () => {
1181
- if (!adapter.cancel) return errorResult("Host did not provide a cancel handler.");
1182
- adapter.cancel();
1183
- return textResult("Run cancelled");
1184
- },
1185
- flTarget
1186
- );
1187
- return {
1188
- id: "flow",
1189
- title: "Flow",
1190
- dispose: () => {
1191
- for (const d of disposers) d();
1192
- }
1193
- };
1194
- }
1195
-
1196
311
  // src/bridges/forms.ts
1197
- var DEFAULT_AGENT3 = { id: "agent", name: "Agent", color: "#a855f7" };
312
+ var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
1198
313
  function registerFormBridge(host, options) {
1199
314
  const { adapter } = options;
1200
- const agent = { ...DEFAULT_AGENT3, ...options.agent ?? {} };
315
+ const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
1201
316
  const disposers = [];
1202
317
  const formId = adapter.id;
1203
318
  const target = (args) => ({
@@ -1346,10 +461,10 @@ function registerFormBridge(host, options) {
1346
461
  }
1347
462
 
1348
463
  // src/bridges/sheets.ts
1349
- var DEFAULT_AGENT4 = { id: "agent", name: "Agent", color: "#a855f7" };
464
+ var DEFAULT_AGENT2 = { id: "agent", name: "Agent", color: "#a855f7" };
1350
465
  function registerSheetsBridge(host, options) {
1351
466
  const { adapter } = options;
1352
- const agent = { ...DEFAULT_AGENT4, ...options.agent ?? {} };
467
+ const agent = { ...DEFAULT_AGENT2, ...options.agent ?? {} };
1353
468
  const disposers = [];
1354
469
  const target = (sheetId, address) => ({
1355
470
  kind: "sheet",
@@ -1691,7 +806,7 @@ function useSheetsActivityHighlights(options = {}) {
1691
806
  const color = event.agentColor ?? "#a855f7";
1692
807
  out[address] = {
1693
808
  color,
1694
- background: color + "33",
809
+ backgroundColor: color + "33",
1695
810
  label: event.agentName ?? event.agentId ?? "agent"
1696
811
  };
1697
812
  }
@@ -1699,10 +814,10 @@ function useSheetsActivityHighlights(options = {}) {
1699
814
  }
1700
815
 
1701
816
  // src/bridges/code.ts
1702
- var DEFAULT_AGENT5 = { id: "agent", name: "Agent", color: "#a855f7" };
817
+ var DEFAULT_AGENT3 = { id: "agent", name: "Agent", color: "#a855f7" };
1703
818
  function registerCodeBridge(host, options) {
1704
819
  const { adapter } = options;
1705
- const agent = { ...DEFAULT_AGENT5, ...options.agent ?? {} };
820
+ const agent = { ...DEFAULT_AGENT3, ...options.agent ?? {} };
1706
821
  const disposers = [];
1707
822
  const target = {
1708
823
  kind: "code",
@@ -1865,10 +980,10 @@ function registerCodeBridge(host, options) {
1865
980
  }
1866
981
 
1867
982
  // src/bridges/charts.ts
1868
- var DEFAULT_AGENT6 = { id: "agent", name: "Agent", color: "#a855f7" };
983
+ var DEFAULT_AGENT4 = { id: "agent", name: "Agent", color: "#a855f7" };
1869
984
  function registerChartsBridge(host, options) {
1870
985
  const { adapter } = options;
1871
- const agent = { ...DEFAULT_AGENT6, ...options.agent ?? {} };
986
+ const agent = { ...DEFAULT_AGENT4, ...options.agent ?? {} };
1872
987
  const disposers = [];
1873
988
  const target = {
1874
989
  kind: "chart",
@@ -1979,10 +1094,10 @@ function registerChartsBridge(host, options) {
1979
1094
  }
1980
1095
 
1981
1096
  // src/bridges/scene.ts
1982
- var DEFAULT_AGENT7 = { id: "agent", name: "Agent", color: "#a855f7" };
1097
+ var DEFAULT_AGENT5 = { id: "agent", name: "Agent", color: "#a855f7" };
1983
1098
  function registerSceneBridge(host, options) {
1984
1099
  const { adapter } = options;
1985
- const agent = { ...DEFAULT_AGENT7, ...options.agent ?? {} };
1100
+ const agent = { ...DEFAULT_AGENT5, ...options.agent ?? {} };
1986
1101
  const disposers = [];
1987
1102
  const target = (objectId) => ({
1988
1103
  kind: "scene",
@@ -2012,7 +1127,7 @@ function registerSceneBridge(host, options) {
2012
1127
  )
2013
1128
  );
2014
1129
  };
2015
- const newId3 = (kind) => `${kind}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
1130
+ const newId = (kind) => `${kind}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
2016
1131
  reg(
2017
1132
  "scene_describe",
2018
1133
  "Describe the scene \u2014 object count, kinds, camera position.",
@@ -2056,7 +1171,7 @@ function registerSceneBridge(host, options) {
2056
1171
  ["kind"],
2057
1172
  (args) => {
2058
1173
  const obj = {
2059
- id: newId3(String(args.kind)),
1174
+ id: newId(String(args.kind)),
2060
1175
  kind: String(args.kind),
2061
1176
  position: parseTriple(args.position),
2062
1177
  rotation: parseTriple(args.rotation),
@@ -2176,10 +1291,10 @@ function parseTriple(v) {
2176
1291
  }
2177
1292
 
2178
1293
  // src/bridges/screens.ts
2179
- var DEFAULT_AGENT8 = { id: "agent", name: "Agent", color: "#a855f7" };
1294
+ var DEFAULT_AGENT6 = { id: "agent", name: "Agent", color: "#a855f7" };
2180
1295
  function registerScreensBridge(host, options) {
2181
1296
  const { adapter } = options;
2182
- const agent = { ...DEFAULT_AGENT8, ...options.agent ?? {} };
1297
+ const agent = { ...DEFAULT_AGENT6, ...options.agent ?? {} };
2183
1298
  const disposers = [];
2184
1299
  const target = (screenId) => ({
2185
1300
  kind: "screens",
@@ -2713,72 +1828,245 @@ function ShareControls({
2713
1828
  label: "Paste into Claude Desktop / Cline MCP server config",
2714
1829
  value: JSON.stringify(config, null, 2)
2715
1830
  }
2716
- ),
2717
- tab === "curl" && /* @__PURE__ */ jsxRuntime.jsx(
2718
- CopyBox,
2719
- {
2720
- label: "Connect from a terminal (verifies the relay is reachable)",
2721
- value: curl,
2722
- multiline: true
1831
+ ),
1832
+ tab === "curl" && /* @__PURE__ */ jsxRuntime.jsx(
1833
+ CopyBox,
1834
+ {
1835
+ label: "Connect from a terminal (verifies the relay is reachable)",
1836
+ value: curl,
1837
+ multiline: true
1838
+ }
1839
+ )
1840
+ ] })
1841
+ ] });
1842
+ }
1843
+ function TabButton({ tab, active, setTab, children }) {
1844
+ return /* @__PURE__ */ jsxRuntime.jsx(
1845
+ "button",
1846
+ {
1847
+ type: "button",
1848
+ role: "tab",
1849
+ "aria-selected": tab === active,
1850
+ className: `fai-share__tab${tab === active ? " is-active" : ""}`,
1851
+ onClick: () => setTab(tab),
1852
+ children
1853
+ }
1854
+ );
1855
+ }
1856
+ function CopyBox({ label, value, multiline }) {
1857
+ const [copied, setCopied] = react.useState(false);
1858
+ const copy = async () => {
1859
+ try {
1860
+ await navigator.clipboard.writeText(value);
1861
+ setCopied(true);
1862
+ setTimeout(() => setCopied(false), 1200);
1863
+ } catch {
1864
+ }
1865
+ };
1866
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1867
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fai-share__panel-label", children: label }),
1868
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-share__copy", children: [
1869
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: `fai-share__pre${multiline ? " is-multi" : ""}`, children: value }),
1870
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "fai-share__copy-btn", onClick: copy, children: copied ? "Copied" : "Copy" })
1871
+ ] })
1872
+ ] });
1873
+ }
1874
+ function buildCurlRecipe(session) {
1875
+ const base = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.host}` : "http://localhost";
1876
+ const inbox = `${base}/whiteboard-share/${session.id}/inbox?token=${session.token}`;
1877
+ const events = `${base}/whiteboard-share/${session.id}/events?token=${session.token}`;
1878
+ return [
1879
+ `# 1) In one terminal, subscribe to server-pushed frames (SSE)`,
1880
+ `curl -N "${events}"`,
1881
+ ``,
1882
+ `# 2) In another terminal, send an initialize handshake`,
1883
+ `curl -X POST "${inbox}" \\`,
1884
+ ` -H 'content-type: application/json' \\`,
1885
+ ` -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'`,
1886
+ ``,
1887
+ `# 3) List the tools the bridge exposes`,
1888
+ `curl -X POST "${inbox}" \\`,
1889
+ ` -H 'content-type: application/json' \\`,
1890
+ ` -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'`,
1891
+ ``,
1892
+ `# 4) Add a sticky note`,
1893
+ `curl -X POST "${inbox}" \\`,
1894
+ ` -H 'content-type: application/json' \\`,
1895
+ ` -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"whiteboard_add_sticky","arguments":{"x":300,"y":300,"text":"hello from curl"}}}'`
1896
+ ].join("\n");
1897
+ }
1898
+
1899
+ // src/presence/index.ts
1900
+ init_registry();
1901
+
1902
+ // src/presence/use-agent-activity.ts
1903
+ init_registry();
1904
+ function useAgentActivity(filter, options = {}) {
1905
+ const cap = options.capacity ?? 50;
1906
+ const [events, setEvents] = react.useState(() => readActivityHistory(filter).slice(-cap));
1907
+ react.useEffect(() => {
1908
+ setEvents(readActivityHistory(filter).slice(-cap));
1909
+ return onActivity((event) => {
1910
+ setEvents((prev) => {
1911
+ const next = prev.length >= cap ? prev.slice(prev.length - cap + 1) : prev.slice();
1912
+ next.push(event);
1913
+ return next;
1914
+ });
1915
+ }, filter);
1916
+ }, [filter?.agentId, filter?.screenId, filter?.kind, cap]);
1917
+ return { events, latest: events.length > 0 ? events[events.length - 1] : null };
1918
+ }
1919
+ function useAgentActivityForScreen(screenId, options = {}) {
1920
+ const { events, latest } = useAgentActivity({ screenId }, options);
1921
+ const fadeAfter = latest?.ttlMs ?? 1500;
1922
+ const [isAgentActive, setActive] = react.useState(false);
1923
+ react.useEffect(() => {
1924
+ if (!latest) {
1925
+ setActive(false);
1926
+ return;
1927
+ }
1928
+ setActive(true);
1929
+ const timer = setTimeout(() => setActive(false), fadeAfter);
1930
+ return () => clearTimeout(timer);
1931
+ }, [latest, fadeAfter]);
1932
+ return { events, latest, isAgentActive };
1933
+ }
1934
+
1935
+ // src/undo/undo-stack.ts
1936
+ var stacks = /* @__PURE__ */ new Map();
1937
+ var CAP = 200;
1938
+ function getStack(agentId) {
1939
+ let s = stacks.get(agentId);
1940
+ if (!s) {
1941
+ s = { past: [], future: [] };
1942
+ stacks.set(agentId, s);
1943
+ }
1944
+ return s;
1945
+ }
1946
+ function pushUndoEntry(agentId, entry) {
1947
+ const s = getStack(agentId);
1948
+ s.past.push(entry);
1949
+ if (s.past.length > CAP) s.past.splice(0, s.past.length - CAP);
1950
+ s.future.length = 0;
1951
+ }
1952
+ async function undoOne(agentId) {
1953
+ const s = getStack(agentId);
1954
+ const entry = s.past.pop();
1955
+ if (!entry) return null;
1956
+ await entry.undo();
1957
+ s.future.push(entry);
1958
+ return entry;
1959
+ }
1960
+ async function redoOne(agentId) {
1961
+ const s = getStack(agentId);
1962
+ const entry = s.future.pop();
1963
+ if (!entry) return null;
1964
+ await entry.redo();
1965
+ s.past.push(entry);
1966
+ return entry;
1967
+ }
1968
+ function readHistory(agentId) {
1969
+ return getStack(agentId).past.slice();
1970
+ }
1971
+ function clearStack(agentId) {
1972
+ stacks.delete(agentId);
1973
+ }
1974
+ function resetAllUndoStacks() {
1975
+ stacks.clear();
1976
+ }
1977
+
1978
+ // src/undo/undo-tools.ts
1979
+ var installedHosts = /* @__PURE__ */ new WeakSet();
1980
+ function ensureUndoToolsRegistered(host, options = {}) {
1981
+ if (installedHosts.has(host)) return;
1982
+ installedHosts.add(host);
1983
+ registerUndoTools(host, options);
1984
+ }
1985
+ function registerUndoTools(host, options = {}) {
1986
+ const defaultAgent = options.defaultAgentId ?? "agent";
1987
+ const disposers = [];
1988
+ const agentOf = (args) => typeof args?.agentId === "string" ? args.agentId : defaultAgent;
1989
+ disposers.push(
1990
+ host.registerTool(
1991
+ {
1992
+ name: "agent_undo",
1993
+ description: "Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.",
1994
+ inputSchema: {
1995
+ type: "object",
1996
+ properties: { agentId: { type: "string" } },
1997
+ additionalProperties: false
1998
+ }
1999
+ },
2000
+ async (args) => {
2001
+ const entry = await undoOne(agentOf(args));
2002
+ if (!entry) return errorResult("Nothing to undo.");
2003
+ return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });
2004
+ }
2005
+ )
2006
+ );
2007
+ disposers.push(
2008
+ host.registerTool(
2009
+ {
2010
+ name: "agent_redo",
2011
+ description: "Redo the most recently undone action.",
2012
+ inputSchema: {
2013
+ type: "object",
2014
+ properties: { agentId: { type: "string" } },
2015
+ additionalProperties: false
2723
2016
  }
2724
- )
2725
- ] })
2726
- ] });
2727
- }
2728
- function TabButton({ tab, active, setTab, children }) {
2729
- return /* @__PURE__ */ jsxRuntime.jsx(
2730
- "button",
2731
- {
2732
- type: "button",
2733
- role: "tab",
2734
- "aria-selected": tab === active,
2735
- className: `fai-share__tab${tab === active ? " is-active" : ""}`,
2736
- onClick: () => setTab(tab),
2737
- children
2738
- }
2017
+ },
2018
+ async (args) => {
2019
+ const entry = await redoOne(agentOf(args));
2020
+ if (!entry) return errorResult("Nothing to redo.");
2021
+ return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });
2022
+ }
2023
+ )
2024
+ );
2025
+ disposers.push(
2026
+ host.registerTool(
2027
+ {
2028
+ name: "agent_history",
2029
+ description: "List the agent's undo stack (oldest first). Useful for understanding what's reversible.",
2030
+ inputSchema: {
2031
+ type: "object",
2032
+ properties: { agentId: { type: "string" } },
2033
+ additionalProperties: false
2034
+ }
2035
+ },
2036
+ async (args) => {
2037
+ const history2 = readHistory(agentOf(args)).map(serialize);
2038
+ const text = history2.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join("\n");
2039
+ return textResult(text || "(empty)", history2);
2040
+ }
2041
+ )
2739
2042
  );
2043
+ return () => disposers.forEach((d) => d());
2740
2044
  }
2741
- function CopyBox({ label, value, multiline }) {
2742
- const [copied, setCopied] = react.useState(false);
2743
- const copy = async () => {
2744
- try {
2745
- await navigator.clipboard.writeText(value);
2746
- setCopied(true);
2747
- setTimeout(() => setCopied(false), 1200);
2748
- } catch {
2749
- }
2045
+ function serialize(entry) {
2046
+ return {
2047
+ timestamp: entry.timestamp,
2048
+ bridgeId: entry.bridgeId,
2049
+ action: entry.action,
2050
+ label: entry.label
2750
2051
  };
2751
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2752
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fai-share__panel-label", children: label }),
2753
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-share__copy", children: [
2754
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: `fai-share__pre${multiline ? " is-multi" : ""}`, children: value }),
2755
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "fai-share__copy-btn", onClick: copy, children: copied ? "Copied" : "Copy" })
2756
- ] })
2757
- ] });
2758
2052
  }
2759
- function buildCurlRecipe(session) {
2760
- const base = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.host}` : "http://localhost";
2761
- const inbox = `${base}/whiteboard-share/${session.id}/inbox?token=${session.token}`;
2762
- const events = `${base}/whiteboard-share/${session.id}/events?token=${session.token}`;
2763
- return [
2764
- `# 1) In one terminal, subscribe to server-pushed frames (SSE)`,
2765
- `curl -N "${events}"`,
2766
- ``,
2767
- `# 2) In another terminal, send an initialize handshake`,
2768
- `curl -X POST "${inbox}" \\`,
2769
- ` -H 'content-type: application/json' \\`,
2770
- ` -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'`,
2771
- ``,
2772
- `# 3) List the tools the bridge exposes`,
2773
- `curl -X POST "${inbox}" \\`,
2774
- ` -H 'content-type: application/json' \\`,
2775
- ` -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'`,
2776
- ``,
2777
- `# 4) Add a sticky note`,
2778
- `curl -X POST "${inbox}" \\`,
2779
- ` -H 'content-type: application/json' \\`,
2780
- ` -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"whiteboard_add_sticky","arguments":{"x":300,"y":300,"text":"hello from curl"}}}'`
2781
- ].join("\n");
2053
+ function useUndoStack(agentId, intervalMs = 500) {
2054
+ const [history2, setHistory] = react.useState(() => readHistory(agentId));
2055
+ react.useEffect(() => {
2056
+ let cancelled = false;
2057
+ const tick = () => {
2058
+ if (cancelled) return;
2059
+ setHistory(readHistory(agentId));
2060
+ };
2061
+ const id = setInterval(tick, intervalMs);
2062
+ tick();
2063
+ return () => {
2064
+ cancelled = true;
2065
+ clearInterval(id);
2066
+ };
2067
+ }, [agentId, intervalMs]);
2068
+ const refresh = react.useCallback(() => setHistory(readHistory(agentId)), [agentId]);
2069
+ return { history: history2, refresh };
2782
2070
  }
2783
2071
 
2784
2072
  // src/sharing/sse-relay.ts
@@ -2893,329 +2181,6 @@ function attachSseRelay(server, options) {
2893
2181
  });
2894
2182
  return transport;
2895
2183
  }
2896
- var DEFAULT_AGENT9 = { id: "agent", name: "Agent", color: "#a855f7" };
2897
- function SharedWhiteboard({
2898
- initialNotes = [],
2899
- initialShapes = [],
2900
- initialConnectors = [],
2901
- initialStrokes = [],
2902
- initialViewport = { x: 0, y: 0, zoom: 1 },
2903
- agent = DEFAULT_AGENT9,
2904
- shareBaseUrl = "/whiteboard-share",
2905
- onRegisterSession,
2906
- showAgentPanel = true,
2907
- showShareControls = true,
2908
- broadcastEdits = true,
2909
- height = 640,
2910
- header,
2911
- className,
2912
- style
2913
- }) {
2914
- const [notes, setNotes] = react.useState(initialNotes);
2915
- const [shapes, setShapes] = react.useState(initialShapes);
2916
- const [connectors, setConnectors] = react.useState(initialConnectors);
2917
- const [strokes, setStrokes] = react.useState(initialStrokes);
2918
- const [viewport, setViewport] = react.useState(initialViewport);
2919
- const [agentCursor, setAgentCursor] = react.useState(null);
2920
- const [activity, setActivity] = react.useState([]);
2921
- const [highlight, setHighlight] = react.useState(null);
2922
- const stateRefs = react.useRef({ notes, shapes, connectors, strokes, viewport });
2923
- react.useEffect(() => {
2924
- stateRefs.current = { notes, shapes, connectors, strokes, viewport };
2925
- }, [notes, shapes, connectors, strokes, viewport]);
2926
- const serverRef = react.useRef(null);
2927
- const inProcRef = react.useRef(null);
2928
- const bridgeRef = react.useRef(null);
2929
- react.useEffect(() => {
2930
- const server = new MicroMcpServer({
2931
- info: { name: "shared-whiteboard", version: "0.2.0" },
2932
- instructions: "Collaborative whiteboard. Use whiteboard_* tools to read or modify the board."
2933
- });
2934
- bridgeRef.current = registerWhiteboardBridge(server, {
2935
- adapter: {
2936
- getNotes: () => stateRefs.current.notes,
2937
- setNotes: (next) => setNotes(typeof next === "function" ? next : () => next),
2938
- getShapes: () => stateRefs.current.shapes,
2939
- setShapes: (next) => setShapes(typeof next === "function" ? next : () => next),
2940
- getConnectors: () => stateRefs.current.connectors,
2941
- setConnectors: (next) => setConnectors(typeof next === "function" ? next : () => next),
2942
- getStrokes: () => stateRefs.current.strokes,
2943
- setStrokes: (next) => setStrokes(typeof next === "function" ? next : () => next),
2944
- getViewport: () => stateRefs.current.viewport,
2945
- setViewport,
2946
- setAgentCursor
2947
- },
2948
- agent
2949
- });
2950
- inProcRef.current = attachInProcess(server);
2951
- serverRef.current = server;
2952
- const off = inProcRef.current.onServerMessage((msg) => {
2953
- if (msg?.id !== void 0 && "result" in msg && msg.result?.structuredContent?.id) {
2954
- const id = msg.result.structuredContent.id;
2955
- requestAnimationFrame(() => pulseFor(id));
2956
- }
2957
- });
2958
- return () => {
2959
- off();
2960
- bridgeRef.current?.dispose();
2961
- bridgeRef.current = null;
2962
- if (inProcRef.current) server.detach(inProcRef.current);
2963
- };
2964
- }, []);
2965
- const pulseFor = (id) => {
2966
- const n = stateRefs.current.notes.find((x) => x.id === id);
2967
- if (n) return setHighlight({ pulseKey: Date.now(), bounds: { x: n.x, y: n.y, width: n.width, height: n.height } });
2968
- const s = stateRefs.current.shapes.find((x) => x.id === id);
2969
- if (s) return setHighlight({ pulseKey: Date.now(), bounds: { x: s.x, y: s.y, width: s.width, height: s.height } });
2970
- };
2971
- const log = react.useCallback((entry) => {
2972
- setActivity((all) => [...all.slice(-200), { id: `a_${Date.now()}_${all.length}`, at: Date.now(), ...entry }]);
2973
- }, []);
2974
- const [session, setSession] = react.useState(null);
2975
- const [relayState, setRelayState] = react.useState("idle");
2976
- const sseRef = react.useRef(null);
2977
- const logEsRef = react.useRef(null);
2978
- const startShare = async () => {
2979
- if (session || !serverRef.current || !shareBaseUrl) return;
2980
- const desc = createSessionDescriptor();
2981
- try {
2982
- if (onRegisterSession) {
2983
- await onRegisterSession(desc);
2984
- } else {
2985
- const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? "";
2986
- const reg = await fetch(`${shareBaseUrl}/register`, {
2987
- method: "POST",
2988
- headers: { "content-type": "application/json", "x-csrf-token": csrf, accept: "application/json" },
2989
- body: JSON.stringify({ session: desc.id, token: desc.token })
2990
- });
2991
- if (!reg.ok) throw new Error(`registration failed (HTTP ${reg.status})`);
2992
- }
2993
- } catch (e) {
2994
- log({ kind: "error", source: "share", text: e instanceof Error ? e.message : String(e) });
2995
- return;
2996
- }
2997
- const relay = attachSseRelay(serverRef.current, {
2998
- baseUrl: shareBaseUrl,
2999
- sessionId: desc.id,
3000
- token: desc.token
3001
- });
3002
- sseRef.current = relay;
3003
- relay.onStateChange(setRelayState);
3004
- const es = new EventSource(`${shareBaseUrl}/${desc.id}/events?token=${desc.token}&direction=inbound`);
3005
- es.addEventListener("mcp", (ev) => {
3006
- try {
3007
- const frame = JSON.parse(ev.data);
3008
- if (frame.method === "notifications/peer_joined") {
3009
- setAgentCursor((c) => c ?? { userId: agent.id, name: agent.name, color: agent.color, x: 60, y: 60 });
3010
- log({ kind: "info", source: "presence", text: `${agent.name ?? "Agent"} connected` });
3011
- return;
3012
- }
3013
- if (frame.method === "notifications/peer_left") {
3014
- setAgentCursor(null);
3015
- log({ kind: "info", source: "presence", text: `${agent.name ?? "Agent"} disconnected` });
3016
- return;
3017
- }
3018
- if (frame.method === "notifications/agent_message") {
3019
- log({ kind: "message", source: agent.name ?? "Agent", text: String(frame.params?.text ?? "") });
3020
- } else if (frame.method === "notifications/agent_status") {
3021
- log({ kind: "info", source: agent.name ?? "Agent", text: String(frame.params?.text ?? "") });
3022
- } else if (frame.method?.startsWith("notifications/")) {
3023
- } else {
3024
- log({ kind: "tool", source: "remote", text: `\u2190 ${frame.method ?? `id:${frame.id}`}`, detail: frame });
3025
- }
3026
- } catch {
3027
- }
3028
- });
3029
- logEsRef.current = es;
3030
- setSession(desc);
3031
- log({ kind: "info", source: "share", text: `Sharing started \xB7 session ${desc.id}` });
3032
- };
3033
- const stopShare = async () => {
3034
- if (!session) return;
3035
- const desc = session;
3036
- setSession(null);
3037
- logEsRef.current?.close();
3038
- logEsRef.current = null;
3039
- if (sseRef.current && serverRef.current) serverRef.current.detach(sseRef.current);
3040
- sseRef.current = null;
3041
- setRelayState("closed");
3042
- if (shareBaseUrl) {
3043
- const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? "";
3044
- await fetch(`${shareBaseUrl}/${desc.id}/unregister?token=${encodeURIComponent(desc.token)}`, {
3045
- method: "POST",
3046
- headers: { "x-csrf-token": csrf, accept: "application/json" }
3047
- }).catch(() => {
3048
- });
3049
- }
3050
- log({ kind: "info", source: "share", text: "Sharing stopped." });
3051
- };
3052
- const lastBroadcastRef = react.useRef(0);
3053
- react.useEffect(() => {
3054
- if (!broadcastEdits || !sseRef.current || !session) return;
3055
- const now = Date.now();
3056
- if (now - lastBroadcastRef.current < 80) return;
3057
- lastBroadcastRef.current = now;
3058
- sseRef.current.send({
3059
- jsonrpc: "2.0",
3060
- method: "notifications/state_update",
3061
- params: { notes, shapes, connectors, viewport, ts: now }
3062
- });
3063
- }, [notes, shapes, connectors, viewport, session, broadcastEdits]);
3064
- const handleSubmit = (text) => {
3065
- if (!sseRef.current) {
3066
- log({ kind: "error", source: "you", text: "Start a shared session first." });
3067
- return;
3068
- }
3069
- sseRef.current.send({
3070
- jsonrpc: "2.0",
3071
- method: "notifications/user_message",
3072
- params: { text, ts: Date.now() }
3073
- });
3074
- log({ kind: "message", source: "You", text });
3075
- };
3076
- const cursors = react.useMemo(() => [], []);
3077
- const statusText = (() => {
3078
- switch (relayState) {
3079
- case "open":
3080
- return "live";
3081
- case "connecting":
3082
- return "connecting\u2026";
3083
- case "error":
3084
- return "error";
3085
- case "closed":
3086
- return "closed";
3087
- default:
3088
- return void 0;
3089
- }
3090
- })();
3091
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["fai-shared-whiteboard", className ?? ""].filter(Boolean).join(" "), style, children: [
3092
- header,
3093
- showShareControls && shareBaseUrl !== null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fai-shared-whiteboard__controls", children: /* @__PURE__ */ jsxRuntime.jsx(ShareControls, { session, onStart: startShare, onStop: stopShare, status: statusText }) }),
3094
- /* @__PURE__ */ jsxRuntime.jsxs(
3095
- "div",
3096
- {
3097
- className: "fai-shared-whiteboard__layout",
3098
- style: {
3099
- display: "grid",
3100
- gap: 16,
3101
- gridTemplateColumns: showAgentPanel ? "1fr 360px" : "1fr"
3102
- },
3103
- children: [
3104
- /* @__PURE__ */ jsxRuntime.jsx(
3105
- "div",
3106
- {
3107
- className: "fai-shared-whiteboard__board",
3108
- style: {
3109
- position: "relative",
3110
- overflow: "hidden",
3111
- borderRadius: 12,
3112
- border: "1px solid #e4e4e7",
3113
- background: "radial-gradient(circle at 1px 1px, rgba(0,0,0,0.07) 1px, transparent 0)",
3114
- backgroundSize: "20px 20px",
3115
- height
3116
- },
3117
- children: /* @__PURE__ */ jsxRuntime.jsxs(fancyWhiteboard.Board, { viewport, onViewportChange: setViewport, style: { width: "100%", height: "100%" }, children: [
3118
- connectors.map((c) => {
3119
- const a = resolveCenter(c.from, notes, shapes);
3120
- const b = resolveCenter(c.to, notes, shapes);
3121
- if (!a || !b) return null;
3122
- return /* @__PURE__ */ jsxRuntime.jsx(fancyWhiteboard.Connector, { from: a, to: b, color: c.color ?? "#64748b" }, c.id);
3123
- }),
3124
- shapes.map((s) => /* @__PURE__ */ jsxRuntime.jsx(fancyWhiteboard.Shape, { item: s, onChange: (next) => setShapes((all) => all.map((x) => x.id === next.id ? next : x)) }, s.id)),
3125
- notes.map((n) => /* @__PURE__ */ jsxRuntime.jsx(fancyWhiteboard.StickyNote, { item: n, onChange: (next) => setNotes((all) => all.map((x) => x.id === next.id ? next : x)) }, n.id)),
3126
- /* @__PURE__ */ jsxRuntime.jsx(fancyWhiteboard.CursorLayer, { cursors }),
3127
- agentCursor && /* @__PURE__ */ jsxRuntime.jsx(AgentCursor, { x: agentCursor.x, y: agentCursor.y, name: agentCursor.name, color: agentCursor.color }),
3128
- highlight && /* @__PURE__ */ jsxRuntime.jsx(
3129
- AgentActivityHighlight,
3130
- {
3131
- x: highlight.bounds.x,
3132
- y: highlight.bounds.y,
3133
- width: highlight.bounds.width,
3134
- height: highlight.bounds.height,
3135
- color: agent.color ?? "#a855f7",
3136
- pulseKey: highlight.pulseKey
3137
- }
3138
- )
3139
- ] })
3140
- }
3141
- ),
3142
- showAgentPanel && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height }, children: /* @__PURE__ */ jsxRuntime.jsx(
3143
- AgentPanel,
3144
- {
3145
- agent,
3146
- activity,
3147
- onSubmit: handleSubmit
3148
- }
3149
- ) })
3150
- ]
3151
- }
3152
- )
3153
- ] });
3154
- }
3155
- function resolveCenter(ref, notes, shapes) {
3156
- if (typeof ref === "string") {
3157
- const n = notes.find((x) => x.id === ref);
3158
- if (n) return { x: n.x + n.width / 2, y: n.y + n.height / 2 };
3159
- const s = shapes.find((x) => x.id === ref);
3160
- if (s) return { x: s.x + s.width / 2, y: s.y + s.height / 2 };
3161
- return null;
3162
- }
3163
- return ref;
3164
- }
3165
-
3166
- // src/presence/index.ts
3167
- init_registry();
3168
-
3169
- // src/presence/use-agent-activity.ts
3170
- init_registry();
3171
- function useAgentActivity(filter, options = {}) {
3172
- const cap = options.capacity ?? 50;
3173
- const [events, setEvents] = react.useState(() => readActivityHistory(filter).slice(-cap));
3174
- react.useEffect(() => {
3175
- setEvents(readActivityHistory(filter).slice(-cap));
3176
- return onActivity((event) => {
3177
- setEvents((prev) => {
3178
- const next = prev.length >= cap ? prev.slice(prev.length - cap + 1) : prev.slice();
3179
- next.push(event);
3180
- return next;
3181
- });
3182
- }, filter);
3183
- }, [filter?.agentId, filter?.screenId, filter?.kind, cap]);
3184
- return { events, latest: events.length > 0 ? events[events.length - 1] : null };
3185
- }
3186
- function useAgentActivityForScreen(screenId, options = {}) {
3187
- const { events, latest } = useAgentActivity({ screenId }, options);
3188
- const fadeAfter = latest?.ttlMs ?? 1500;
3189
- const [isAgentActive, setActive] = react.useState(false);
3190
- react.useEffect(() => {
3191
- if (!latest) {
3192
- setActive(false);
3193
- return;
3194
- }
3195
- setActive(true);
3196
- const timer = setTimeout(() => setActive(false), fadeAfter);
3197
- return () => clearTimeout(timer);
3198
- }, [latest, fadeAfter]);
3199
- return { events, latest, isAgentActive };
3200
- }
3201
- function useUndoStack(agentId, intervalMs = 500) {
3202
- const [history2, setHistory] = react.useState(() => readHistory(agentId));
3203
- react.useEffect(() => {
3204
- let cancelled = false;
3205
- const tick = () => {
3206
- if (cancelled) return;
3207
- setHistory(readHistory(agentId));
3208
- };
3209
- const id = setInterval(tick, intervalMs);
3210
- tick();
3211
- return () => {
3212
- cancelled = true;
3213
- clearInterval(id);
3214
- };
3215
- }, [agentId, intervalMs]);
3216
- const refresh = react.useCallback(() => setHistory(readHistory(agentId)), [agentId]);
3217
- return { history: history2, refresh };
3218
- }
3219
2184
 
3220
2185
  exports.AgentActivityHighlight = AgentActivityHighlight;
3221
2186
  exports.AgentCursor = AgentCursor;
@@ -3227,7 +2192,6 @@ exports.MicroMcpServer = MicroMcpServer;
3227
2192
  exports.RelayTransport = RelayTransport;
3228
2193
  exports.ScreensActivityBridge = ScreensActivityBridge;
3229
2194
  exports.ShareControls = ShareControls;
3230
- exports.SharedWhiteboard = SharedWhiteboard;
3231
2195
  exports.SseRelayTransport = SseRelayTransport;
3232
2196
  exports.ToolRegistry = ToolRegistry;
3233
2197
  exports.attachInProcess = attachInProcess;
@@ -3249,13 +2213,11 @@ exports.readUndoHistory = readHistory;
3249
2213
  exports.redoOne = redoOne;
3250
2214
  exports.registerChartsBridge = registerChartsBridge;
3251
2215
  exports.registerCodeBridge = registerCodeBridge;
3252
- exports.registerFlowBridge = registerFlowBridge;
3253
2216
  exports.registerFormBridge = registerFormBridge;
3254
2217
  exports.registerSceneBridge = registerSceneBridge;
3255
2218
  exports.registerScreensBridge = registerScreensBridge;
3256
2219
  exports.registerSheetsBridge = registerSheetsBridge;
3257
2220
  exports.registerUndoTools = registerUndoTools;
3258
- exports.registerWhiteboardBridge = registerWhiteboardBridge;
3259
2221
  exports.resetActivityRegistry = resetActivityRegistry;
3260
2222
  exports.resetAllUndoStacks = resetAllUndoStacks;
3261
2223
  exports.rpcError = rpcError;