@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/README.md +20 -0
- package/dist/chunk-54QEFRMS.js +285 -0
- package/dist/chunk-54QEFRMS.js.map +1 -0
- package/dist/chunk-GO2Y6H6U.js +62 -0
- package/dist/chunk-GO2Y6H6U.js.map +1 -0
- package/dist/connectors/build.d.cts +56 -0
- package/dist/connectors/build.d.ts +56 -0
- package/dist/connectors/index.d.cts +130 -0
- package/dist/connectors/index.d.ts +130 -0
- package/dist/connectors-build.cjs +133 -0
- package/dist/connectors-build.cjs.map +1 -0
- package/dist/connectors-build.js +66 -0
- package/dist/connectors-build.js.map +1 -0
- package/dist/connectors.cjs +366 -0
- package/dist/connectors.cjs.map +1 -0
- package/dist/connectors.js +4 -0
- package/dist/connectors.js.map +1 -0
- package/dist/index.cjs +841 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +199 -6
- package/dist/index.d.ts +199 -6
- package/dist/index.js +325 -7
- package/dist/index.js.map +1 -1
- package/dist/mcpb-BXOrsRnv.d.cts +54 -0
- package/dist/mcpb-BXOrsRnv.d.ts +54 -0
- package/dist/styles.css +156 -0
- package/dist/styles.css.map +1 -1
- package/docs/connectors.md +118 -0
- package/package.json +28 -2
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/
|
|
2707
|
-
var
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
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
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
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;
|