@particle-academy/agent-integrations 0.12.0 → 0.14.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/dist/index.cjs CHANGED
@@ -2216,6 +2216,459 @@ function registerTerminalBridge(host, options) {
2216
2216
  pending: () => [...staged.values()]
2217
2217
  };
2218
2218
  }
2219
+
2220
+ // src/bridges/navigation.ts
2221
+ var DEFAULT_AGENT9 = { id: "agent", name: "Agent", color: "#a855f7" };
2222
+ function registerNavigationBridge(host, options) {
2223
+ const { adapter } = options;
2224
+ const agent = { ...DEFAULT_AGENT9, ...options.agent ?? {} };
2225
+ const pendingMode = options.pendingMode ?? true;
2226
+ const disposers = [];
2227
+ ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
2228
+ const target = (label, elementId) => ({
2229
+ kind: "navigation",
2230
+ screenId: adapter.screenId,
2231
+ elementId,
2232
+ label
2233
+ });
2234
+ const reg = (name, description, properties, required, handler, activity) => {
2235
+ const wrapped = async (args) => {
2236
+ try {
2237
+ return await handler(args);
2238
+ } catch (e) {
2239
+ return errorResult(e instanceof Error ? e.message : String(e));
2240
+ }
2241
+ };
2242
+ const final = activity ? wrapToolWithActivity(wrapped, {
2243
+ toolName: name,
2244
+ agent: { id: agent.id, name: agent.name, color: agent.color },
2245
+ kind: "navigation",
2246
+ screenId: adapter.screenId,
2247
+ resolveTarget: ({ args }) => activity(args)
2248
+ }) : wrapped;
2249
+ disposers.push(
2250
+ host.registerTool(
2251
+ {
2252
+ name,
2253
+ description,
2254
+ inputSchema: { type: "object", properties, required, additionalProperties: false }
2255
+ },
2256
+ final
2257
+ )
2258
+ );
2259
+ };
2260
+ reg(
2261
+ "page_describe",
2262
+ "Describe the current page: its URL, title, and the interactive elements you can act on (each with a stable `handle`, role, and label). Call this first, and again after navigating.",
2263
+ {},
2264
+ [],
2265
+ () => {
2266
+ const snap = adapter.describe();
2267
+ const text = [
2268
+ `URL: ${snap.url}`,
2269
+ `Title: ${snap.title}`,
2270
+ "",
2271
+ ...snap.actions.map((a) => `[${a.handle}] ${a.role}: ${a.label}${a.destructive ? " (destructive)" : ""}`)
2272
+ ].join("\n");
2273
+ return textResult(text, snap);
2274
+ },
2275
+ false
2276
+ );
2277
+ reg(
2278
+ "page_read",
2279
+ "Read the page's visible text / heading outline for grounding.",
2280
+ {},
2281
+ [],
2282
+ () => textResult(adapter.read ? adapter.read() : "(host did not provide page text)"),
2283
+ false
2284
+ );
2285
+ reg(
2286
+ "nav_visit",
2287
+ "Navigate to a URL (same-site path or absolute). The human watches the page change.",
2288
+ { url: { type: "string", description: "Path like /packages or an absolute URL." } },
2289
+ ["url"],
2290
+ async (args) => {
2291
+ const url = String(args.url ?? "");
2292
+ if (!url) return errorResult("url is required");
2293
+ const from = adapter.getLocation().url;
2294
+ await adapter.visit(url);
2295
+ fancyAutoCommon.pushUndoEntry(agent.id, {
2296
+ timestamp: Date.now(),
2297
+ bridgeId: "navigation",
2298
+ action: "nav_visit",
2299
+ label: `Navigate to ${url}`,
2300
+ undo: () => {
2301
+ adapter.visit(from);
2302
+ },
2303
+ redo: () => {
2304
+ adapter.visit(url);
2305
+ }
2306
+ });
2307
+ return textResult(`Navigated to ${url}`, { url });
2308
+ },
2309
+ (args) => target(`Navigate \u2192 ${String(args.url ?? "")}`)
2310
+ );
2311
+ reg(
2312
+ "nav_back",
2313
+ "Go back to the previous page.",
2314
+ {},
2315
+ [],
2316
+ async () => {
2317
+ if (!adapter.back) return errorResult("Host did not provide back navigation.");
2318
+ await adapter.back();
2319
+ return textResult("Went back");
2320
+ },
2321
+ () => target("Back")
2322
+ );
2323
+ reg(
2324
+ "nav_forward",
2325
+ "Go forward to the next page.",
2326
+ {},
2327
+ [],
2328
+ async () => {
2329
+ if (!adapter.forward) return errorResult("Host did not provide forward navigation.");
2330
+ await adapter.forward();
2331
+ return textResult("Went forward");
2332
+ },
2333
+ () => target("Forward")
2334
+ );
2335
+ reg(
2336
+ "nav_scroll_to",
2337
+ "Scroll the page to absolute coordinates, or to a specific element by its handle.",
2338
+ {
2339
+ handle: { type: "string", description: "Scroll this element into view." },
2340
+ x: { type: "number" },
2341
+ y: { type: "number" }
2342
+ },
2343
+ [],
2344
+ (args) => {
2345
+ adapter.scrollTo({
2346
+ handle: typeof args.handle === "string" ? args.handle : void 0,
2347
+ x: typeof args.x === "number" ? args.x : void 0,
2348
+ y: typeof args.y === "number" ? args.y : void 0
2349
+ });
2350
+ return textResult("Scrolled");
2351
+ },
2352
+ () => target("Scroll")
2353
+ );
2354
+ reg(
2355
+ "nav_scroll_by",
2356
+ "Scroll the page by a vertical delta in pixels (negative scrolls up).",
2357
+ { dy: { type: "number" } },
2358
+ ["dy"],
2359
+ (args) => {
2360
+ adapter.scrollBy(Number(args.dy ?? 0));
2361
+ return textResult(`Scrolled by ${Number(args.dy ?? 0)}px`);
2362
+ },
2363
+ () => target("Scroll")
2364
+ );
2365
+ reg(
2366
+ "page_set_field",
2367
+ "Set a form field's value by handle. The host updates the controlled input and the human sees it change.",
2368
+ {
2369
+ handle: { type: "string" },
2370
+ value: { description: "Value to set; type matches the field." }
2371
+ },
2372
+ ["handle", "value"],
2373
+ (args) => {
2374
+ const handle = String(args.handle ?? "");
2375
+ const res = adapter.setField(handle, args.value);
2376
+ if (!res.ok) return errorResult(res.error ?? `Could not set ${handle}`);
2377
+ return textResult(`${handle} \u2190 ${JSON.stringify(args.value)}`, { handle, value: args.value });
2378
+ },
2379
+ (args) => target(`Set ${String(args.handle ?? "")}`, String(args.handle ?? ""))
2380
+ );
2381
+ reg(
2382
+ "page_click",
2383
+ "Activate an element by handle (link, button, checkbox\u2026). Destructive elements are staged for the human to confirm.",
2384
+ { handle: { type: "string" } },
2385
+ ["handle"],
2386
+ async (args) => {
2387
+ const handle = String(args.handle ?? "");
2388
+ const action = adapter.describe().actions.find((a) => a.handle === handle);
2389
+ if (pendingMode && action?.destructive && adapter.confirm) {
2390
+ const ok = await adapter.confirm({ action: "click", handle, label: action.label });
2391
+ if (!ok) return errorResult("Declined by user");
2392
+ }
2393
+ const res = adapter.click(handle);
2394
+ if (!res.ok) return errorResult(res.error ?? `Could not click ${handle}`);
2395
+ return textResult(`Clicked ${handle}`, { handle });
2396
+ },
2397
+ (args) => target(`Click ${String(args.handle ?? "")}`, String(args.handle ?? ""))
2398
+ );
2399
+ reg(
2400
+ "page_submit",
2401
+ "Submit a form by handle. Always staged for the human to confirm when pendingMode is on.",
2402
+ { handle: { type: "string" } },
2403
+ ["handle"],
2404
+ async (args) => {
2405
+ const handle = String(args.handle ?? "");
2406
+ if (pendingMode && adapter.confirm) {
2407
+ const ok = await adapter.confirm({ action: "submit", handle, label: handle });
2408
+ if (!ok) return errorResult("Declined by user");
2409
+ }
2410
+ const res = await adapter.submit(handle);
2411
+ if (!res.ok) return errorResult(res.error ?? "Submit failed");
2412
+ return textResult(`Submitted ${handle}`, { handle });
2413
+ },
2414
+ (args) => target(`Submit ${String(args.handle ?? "")}`, String(args.handle ?? ""))
2415
+ );
2416
+ return {
2417
+ id: "navigation",
2418
+ title: "Co-browsing",
2419
+ dispose: () => {
2420
+ for (const d of disposers) d();
2421
+ }
2422
+ };
2423
+ }
2424
+
2425
+ // src/sharing/token.ts
2426
+ var TOKEN_BYTES = 24;
2427
+ function createSessionDescriptor() {
2428
+ const id = randomId(8);
2429
+ const token = randomToken();
2430
+ return { id, token, display: token.slice(0, 8) };
2431
+ }
2432
+ function describeSession(id, token) {
2433
+ return { id, token, display: token.slice(0, 8) };
2434
+ }
2435
+ function buildShareUrl(descriptor, baseUrl = typeof window !== "undefined" ? window.location.href.split("?")[0] : "") {
2436
+ const u = new URL(baseUrl);
2437
+ u.searchParams.set("session", descriptor.id);
2438
+ u.searchParams.set("token", descriptor.token);
2439
+ return u.toString();
2440
+ }
2441
+ function buildShareConfig(descriptor, transport = "broadcast-channel") {
2442
+ return {
2443
+ name: `whiteboard-${descriptor.id}`,
2444
+ transport,
2445
+ session: descriptor.id,
2446
+ token: descriptor.token,
2447
+ channel: `fai:share:${descriptor.id}`,
2448
+ protocol_version: "2025-06-18"
2449
+ };
2450
+ }
2451
+ function readSessionFromUrl() {
2452
+ if (typeof window === "undefined") return null;
2453
+ const params = new URL(window.location.href).searchParams;
2454
+ const id = params.get("session");
2455
+ const token = params.get("token");
2456
+ if (!id || !token) return null;
2457
+ return describeSession(id, token);
2458
+ }
2459
+ function randomToken() {
2460
+ const bytes = new Uint8Array(TOKEN_BYTES);
2461
+ crypto.getRandomValues(bytes);
2462
+ return base64Url(bytes);
2463
+ }
2464
+ function randomId(len) {
2465
+ const bytes = new Uint8Array(Math.ceil(len * 3 / 4));
2466
+ crypto.getRandomValues(bytes);
2467
+ return base64Url(bytes).slice(0, len);
2468
+ }
2469
+ function base64Url(bytes) {
2470
+ let s = "";
2471
+ for (const b of bytes) s += String.fromCharCode(b);
2472
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2473
+ }
2474
+ function constantTimeEqual(a, b) {
2475
+ if (a.length !== b.length) return false;
2476
+ let diff = 0;
2477
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
2478
+ return diff === 0;
2479
+ }
2480
+
2481
+ // src/sharing/sse-relay.ts
2482
+ var SseRelayTransport = class {
2483
+ constructor(options) {
2484
+ this.sendQueue = [];
2485
+ this.connected = false;
2486
+ this.listeners = /* @__PURE__ */ new Set();
2487
+ this.state = "idle";
2488
+ this.opts = options;
2489
+ this.expectedToken = options.token;
2490
+ }
2491
+ bindServer(server) {
2492
+ this.server = server;
2493
+ }
2494
+ /** Open the SSE channel. Idempotent. */
2495
+ start() {
2496
+ if (this.connected || typeof window === "undefined") return;
2497
+ const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;
2498
+ this.setState("connecting");
2499
+ const es = new EventSource(url, { withCredentials: false });
2500
+ this.es = es;
2501
+ es.addEventListener("open", () => {
2502
+ this.connected = true;
2503
+ this.setState("open");
2504
+ const queued = this.sendQueue.splice(0);
2505
+ for (const msg of queued) this.postOut(msg);
2506
+ });
2507
+ es.addEventListener("mcp", (ev) => {
2508
+ const raw = ev.data;
2509
+ this.handleInbound(raw);
2510
+ });
2511
+ es.addEventListener("error", () => {
2512
+ this.setState("error");
2513
+ });
2514
+ }
2515
+ send(message) {
2516
+ if (!this.connected) {
2517
+ this.sendQueue.push(message);
2518
+ return;
2519
+ }
2520
+ this.postOut(message);
2521
+ }
2522
+ close() {
2523
+ this.es?.close();
2524
+ this.es = void 0;
2525
+ this.connected = false;
2526
+ this.setState("closed");
2527
+ }
2528
+ onStateChange(listener) {
2529
+ this.listeners.add(listener);
2530
+ listener(this.state);
2531
+ return () => this.listeners.delete(listener);
2532
+ }
2533
+ /**
2534
+ * For relays that wrap each frame with auth metadata: hosts can call this
2535
+ * directly when a frame arrives via a non-SSE path. The transport will
2536
+ * dispatch it to the bound server.
2537
+ */
2538
+ async deliverFromRemote(payload, token) {
2539
+ if (token !== void 0 && !constantTimeEqual(token, this.expectedToken)) return;
2540
+ if (!this.server) throw new Error("SseRelayTransport has no bound server");
2541
+ const message = typeof payload === "string" ? JSON.parse(payload) : payload;
2542
+ await this.server.receive(this, message);
2543
+ }
2544
+ async postOut(message) {
2545
+ const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;
2546
+ const f = this.opts.fetch ?? fetch;
2547
+ try {
2548
+ await f(url, {
2549
+ method: "POST",
2550
+ headers: { "content-type": "application/json", "accept": "application/json" },
2551
+ body: JSON.stringify(message)
2552
+ });
2553
+ } catch {
2554
+ }
2555
+ }
2556
+ async handleInbound(raw) {
2557
+ if (!this.server) return;
2558
+ let message;
2559
+ try {
2560
+ message = JSON.parse(raw);
2561
+ } catch {
2562
+ return;
2563
+ }
2564
+ await this.server.receive(this, message);
2565
+ }
2566
+ setState(state) {
2567
+ this.state = state;
2568
+ for (const l of this.listeners) l(state);
2569
+ }
2570
+ };
2571
+ function attachSseRelay(server, options) {
2572
+ const transport = new SseRelayTransport(options);
2573
+ transport.bindServer(server);
2574
+ server.attach(transport);
2575
+ transport.start();
2576
+ Promise.resolve().then(() => (init_registry(), registry_exports)).then(({ onActivity: onActivity2 }) => {
2577
+ const off = onActivity2((event) => {
2578
+ transport.send({
2579
+ jsonrpc: "2.0",
2580
+ method: "notifications/agent_activity",
2581
+ params: event
2582
+ });
2583
+ });
2584
+ const origClose = transport.close.bind(transport);
2585
+ transport.close = () => {
2586
+ off();
2587
+ origClose();
2588
+ };
2589
+ }).catch(() => {
2590
+ });
2591
+ return transport;
2592
+ }
2593
+
2594
+ // src/sharing/use-co-browse-session.ts
2595
+ init_registry();
2596
+ var DEFAULT_AGENT10 = { id: "agent", name: "Agent", color: "#a855f7" };
2597
+ var USER = { id: "human", name: "You" };
2598
+ function useCoBrowseSession(options) {
2599
+ const { adapter, extraBridges } = options;
2600
+ const agent = { ...DEFAULT_AGENT10, ...options.agent ?? {} };
2601
+ const relayBaseUrl = options.relayBaseUrl ?? "/whiteboard-share";
2602
+ const serverRef = react.useRef(null);
2603
+ const relayRef = react.useRef(null);
2604
+ const detachInProc = react.useRef(null);
2605
+ const disposeBridge = react.useRef(null);
2606
+ const [session, setSession] = react.useState(null);
2607
+ const [relayState, setRelayState] = react.useState("idle");
2608
+ react.useEffect(() => {
2609
+ const server = new MicroMcpServer({
2610
+ info: options.info ?? { name: "fancy-co-browse", version: "0.1.0" },
2611
+ instructions: options.info?.instructions ?? `Co-browse with a watching human. Call page_describe first; navigate, scroll, and (with confirm) fill/click via stable handles. You receive notifications/agent_activity for the human's actions (source:"user").`
2612
+ });
2613
+ const bridge = registerNavigationBridge(server, { adapter, agent, pendingMode: options.pendingMode });
2614
+ extraBridges?.(server);
2615
+ const inProc = attachInProcess(server);
2616
+ detachInProc.current = () => inProc.close();
2617
+ disposeBridge.current = bridge.dispose;
2618
+ serverRef.current = server;
2619
+ return () => {
2620
+ relayRef.current?.close();
2621
+ relayRef.current = null;
2622
+ disposeBridge.current?.();
2623
+ detachInProc.current?.();
2624
+ serverRef.current = null;
2625
+ };
2626
+ }, []);
2627
+ const startShare = react.useCallback(async () => {
2628
+ const server = serverRef.current;
2629
+ if (!server || relayRef.current) return;
2630
+ const descriptor = createSessionDescriptor();
2631
+ const csrf = options.csrfToken?.() ?? "";
2632
+ await fetch(`${relayBaseUrl}/register`, {
2633
+ method: "POST",
2634
+ headers: { "content-type": "application/json", "x-csrf-token": csrf },
2635
+ body: JSON.stringify({ session: descriptor.id, token: descriptor.token })
2636
+ });
2637
+ const relay = attachSseRelay(server, { baseUrl: relayBaseUrl, sessionId: descriptor.id, token: descriptor.token });
2638
+ relay.onStateChange(setRelayState);
2639
+ relayRef.current = relay;
2640
+ setSession(descriptor);
2641
+ }, [relayBaseUrl, options]);
2642
+ const stopShare = react.useCallback(() => {
2643
+ const current = session;
2644
+ relayRef.current?.close();
2645
+ relayRef.current = null;
2646
+ setRelayState("idle");
2647
+ setSession(null);
2648
+ if (current) {
2649
+ const csrf = options.csrfToken?.() ?? "";
2650
+ void fetch(`${relayBaseUrl}/${current.id}/unregister`, {
2651
+ method: "POST",
2652
+ headers: { "content-type": "application/json", "x-csrf-token": csrf },
2653
+ body: JSON.stringify({ token: current.token })
2654
+ }).catch(() => {
2655
+ });
2656
+ }
2657
+ }, [relayBaseUrl, options, session]);
2658
+ const observeUser = react.useCallback((event) => {
2659
+ const label = event.kind === "navigation" ? `You navigated to ${event.url}` : event.kind === "scroll" ? "You scrolled" : `You edited ${event.handle}${event.masked ? " (hidden)" : ""}`;
2660
+ fancyAutoCommon.emitActivity({
2661
+ agentId: USER.id,
2662
+ agentName: USER.name,
2663
+ source: "user",
2664
+ target: { kind: "navigation", label },
2665
+ action: `user_${event.kind}`,
2666
+ timestamp: Date.now(),
2667
+ meta: event
2668
+ });
2669
+ }, []);
2670
+ return { server: serverRef.current, session, relayState, startShare, stopShare, observeUser };
2671
+ }
2219
2672
  function AgentPanel({ agent, activity, onSubmit, busy, actions, className, style }) {
2220
2673
  const scrollRef = react.useRef(null);
2221
2674
  const inputRef = react.useRef(null);
@@ -2474,62 +2927,6 @@ function ScreensActivityBridge({ system, fadeMs = 1500 }) {
2474
2927
  }, [system, fadeMs]);
