@particle-academy/agent-integrations 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/bridges-flow.js +340 -3
- package/dist/bridges-flow.js.map +1 -1
- package/dist/{chunk-E4AICMFZ.js → chunk-5XELJIJR.js} +3 -3
- package/dist/chunk-5XELJIJR.js.map +1 -0
- package/dist/{chunk-6LTKCNLF.js → chunk-AFUULW5E.js} +3 -34
- package/dist/chunk-AFUULW5E.js.map +1 -0
- package/dist/chunk-G6N2TQVO.js +34 -0
- package/dist/chunk-G6N2TQVO.js.map +1 -0
- package/dist/chunk-IJ6JX5VC.js +3 -0
- package/dist/chunk-IJ6JX5VC.js.map +1 -0
- package/dist/{chunk-JMYPUAFH.js → chunk-LVQXIUJH.js} +2 -2
- package/dist/{chunk-JMYPUAFH.js.map → chunk-LVQXIUJH.js.map} +1 -1
- package/dist/chunk-ZHAK2DQR.js +289 -0
- package/dist/chunk-ZHAK2DQR.js.map +1 -0
- package/dist/components/SharedWhiteboard/index.d.cts +55 -0
- package/dist/components/SharedWhiteboard/index.d.ts +55 -0
- package/dist/components-shared-whiteboard.cjs +1533 -0
- package/dist/components-shared-whiteboard.cjs.map +1 -0
- package/dist/components-shared-whiteboard.js +285 -0
- package/dist/components-shared-whiteboard.js.map +1 -0
- package/dist/index.cjs +249 -1287
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -55
- package/dist/index.d.ts +4 -55
- package/dist/index.js +9 -563
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +2 -1
- package/dist/sharing/index.d.cts +2 -34
- package/dist/sharing/index.d.ts +2 -34
- package/dist/sharing.js +2 -1
- package/dist/sheets-adapter.cjs +1 -1
- package/dist/sheets-adapter.cjs.map +1 -1
- package/dist/sheets-adapter.d.cts +11 -7
- package/dist/sheets-adapter.d.ts +11 -7
- package/dist/sheets-adapter.js +1 -1
- package/dist/token-CrJF76oH.d.cts +34 -0
- package/dist/token-CrJF76oH.d.ts +34 -0
- package/package.json +57 -7
- package/dist/chunk-6LTKCNLF.js.map +0 -1
- package/dist/chunk-E4AICMFZ.js.map +0 -1
- package/dist/chunk-N3H4DXY5.js +0 -342
- 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
|
|
312
|
+
var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1198
313
|
function registerFormBridge(host, options) {
|
|
1199
314
|
const { adapter } = options;
|
|
1200
|
-
const 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
|
|
464
|
+
var DEFAULT_AGENT2 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1350
465
|
function registerSheetsBridge(host, options) {
|
|
1351
466
|
const { adapter } = options;
|
|
1352
|
-
const 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
|
-
|
|
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
|
|
817
|
+
var DEFAULT_AGENT3 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1703
818
|
function registerCodeBridge(host, options) {
|
|
1704
819
|
const { adapter } = options;
|
|
1705
|
-
const 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
|
|
983
|
+
var DEFAULT_AGENT4 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1869
984
|
function registerChartsBridge(host, options) {
|
|
1870
985
|
const { adapter } = options;
|
|
1871
|
-
const 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
|
|
1097
|
+
var DEFAULT_AGENT5 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1983
1098
|
function registerSceneBridge(host, options) {
|
|
1984
1099
|
const { adapter } = options;
|
|
1985
|
-
const 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
|
|
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:
|
|
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
|
|
1294
|
+
var DEFAULT_AGENT6 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
2180
1295
|
function registerScreensBridge(host, options) {
|
|
2181
1296
|
const { adapter } = options;
|
|
2182
|
-
const 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
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
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
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
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
|
|
2760
|
-
const
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
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;
|