2475
2928
  return null;
2476
2929
  }
2477
-
2478
- // src/sharing/token.ts
2479
- var TOKEN_BYTES = 24;
2480
- function createSessionDescriptor() {
2481
- const id = randomId(8);
2482
- const token = randomToken();
2483
- return { id, token, display: token.slice(0, 8) };
2484
- }
2485
- function describeSession(id, token) {
2486
- return { id, token, display: token.slice(0, 8) };
2487
- }
2488
- function buildShareUrl(descriptor, baseUrl = typeof window !== "undefined" ? window.location.href.split("?")[0] : "") {
2489
- const u = new URL(baseUrl);
2490
- u.searchParams.set("session", descriptor.id);
2491
- u.searchParams.set("token", descriptor.token);
2492
- return u.toString();
2493
- }
2494
- function buildShareConfig(descriptor, transport = "broadcast-channel") {
2495
- return {
2496
- name: `whiteboard-${descriptor.id}`,
2497
- transport,
2498
- session: descriptor.id,
2499
- token: descriptor.token,
2500
- channel: `fai:share:${descriptor.id}`,
2501
- protocol_version: "2025-06-18"
2502
- };
2503
- }
2504
- function readSessionFromUrl() {
2505
- if (typeof window === "undefined") return null;
2506
- const params = new URL(window.location.href).searchParams;
2507
- const id = params.get("session");
2508
- const token = params.get("token");
2509
- if (!id || !token) return null;
2510
- return describeSession(id, token);
2511
- }
2512
- function randomToken() {
2513
- const bytes = new Uint8Array(TOKEN_BYTES);
2514
- crypto.getRandomValues(bytes);
2515
- return base64Url(bytes);
2516
- }
2517
- function randomId(len) {
2518
- const bytes = new Uint8Array(Math.ceil(len * 3 / 4));
2519
- crypto.getRandomValues(bytes);
2520
- return base64Url(bytes).slice(0, len);
2521
- }
2522
- function base64Url(bytes) {
2523
- let s = "";
2524
- for (const b of bytes) s += String.fromCharCode(b);
2525
- return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2526
- }
2527
- function constantTimeEqual(a, b) {
2528
- if (a.length !== b.length) return false;
2529
- let diff = 0;
2530
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
2531
- return diff === 0;
2532
- }
2533
2930
  function ShareControls({
2534
2931
  session,
2535
2932
  onStart,
@@ -2649,9 +3046,6 @@ function buildCurlRecipe(session) {
2649
3046
  ].join("\n");
2650
3047
  }
2651
3048
 
2652
- // src/presence/index.ts
2653
- init_registry();
2654
-
2655
3049
  // src/presence/use-agent-activity.ts
2656
3050
  init_registry();
2657
3051
  function useAgentActivity(filter, options = {}) {
@@ -2684,6 +3078,314 @@ function useAgentActivityForScreen(screenId, options = {}) {
2684
3078
  }, [latest, fadeAfter]);
2685
3079
  return { events, latest, isAgentActive };
2686
3080
  }
3081
+
3082
+ // src/connectors/targets.ts
3083
+ var CLAUDE_CONNECTORS_URL = "https://claude.ai/settings/connectors";
3084
+ function encodeBase64Json(value) {
3085
+ const json = JSON.stringify(value);
3086
+ if (typeof btoa === "function") {
3087
+ return btoa(unescape(encodeURIComponent(json)));
3088
+ }
3089
+ return Buffer.from(json, "utf8").toString("base64");
3090
+ }
3091
+ function buildCursorDeeplink(server) {
3092
+ const config = encodeBase64Json({ url: server.url });
3093
+ return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(
3094
+ server.name
3095
+ )}&config=${config}`;
3096
+ }
3097
+ function buildVscodeDeeplink(server, opts = {}) {
3098
+ const scheme = opts.insiders ? "vscode-insiders" : "vscode";
3099
+ const payload = encodeURIComponent(
3100
+ JSON.stringify({ name: server.name, url: server.url })
3101
+ );
3102
+ return `${scheme}://mcp/install?${payload}`;
3103
+ }
3104
+ function slugifyServerName(name) {
3105
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3106
+ return slug || "mcp-server";
3107
+ }
3108
+ function buildManualConfig(server) {
3109
+ return {
3110
+ mcpServers: {
3111
+ [slugifyServerName(server.name)]: {
3112
+ command: "npx",
3113
+ args: ["-y", "mcp-remote", server.url]
3114
+ }
3115
+ }
3116
+ };
3117
+ }
3118
+ function buildManualConfigSnippet(server) {
3119
+ return JSON.stringify(buildManualConfig(server), null, 2);
3120
+ }
3121
+ var CONNECTOR_TARGETS = {
3122
+ "claude-web": {
3123
+ id: "claude-web",
3124
+ label: "Add to Claude",
3125
+ mechanism: "copy-open",
3126
+ hint: "Copy the MCP URL and open Claude's Connectors page \u2014 click 'Add custom connector' and paste."
3127
+ },
3128
+ "claude-desktop": {
3129
+ id: "claude-desktop",
3130
+ label: "Claude Desktop",
3131
+ mechanism: "download",
3132
+ hint: "Download a .mcpb bundle and double-click it to install in Claude Desktop."
3133
+ },
3134
+ cursor: {
3135
+ id: "cursor",
3136
+ label: "Add to Cursor",
3137
+ mechanism: "deeplink",
3138
+ hint: "Open Cursor with this MCP server pre-filled \u2014 confirm to install."
3139
+ },
3140
+ vscode: {
3141
+ id: "vscode",
3142
+ label: "Add to VS Code",
3143
+ mechanism: "deeplink",
3144
+ hint: "Open VS Code with this MCP server pre-filled \u2014 confirm to install."
3145
+ },
3146
+ manual: {
3147
+ id: "manual",
3148
+ label: "Manual setup",
3149
+ mechanism: "snippet",
3150
+ hint: "Show a config snippet to paste into any stdio MCP client."
3151
+ }
3152
+ };
3153
+ function connectorHref(client, server, opts = {}) {
3154
+ switch (client) {
3155
+ case "cursor":
3156
+ return buildCursorDeeplink(server);
3157
+ case "vscode":
3158
+ return buildVscodeDeeplink(server, opts);
3159
+ default:
3160
+ return null;
3161
+ }
3162
+ }
3163
+ function ClaudeMark(props) {
3164
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2 L 14 10 L 22 12 L 14 14 L 12 22 L 10 14 L 2 12 L 10 10 Z" }) });
3165
+ }
3166
+ function CursorMark(props) {
3167
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3 L 20 11 L 12 13 L 9 21 Z" }) });
3168
+ }
3169
+ function VscodeMark(props) {
3170
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 2 L 22 4.5 V 19.5 L 17 22 L 6.5 13.2 L 3 16 L 1.5 15 V 9 L 3 8 L 6.5 10.8 Z M 17 6.5 L 10 12 L 17 17.5 Z" }) });
3171
+ }
3172
+ function DesktopMark(props) {
3173
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 4 H 21 A 1 1 0 0 1 22 5 V 16 A 1 1 0 0 1 21 17 H 14 V 19 H 16 V 21 H 8 V 19 H 10 V 17 H 3 A 1 1 0 0 1 2 16 V 5 A 1 1 0 0 1 3 4 Z" }) });
3174
+ }
3175
+ function WrenchMark(props) {
3176
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 4 a 5 5 0 0 1 -6.5 6.5 L 6 19 l -3 -3 l 8.5 -8.5 A 5 5 0 0 1 17 1 l -2.5 2.5 l 1.5 3 l 3 1.5 Z" }) });
3177
+ }
3178
+ var CONNECTOR_GLYPHS = {
3179
+ "claude-web": ClaudeMark,
3180
+ "claude-desktop": DesktopMark,
3181
+ cursor: CursorMark,
3182
+ vscode: VscodeMark,
3183
+ manual: WrenchMark
3184
+ };
3185
+ var DEFAULT_CLIENTS = [
3186
+ "claude-web",
3187
+ "cursor",
3188
+ "vscode",
3189
+ "manual"
3190
+ ];
3191
+ function ConnectorButtons({
3192
+ serverName,
3193
+ mcpUrl,
3194
+ clients,
3195
+ mcpbDownloadUrl,
3196
+ claudeConnectorsUrl = CLAUDE_CONNECTORS_URL,
3197
+ vscodeInsiders,
3198
+ onCopy,
3199
+ onAction,
3200
+ labels,
3201
+ className,
3202
+ style
3203
+ }) {
3204
+ const server = { name: serverName, url: mcpUrl };
3205
+ const [copied, setCopied] = react.useState(null);
3206
+ const [manualOpen, setManualOpen] = react.useState(false);
3207
+ const manualId = react.useId();
3208
+ const list = (clients ?? defaultClients(mcpbDownloadUrl)).filter(
3209
+ (c) => c === "claude-desktop" ? !!mcpbDownloadUrl : true
3210
+ );
3211
+ const flashCopied = (target) => {
3212
+ setCopied(target);
3213
+ window.setTimeout(() => setCopied((c) => c === target ? null : c), 2e3);
3214
+ };
3215
+ const copy = async (value, target) => {
3216
+ try {
3217
+ await navigator.clipboard?.writeText(value);
3218
+ flashCopied(target);
3219
+ onCopy?.(target);
3220
+ } catch {
3221
+ }
3222
+ };
3223
+ const labelFor = (c) => labels?.[c] ?? CONNECTOR_TARGETS[c].label;
3224
+ return /* @__PURE__ */ jsxRuntime.jsx(
3225
+ "div",
3226
+ {
3227
+ className: ["fai-connect", className].filter(Boolean).join(" "),
3228
+ style,
3229
+ children: list.map((client) => {
3230
+ const meta = CONNECTOR_TARGETS[client];
3231
+ const Glyph = CONNECTOR_GLYPHS[client];
3232
+ const base = `fai-connect__btn fai-connect__btn--${client}`;
3233
+ const href = connectorHref(client, server, { insiders: vscodeInsiders });
3234
+ if (href) {
3235
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3236
+ "a",
3237
+ {
3238
+ href,
3239
+ className: base,
3240
+ title: meta.hint,
3241
+ onClick: () => onAction?.(client),
3242
+ children: [
3243
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
3244
+ labelFor(client)
3245
+ ]
3246
+ },
3247
+ client
3248
+ );
3249
+ }
3250
+ if (client === "claude-desktop") {
3251
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3252
+ "a",
3253
+ {
3254
+ href: mcpbDownloadUrl,
3255
+ download: true,
3256
+ className: base,
3257
+ title: meta.hint,
3258
+ onClick: () => onAction?.(client),
3259
+ children: [
3260
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
3261
+ labelFor(client)
3262
+ ]
3263
+ },
3264
+ client
3265
+ );
3266
+ }
3267
+ if (client === "claude-web") {
3268
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3269
+ "button",
3270
+ {
3271
+ type: "button",
3272
+ className: base,
3273
+ title: meta.hint,
3274
+ onClick: () => {
3275
+ void copy(mcpUrl, client);
3276
+ window.open(
3277
+ claudeConnectorsUrl,
3278
+ "_blank",
3279
+ "noopener,noreferrer"
3280
+ );
3281
+ onAction?.(client);
3282
+ },
3283
+ children: [
3284
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
3285
+ copied === client ? "Copied \u2014 paste in Claude" : labelFor(client)
3286
+ ]
3287
+ },
3288
+ client
3289
+ );
3290
+ }
3291
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-connect__manual-wrap", children: [
3292
+ /* @__PURE__ */ jsxRuntime.jsxs(
3293
+ "button",
3294
+ {
3295
+ type: "button",
3296
+ className: base,
3297
+ title: meta.hint,
3298
+ "aria-expanded": manualOpen,
3299
+ "aria-controls": manualId,
3300
+ onClick: () => {
3301
+ setManualOpen((o) => !o);
3302
+ onAction?.(client);
3303
+ },
3304
+ children: [
3305
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
3306
+ labelFor(client)
3307
+ ]
3308
+ }
3309
+ ),
3310
+ manualOpen && /* @__PURE__ */ jsxRuntime.jsx(
3311
+ ManualPopover,
3312
+ {
3313
+ id: manualId,
3314
+ snippet: buildManualConfigSnippet(server),
3315
+ copied: copied === client,
3316
+ onCopy: () => copy(buildManualConfigSnippet(server), client),
3317
+ onClose: () => setManualOpen(false)
3318
+ }
3319
+ )
3320
+ ] }, client);
3321
+ })
3322
+ }
3323
+ );
3324
+ }
3325
+ function defaultClients(mcpbDownloadUrl) {
3326
+ return mcpbDownloadUrl ? ["claude-web", "claude-desktop", "cursor", "vscode", "manual"] : DEFAULT_CLIENTS;
3327
+ }
3328
+ function ManualPopover({
3329
+ id,
3330
+ snippet,
3331
+ copied,
3332
+ onCopy,
3333
+ onClose
3334
+ }) {
3335
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id, className: "fai-connect__popover", role: "dialog", children: [
3336
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-connect__popover-head", children: [
3337
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add to any stdio MCP client" }),
3338
+ /* @__PURE__ */ jsxRuntime.jsx(
3339
+ "button",
3340
+ {
3341
+ type: "button",
3342
+ className: "fai-connect__popover-close",
3343
+ "aria-label": "Close",
3344
+ onClick: onClose,
3345
+ children: "\xD7"
3346
+ }
3347
+ )
3348
+ ] }),
3349
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "fai-connect__popover-hint", children: [
3350
+ "Paste into ",
3351
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "claude_desktop_config.json" }),
3352
+ " (or any stdio MCP client config). Needs Node 18+."
3353
+ ] }),
3354
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "fai-connect__snippet", children: snippet }),
3355
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "fai-connect__copy-btn", onClick: onCopy, children: copied ? "Copied" : "Copy snippet" })
3356
+ ] });
3357
+ }
3358
+ function CoBrowsePresence({ session, connectUrl, shareBaseUrl, className }) {
3359
+ const { events } = useAgentActivity(void 0, { capacity: 40 });
3360
+ const lastAgentAction = [...events].reverse().find((e) => (e.source ?? "agent") !== "user");
3361
+ const connected = session.relayState === "open";
3362
+ if (!session.session) {
3363
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, "data-co-browse-presence": "idle", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void session.startShare(), "data-co-browse-start": true, children: "Let an agent drive" }) });
3364
+ }
3365
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, "data-co-browse-presence": connected ? "connected" : "waiting", children: [
3366
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-co-browse-bar": true, children: [
3367
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-co-browse-dot": true, "data-state": session.relayState }),
3368
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-co-browse-status": true, children: connected ? "Agent is driving" : `Waiting for an agent\u2026 (${session.relayState})` }),
3369
+ lastAgentAction && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-co-browse-last": true, children: lastAgentAction.target?.label ?? lastAgentAction.action }),
3370
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: session.stopShare, "data-co-browse-stop": true, children: "Stop" })
3371
+ ] }),
3372
+ /* @__PURE__ */ jsxRuntime.jsx(
3373
+ ShareControls,
3374
+ {
3375
+ session: session.session,
3376
+ onStart: () => void session.startShare(),
3377
+ onStop: session.stopShare,
3378
+ status: session.relayState,
3379
+ shareBaseUrl
3380
+ }
3381
+ ),
3382
+ connectUrl && /* @__PURE__ */ jsxRuntime.jsx(ConnectorButtons, { serverName: "Fancy UI co-browse", mcpUrl: connectUrl })
3383
+ ] });
3384
+ }
3385
+ CoBrowsePresence.displayName = "CoBrowsePresence";
3386
+
3387
+ // src/presence/index.ts
3388
+ init_registry();
2687
3389
  function useUndoStack(agentId, intervalMs = 500) {
2688
3390
  const [history, setHistory] = react.useState(() => fancyAutoCommon.readHistory(agentId));
2689
3391
  react.useEffect(() => {
@@ -2703,117 +3405,63 @@ function useUndoStack(agentId, intervalMs = 500) {
2703
3405
  return { history, refresh };
2704
3406
  }
2705
3407
 
2706
- // src/sharing/sse-relay.ts
2707
- var SseRelayTransport = class {
2708
- constructor(options) {
2709
- this.sendQueue = [];
2710
- this.connected = false;
2711
- this.listeners = /* @__PURE__ */ new Set();
2712
- this.state = "idle";
2713
- this.opts = options;
2714
- this.expectedToken = options.token;
2715
- }
2716
- bindServer(server) {
2717
- this.server = server;
2718
- }
2719
- /** Open the SSE channel. Idempotent. */
2720
- start() {
2721
- if (this.connected || typeof window === "undefined") return;
2722
- const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;
2723
- this.setState("connecting");
2724
- const es = new EventSource(url, { withCredentials: false });
2725
- this.es = es;
2726
- es.addEventListener("open", () => {
2727
- this.connected = true;
2728
- this.setState("open");
2729
- const queued = this.sendQueue.splice(0);
2730
- for (const msg of queued) this.postOut(msg);
2731
- });
2732
- es.addEventListener("mcp", (ev) => {
2733
- const raw = ev.data;
2734
- this.handleInbound(raw);
2735
- });
2736
- es.addEventListener("error", () => {
2737
- this.setState("error");
2738
- });
2739
- }
2740
- send(message) {
2741
- if (!this.connected) {
2742
- this.sendQueue.push(message);
2743
- return;
2744
- }
2745
- this.postOut(message);
2746
- }
2747
- close() {
2748
- this.es?.close();
2749
- this.es = void 0;
2750
- this.connected = false;
2751
- this.setState("closed");
2752
- }
2753
- onStateChange(listener) {
2754
- this.listeners.add(listener);
2755
- listener(this.state);
2756
- return () => this.listeners.delete(listener);
2757
- }
2758
- /**
2759
- * For relays that wrap each frame with auth metadata: hosts can call this
2760
- * directly when a frame arrives via a non-SSE path. The transport will
2761
- * dispatch it to the bound server.
2762
- */
2763
- async deliverFromRemote(payload, token) {
2764
- if (token !== void 0 && !constantTimeEqual(token, this.expectedToken)) return;
2765
- if (!this.server) throw new Error("SseRelayTransport has no bound server");
2766
- const message = typeof payload === "string" ? JSON.parse(payload) : payload;
2767
- await this.server.receive(this, message);
2768
- }
2769
- async postOut(message) {
2770
- const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;
2771
- const f = this.opts.fetch ?? fetch;
2772
- try {
2773
- await f(url, {
2774
- method: "POST",
2775
- headers: { "content-type": "application/json", "accept": "application/json" },
2776
- body: JSON.stringify(message)
2777
- });
2778
- } catch {
2779
- }
2780
- }
2781
- async handleInbound(raw) {
2782
- if (!this.server) return;
2783
- let message;
2784
- try {
2785
- message = JSON.parse(raw);
2786
- } catch {
2787
- return;
3408
+ // src/connectors/mcpb.ts
3409
+ var MCPB_MANIFEST_VERSION = "0.2";
3410
+ var MCPB_MIN_NODE = ">=18.0.0";
3411
+ var DEFAULT_MCPB_ENTRY_POINT = "server/proxy.js";
3412
+ function buildMcpbManifest(input) {
3413
+ const entryPoint = input.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;
3414
+ return {
3415
+ manifest_version: MCPB_MANIFEST_VERSION,
3416
+ name: input.name,
3417
+ display_name: input.display_name ?? input.name,
3418
+ version: input.version,
3419
+ description: input.description,
3420
+ ...input.long_description ? { long_description: input.long_description } : {},
3421
+ author: input.author,
3422
+ ...input.homepage ? { homepage: input.homepage } : {},
3423
+ ...input.documentation ? { documentation: input.documentation } : {},
3424
+ ...input.support ? { support: input.support } : {},
3425
+ server: {
3426
+ type: "node",
3427
+ entry_point: entryPoint,
3428
+ mcp_config: {
3429
+ command: "npx",
3430
+ args: ["-y", "mcp-remote", input.mcpUrl]
3431
+ }
3432
+ },
3433
+ tools: input.tools ?? [],
3434
+ tools_generated: false,
3435
+ prompts_generated: false,
3436
+ ...input.keywords ? { keywords: input.keywords } : {},
3437
+ license: input.license ?? "MIT",
3438
+ compatibility: {
3439
+ claude_desktop: ">=0.10.0",
3440
+ platforms: ["darwin", "win32", "linux"],
3441
+ runtimes: { node: MCPB_MIN_NODE }
2788
3442
  }
2789
- await this.server.receive(this, message);
2790
- }
2791
- setState(state) {
2792
- this.state = state;
2793
- for (const l of this.listeners) l(state);
2794
- }
2795
- };
2796
- function attachSseRelay(server, options) {
2797
- const transport = new SseRelayTransport(options);
2798
- transport.bindServer(server);
2799
- server.attach(transport);
2800
- transport.start();
2801
- Promise.resolve().then(() => (init_registry(), registry_exports)).then(({ onActivity: onActivity2 }) => {
2802
- const off = onActivity2((event) => {
2803
- transport.send({
2804
- jsonrpc: "2.0",
2805
- method: "notifications/agent_activity",
2806
- params: event
2807
- });
2808
- });
2809
- const origClose = transport.close.bind(transport);
2810
- transport.close = () => {
2811
- off();
2812
- origClose();
2813
- };
2814
- }).catch(() => {
2815
- });
2816
- return transport;
3443
+ };
3444
+ }
3445
+ function buildMcpbProxyStub(mcpUrl) {
3446
+ const urlLiteral = JSON.stringify(mcpUrl);
3447
+ return `#!/usr/bin/env node
3448
+ // MCPB proxy shim (generated by @particle-academy/agent-integrations).
3449
+ //
3450
+ // MCPB (Claude Desktop Extensions) only supports local stdio servers, but this
3451
+ // MCP server is a remote HTTP endpoint. The manifest's \`mcp_config\` invokes
3452
+ // \`npx -y mcp-remote <url>\` to bridge the gap \u2014 this file is the entry_point
3453
+ // fallback the manifest validator requires. If you're seeing this run,
3454
+ // mcp_config wasn't honored; spawn mcp-remote directly so the bundle still works.
3455
+
3456
+ const { spawn } = require("node:child_process");
3457
+
3458
+ const url = ${urlLiteral};
3459
+ const child = spawn("npx", ["-y", "mcp-remote", url], { stdio: "inherit" });
3460
+
3461
+ child.on("exit", (code) => process.exit(code ?? 0));
3462
+ process.on("SIGINT", () => child.kill("SIGINT"));
3463
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
3464
+ `;
2817
3465
  }
2818
3466
 
2819
3467
  Object.defineProperty(exports, "clearUndoStack", {
@@ -2860,7 +3508,18 @@ exports.AgentActivityHighlight = AgentActivityHighlight;
2860
3508
  exports.AgentCursor = AgentCursor;
2861
3509
  exports.AgentPanel = AgentPanel;
2862
3510
  exports.BridgedForm = BridgedForm;
3511
+ exports.CLAUDE_CONNECTORS_URL = CLAUDE_CONNECTORS_URL;
3512
+ exports.CONNECTOR_GLYPHS = CONNECTOR_GLYPHS;
3513
+ exports.CONNECTOR_TARGETS = CONNECTOR_TARGETS;
3514
+ exports.ClaudeMark = ClaudeMark;
3515
+ exports.CoBrowsePresence = CoBrowsePresence;
3516
+ exports.ConnectorButtons = ConnectorButtons;
3517
+ exports.CursorMark = CursorMark;
3518
+ exports.DEFAULT_MCPB_ENTRY_POINT = DEFAULT_MCPB_ENTRY_POINT;
3519
+ exports.DesktopMark = DesktopMark;
2863
3520
  exports.InProcessTransport = InProcessTransport;
3521
+ exports.MCPB_MANIFEST_VERSION = MCPB_MANIFEST_VERSION;
3522
+ exports.MCPB_MIN_NODE = MCPB_MIN_NODE;
2864
3523
  exports.MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
2865
3524
  exports.MicroMcpServer = MicroMcpServer;
2866
3525
  exports.RelayTransport = RelayTransport;
@@ -2868,19 +3527,30 @@ exports.ScreensActivityBridge = ScreensActivityBridge;
2868
3527
  exports.ShareControls = ShareControls;
2869
3528
  exports.SseRelayTransport = SseRelayTransport;
2870
3529
  exports.ToolRegistry = ToolRegistry;
3530
+ exports.VscodeMark = VscodeMark;
3531
+ exports.WrenchMark = WrenchMark;
2871
3532
  exports.attachInProcess = attachInProcess;
2872
3533
  exports.attachRelay = attachRelay;
2873
3534
  exports.attachSseRelay = attachSseRelay;
3535
+ exports.buildCursorDeeplink = buildCursorDeeplink;
3536
+ exports.buildManualConfig = buildManualConfig;
3537
+ exports.buildManualConfigSnippet = buildManualConfigSnippet;
3538
+ exports.buildMcpbManifest = buildMcpbManifest;
3539
+ exports.buildMcpbProxyStub = buildMcpbProxyStub;
2874
3540
  exports.buildShareConfig = buildShareConfig;
2875
3541
  exports.buildShareUrl = buildShareUrl;
3542
+ exports.buildVscodeDeeplink = buildVscodeDeeplink;
3543
+ exports.connectorHref = connectorHref;
2876
3544
  exports.createSessionDescriptor = createSessionDescriptor;
2877
3545
  exports.describeSession = describeSession;
3546
+ exports.encodeBase64Json = encodeBase64Json;
2878
3547
  exports.ensureUndoToolsRegistered = ensureUndoToolsRegistered;
2879
3548
  exports.errorResult = errorResult;
2880
3549
  exports.readSessionFromUrl = readSessionFromUrl;
2881
3550
  exports.registerChartsBridge = registerChartsBridge;
2882
3551
  exports.registerCodeBridge = registerCodeBridge;
2883
3552
  exports.registerFormBridge = registerFormBridge;
3553
+ exports.registerNavigationBridge = registerNavigationBridge;
2884
3554
  exports.registerSceneBridge = registerSceneBridge;
2885
3555
  exports.registerScreensBridge = registerScreensBridge;
2886
3556
  exports.registerSheetsBridge = registerSheetsBridge;
@@ -2888,9 +3558,11 @@ exports.registerSlidesBridge = registerSlidesBridge;
2888
3558
  exports.registerTerminalBridge = registerTerminalBridge;
2889
3559
  exports.registerUndoTools = registerUndoTools;
2890
3560
  exports.rpcError = rpcError;
3561
+ exports.slugifyServerName = slugifyServerName;
2891
3562
  exports.textResult = textResult;
2892
3563
  exports.useAgentActivity = useAgentActivity;
2893
3564
  exports.useAgentActivityForScreen = useAgentActivityForScreen;
3565
+ exports.useCoBrowseSession = useCoBrowseSession;
2894
3566
  exports.useSheetsActivityHighlights = useSheetsActivityHighlights;
2895
3567
  exports.useSheetsAdapter = useSheetsAdapter;
2896
3568
  exports.useUndoStack = useUndoStack;