@particle-academy/agent-integrations 0.32.0 → 0.34.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridges/passkeys.ts"],"names":[],"mappings":"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","file":"chunk-7THGQUX3.js","sourcesContent":["import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport type { JsonObject } from \"../mcp/types\";\nimport type { Bridge } from \"./types\";\nimport { wrapToolWithActivity } from \"../presence/wrap-tool-with-activity\";\nimport type { AgentTarget } from \"../presence/types\";\nimport { pushUndoEntry } from \"../undo/undo-stack\";\nimport { ensureUndoToolsRegistered } from \"../undo/undo-tools\";\n\n/**\n * Bridge for `@particle-academy/fancy-passkeys-ui` — the **management** half of\n * a passkey (WebAuthn) surface, and only that half.\n *\n * ## The absence is the design\n *\n * There is **no tool here that completes an authentication or registration\n * ceremony**, and there must never be one. Not `passkey_authenticate`, not\n * `passkey_sign_in`, not `passkey_complete`, not a \"headless\" variant behind a\n * flag.\n *\n * The reason is construction, not policy. `navigator.credentials.get()` requires\n * a user gesture and a biometric or PIN — both things only the person at the\n * keyboard has. An agent-drivable passkey login would be a bypass of the exact\n * property that makes a passkey worth more than a password: that possession of\n * the credential cannot be delegated, replayed, or phished. A bridge offering\n * one would quietly turn a phishing-resistant factor back into a bearer token.\n *\n * So the boundary sits where the cryptography already puts it, and the tool set\n * is drawn to match: **read the management surface, rename, propose, ask the\n * human to start a ceremony.** `bridges/__tests__/passkeys.test.ts` asserts the\n * registered tool names against a closed list, so \"finishing\" this bridge with a\n * sign-in tool fails CI rather than shipping.\n *\n * ## What it does do\n *\n * passkey_list the user's credentials (whitelisted fields only)\n * passkey_status browser support + what the surface currently shows\n * passkey_rename relabel one credential, by credential ID (undoable)\n * passkey_revoke **STAGES** a revoke for the human to confirm\n * passkey_begin_enrollment opens the enrollment prompt; returns immediately\n *\n * `passkey_revoke` never revokes. Revoking a passkey is destructive and revoking\n * the last one is a lockout, so the confirming click comes from the human at the\n * surface — there is deliberately **no `pendingMode: false`**, no `confirm: true`\n * argument, and no host hook that turns staging off. On this bridge staging is\n * not a mode.\n *\n * The adapter type is declared LOCALLY (like `features` / `catalog`) so the\n * bridge builds and type-checks with the sibling package absent. A host wiring a\n * live `PasskeyManager` satisfies it structurally, with no import.\n */\n\n/**\n * One credential as the bridge is willing to describe it — the `PasskeySummary`\n * shape both passkey backends already serialise.\n *\n * Everything the server stores that is NOT on this list — the COSE public key,\n * the user handle, the signature counter, the attestation statement and trust\n * path — is absent on purpose, and {@link toSummary} re-projects onto exactly\n * these keys rather than trusting the adapter to have withheld them. A host that\n * hands `list()` its raw ORM model (the obvious thing to do) therefore cannot\n * leak credential material through an MCP tool by accident.\n */\nexport type PasskeySummaryLike = {\n /** base64url credential ID — the handle every action here is keyed by. */\n id: string;\n /** Human-chosen label, or null when the user never named it. */\n name: string | null;\n /** ISO-8601. */\n createdAt: string;\n /** ISO-8601, or null when never used to sign in. */\n lastUsedAt: string | null;\n /** Transport hints (\"internal\", \"hybrid\", \"usb\", …). */\n transports: string[];\n /** True for a synced passkey (BE/BS flags); false means this device only. */\n backedUp: boolean;\n /** Authenticator model id. Stored, never a trust decision. */\n aaguid: string;\n /** ISO-8601 when the signature counter regressed — a suspected clone. */\n clonedAt: string | null;\n};\n\n/** What the browser can do, as the three `./client` predicates report it. */\nexport type PasskeySupport = {\n supported: boolean;\n /** Touch ID / Windows Hello present? `null` while unknown. */\n platformAuthenticator?: boolean | null;\n /** Can the browser offer a passkey from a username field? `null` while unknown. */\n conditionalUi?: boolean | null;\n};\n\n/** A read of what the management surface is currently showing. */\nexport type PasskeySurfaceState = {\n status?: \"idle\" | \"busy\" | \"error\";\n /** `{ code, message }` as carried in surface state — never an Error. */\n error?: { code: string; message: string } | null;\n /** The revoke awaiting a human's confirmation, if any. */\n pendingRevoke?: { id: string; isLastPasskey: boolean } | null;\n /** Credential ID currently being renamed, if any. */\n renamingId?: string | null;\n};\n\n/**\n * Host-provided access to the management surface. Mirrors the props a\n * `PasskeyManager` is already wired with, so a host usually passes the same four\n * callbacks it passed the component.\n */\nexport type PasskeyBridgeAdapter = {\n /** Stable id for this surface instance. Surfaces in activity logs. */\n id?: string;\n title?: string;\n screenId?: string;\n\n /** The signed-in user's credentials. */\n list(): PasskeySummaryLike[] | Promise<PasskeySummaryLike[]>;\n\n /** Relabel one credential. Cosmetic, reversible, and undoable here. */\n rename(input: { id: string; name: string }): void | Promise<void>;\n\n /**\n * **Stage** a revoke for human confirmation — the `pendingRevoke` the surface\n * renders a confirm/cancel dialog for. It must NOT revoke; a host that wires\n * this to a delete call has removed the human from the loop the bridge was\n * built around.\n */\n proposeRevoke(input: { id: string }): PasskeyProposal | Promise<PasskeyProposal>;\n\n /**\n * Open the enrollment prompt on the human's screen and return immediately.\n * The ceremony completes on their authenticator or not at all.\n */\n beginEnrollment?(input: { name?: string }): void | Promise<void>;\n\n /** Browser capability flags, if the host tracks them. */\n support?(): PasskeySupport | Promise<PasskeySupport>;\n\n /** What the surface currently shows. */\n state?(): PasskeySurfaceState | Promise<PasskeySurfaceState>;\n};\n\nexport type PasskeyProposal = { staged: true; isLastPasskey: boolean };\n\nexport type PasskeyBridgeOptions = {\n adapter: PasskeyBridgeAdapter;\n agent?: { id: string; name?: string; color?: string };\n /**\n * Also route `passkey_rename` through {@link confirm}. Off by default: a\n * rename is cosmetic, reversible and pushed onto the undo stack. Worth turning\n * on where the label itself carries trust — a rogue credential relabelled\n * \"Glenn's iPhone\" survives a human's audit of the list.\n */\n confirmRename?: boolean;\n /**\n * Host confirmation hook for renames, when {@link confirmRename} is on.\n *\n * Note there is no revoke case: a revoke is staged into the surface and\n * confirmed there, never through a hook the agent's own host supplies.\n */\n confirm?: (req: { action: \"passkey_rename\"; id: string; name: string }) => boolean | Promise<boolean>;\n};\n\nconst DEFAULT_AGENT = { id: \"agent\", name: \"Agent\", color: \"#a855f7\" };\n\nconst str = (v: unknown, fallback = \"\"): string => (typeof v === \"string\" ? v : fallback);\n\n/**\n * Project an adapter record onto exactly the public summary fields.\n *\n * Deliberately a whitelist and not a `delete` of known-bad keys: a blacklist has\n * to be updated every time a backend grows a column, and the failure mode of\n * forgetting is silently publishing credential material to every agent in the\n * session.\n */\nfunction toSummary(record: PasskeySummaryLike): PasskeySummaryLike {\n return {\n id: str(record?.id),\n name: typeof record?.name === \"string\" ? record.name : null,\n createdAt: str(record?.createdAt),\n lastUsedAt: typeof record?.lastUsedAt === \"string\" ? record.lastUsedAt : null,\n transports: Array.isArray(record?.transports) ? record.transports.map((t) => String(t)) : [],\n backedUp: record?.backedUp === true,\n aaguid: str(record?.aaguid),\n clonedAt: typeof record?.clonedAt === \"string\" ? record.clonedAt : null,\n };\n}\n\n/**\n * The complete set of tools this bridge may ever register.\n *\n * Exported so the test suite — and anyone auditing what an agent can reach —\n * can assert the registered names against it rather than eyeballing the file.\n */\nexport const PASSKEY_BRIDGE_TOOLS = [\n \"passkey_list\",\n \"passkey_status\",\n \"passkey_rename\",\n \"passkey_revoke\",\n \"passkey_begin_enrollment\",\n] as const;\n\n/**\n * registerPasskeyBridge — MCP tools over a passkey **management** surface.\n *\n * Read the credential list, read browser support and surface state, rename a\n * credential, propose a revoke for the human to confirm, and ask for an\n * enrollment prompt. Nothing signs anyone in; see the file header for why that\n * is a property rather than a gap.\n */\nexport function registerPasskeyBridge(host: ToolHost, options: PasskeyBridgeOptions): Bridge {\n const { adapter } = options;\n const agent = { ...DEFAULT_AGENT, ...(options.agent ?? {}) };\n const surfaceId = adapter.id ?? \"passkeys\";\n const disposers: Array<() => void> = [];\n\n ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });\n\n const target = (elementId?: string, label?: string): AgentTarget => ({\n kind: \"custom\",\n screenId: adapter.screenId,\n elementId,\n label: label ?? surfaceId,\n });\n\n const reg = (\n name: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[],\n handler: (args: JsonObject) => Promise<unknown> | unknown,\n resolveTarget: false | ((args: JsonObject) => AgentTarget),\n ) => {\n const wrapped = async (args: JsonObject) => {\n try {\n return await handler(args);\n } catch (e) {\n return errorResult(e instanceof Error ? e.message : String(e));\n }\n };\n const final = resolveTarget\n ? wrapToolWithActivity(wrapped as never, {\n toolName: name,\n agent,\n kind: \"custom\",\n screenId: adapter.screenId,\n resolveTarget: ({ args }) => resolveTarget(args as JsonObject),\n })\n : wrapped;\n disposers.push(\n host.registerTool(\n {\n name,\n description,\n inputSchema: {\n type: \"object\",\n properties: properties as Record<string, never>,\n required,\n additionalProperties: false,\n },\n },\n final as never,\n ),\n );\n };\n\n const findById = async (id: string): Promise<PasskeySummaryLike | null> =>\n (await adapter.list()).map(toSummary).find((p) => p.id === id) ?? null;\n\n // ─── Read ────────────────────────────────────────────────────────────────\n\n reg(\n \"passkey_list\",\n \"List the signed-in user's passkeys: credential id, label, when it was added and last used, transports, whether it is a synced passkey, and whether the server flagged it as a possible clone. Read-only. Public keys, user handles and signature counters are never returned.\",\n {},\n [],\n async () => {\n const passkeys = (await adapter.list()).map(toSummary);\n return textResult(JSON.stringify(passkeys, null, 2), { passkeys, count: passkeys.length });\n },\n false,\n );\n\n reg(\n \"passkey_status\",\n \"Read what this browser can do (passkey support, a built-in authenticator, autofill sign-in) and what the management surface is currently showing (busy/error, a revoke awaiting confirmation, a rename in progress).\",\n {},\n [],\n async () => {\n const support = adapter.support ? await adapter.support() : null;\n const state = adapter.state ? await adapter.state() : null;\n const out = {\n support,\n state,\n // Said in the payload, not only in the docs: an agent reading this to\n // work out \"how do I sign the user in\" should find the answer here.\n canCompleteCeremony: false,\n note: \"A passkey ceremony needs a user gesture and a biometric or PIN. No tool on this bridge can perform one; ask the human to complete it on their device.\",\n };\n return textResult(JSON.stringify(out, null, 2), out);\n },\n false,\n );\n\n // ─── Rename (undoable) ───────────────────────────────────────────────────\n\n reg(\n \"passkey_rename\",\n \"Rename one passkey, by credential id (from passkey_list). Cosmetic and undoable with agent_undo.\",\n {\n id: { type: \"string\", description: \"base64url credential id.\" },\n name: { type: \"string\", description: \"New label, e.g. 'MacBook Touch ID'. Empty string clears it.\" },\n },\n [\"id\", \"name\"],\n async (args) => {\n const id = str(args.id);\n const name = str(args.name);\n if (!id) return errorResult(\"id is required.\");\n\n const existing = await findById(id);\n if (!existing) return errorResult(`No passkey with id ${id}.`);\n\n if (options.confirmRename) {\n if (!options.confirm) {\n return errorResult(\n \"passkey_rename requires confirmation (confirmRename), but the host wired no confirm hook.\",\n );\n }\n const ok = await options.confirm({ action: \"passkey_rename\", id, name });\n if (!ok) return errorResult(`Declined: rename ${id} (human did not confirm).`);\n }\n\n const previous = existing.name ?? \"\";\n await adapter.rename({ id, name });\n pushUndoEntry(agent.id, {\n timestamp: Date.now(),\n bridgeId: surfaceId,\n action: \"passkey_rename\",\n label: `Renamed passkey to ${name || \"(unnamed)\"}`,\n undo: () => void adapter.rename({ id, name: previous }),\n redo: () => void adapter.rename({ id, name }),\n });\n\n return textResult(`Renamed passkey ${id} to ${name || \"(unnamed)\"}`, { id, name, previous });\n },\n (args) => target(str(args.id), `rename ${str(args.id)}`),\n );\n\n // ─── Revoke — PROPOSAL ONLY ──────────────────────────────────────────────\n\n reg(\n \"passkey_revoke\",\n \"PROPOSE revoking one passkey. This does NOT revoke it: the request is staged on the surface and the human confirms or cancels there. Revoking the last passkey can lock the user out, and the response says when that is the case so you can tell them before they confirm.\",\n { id: { type: \"string\", description: \"base64url credential id.\" } },\n [\"id\"],\n async (args) => {\n const id = str(args.id);\n if (!id) return errorResult(\"id is required.\");\n\n const existing = await findById(id);\n if (!existing) return errorResult(`No passkey with id ${id}.`);\n\n const proposal = await adapter.proposeRevoke({ id });\n const isLastPasskey = proposal?.isLastPasskey === true;\n const out = {\n id,\n name: existing.name,\n staged: true as const,\n awaitingHuman: true as const,\n isLastPasskey,\n };\n\n return textResult(\n isLastPasskey\n ? `Staged a revoke of ${existing.name ?? id}. This is the account's LAST passkey — confirming it removes passkey sign-in entirely. Waiting for the human to confirm or cancel.`\n : `Staged a revoke of ${existing.name ?? id}. Waiting for the human to confirm or cancel on the surface.`,\n out,\n );\n },\n (args) => target(str(args.id), `propose revoke ${str(args.id)}`),\n );\n\n // ─── Enrollment — opens the prompt, never completes it ───────────────────\n\n reg(\n \"passkey_begin_enrollment\",\n \"Ask the surface to start enrolling a new passkey. This only opens the prompt: the ceremony completes on the human's authenticator (Touch ID, Windows Hello, phone, security key) or it does not complete at all. Returns immediately.\",\n { name: { type: \"string\", description: \"Optional label to pre-fill for the new passkey.\" } },\n [],\n async (args) => {\n if (!adapter.beginEnrollment) return errorResult(\"Host did not wire beginEnrollment().\");\n const name = args.name === undefined ? undefined : str(args.name);\n await adapter.beginEnrollment(name === undefined ? {} : { name });\n const out = { awaitingHuman: true as const, ...(name === undefined ? {} : { name }) };\n return textResult(\n \"Enrollment prompt opened. The human has to complete it on their authenticator — there is no tool that can finish it for them.\",\n out,\n );\n },\n (args) => target(undefined, `begin enrollment${args.name ? ` (${str(args.name)})` : \"\"}`),\n );\n\n // ── There is deliberately NO tool below that completes a ceremony. ────────\n // No passkey_authenticate, no passkey_sign_in, no passkey_complete, no\n // passkey_assert. Do not \"finish\" this bridge by adding one — see the header,\n // and see the closed-tool-list test that will fail if you do.\n\n return {\n id: `passkeys:${surfaceId}`,\n title: adapter.title ?? \"Passkeys\",\n dispose: () => {\n for (const d of disposers.splice(0)) d();\n },\n };\n}\n"]}
@@ -170,11 +170,21 @@ function ShareControls({
170
170
  onStart,
171
171
  onStop,
172
172
  status,
173
+ agentConnected = false,
174
+ activity,
173
175
  shareBaseUrl,
174
176
  className,
175
177
  style
176
178
  }) {
177
179
  const [tab, setTab] = useState("prompt");
180
+ const wasConnected = useRef(false);
181
+ useEffect(() => {
182
+ if (agentConnected && !wasConnected.current) {
183
+ wasConnected.current = true;
184
+ setTab("activity");
185
+ }
186
+ if (!agentConnected) wasConnected.current = false;
187
+ }, [agentConnected]);
178
188
  if (!session) {
179
189
  return /* @__PURE__ */ jsxs("div", { className: ["fai-share fai-share--idle", className ?? ""].filter(Boolean).join(" "), style, children: [
180
190
  /* @__PURE__ */ jsx("button", { type: "button", className: "fai-share__start", onClick: onStart, children: "Start shared session" }),
@@ -209,12 +219,14 @@ function ShareControls({
209
219
  ] })
210
220
  ] }),
211
221
  /* @__PURE__ */ jsxs("div", { className: "fai-share__tabs", role: "tablist", children: [
222
+ agentConnected && /* @__PURE__ */ jsx(TabButton, { tab: "activity", active: tab, setTab, children: "Activity" }),
212
223
  /* @__PURE__ */ jsx(TabButton, { tab: "prompt", active: tab, setTab, children: "Agent prompt" }),
213
224
  /* @__PURE__ */ jsx(TabButton, { tab: "url", active: tab, setTab, children: "URL" }),
214
225
  /* @__PURE__ */ jsx(TabButton, { tab: "json", active: tab, setTab, children: "JSON" }),
215
226
  /* @__PURE__ */ jsx(TabButton, { tab: "curl", active: tab, setTab, children: "cURL recipe" })
216
227
  ] }),
217
228
  /* @__PURE__ */ jsxs("div", { className: "fai-share__panel", children: [
229
+ tab === "activity" && /* @__PURE__ */ jsx(ActivityLog, { activity }),
218
230
  tab === "prompt" && /* @__PURE__ */ jsx(
219
231
  CopyBox,
220
232
  {
@@ -242,6 +254,21 @@ function ShareControls({
242
254
  ] })
243
255
  ] });
244
256
  }
257
+ function ActivityLog({ activity }) {
258
+ const entries = (activity ?? []).slice().reverse();
259
+ if (entries.length === 0) {
260
+ return /* @__PURE__ */ jsx("p", { className: "fai-share__empty", children: "Connected. Nothing yet \u2014 actions appear here as the agent takes them." });
261
+ }
262
+ return /* @__PURE__ */ jsx("ol", { className: "fai-share__activity", "aria-live": "polite", children: entries.map((entry) => /* @__PURE__ */ jsxs("li", { className: "fai-share__activity-row", "data-kind": entry.kind, children: [
263
+ /* @__PURE__ */ jsx("time", { className: "fai-share__activity-time", dateTime: new Date(entry.at).toISOString(), children: new Date(entry.at).toLocaleTimeString(void 0, {
264
+ hour: "2-digit",
265
+ minute: "2-digit",
266
+ second: "2-digit"
267
+ }) }),
268
+ /* @__PURE__ */ jsx("span", { className: "fai-share__activity-source", children: entry.source }),
269
+ /* @__PURE__ */ jsx("span", { className: "fai-share__activity-text", children: entry.text })
270
+ ] }, entry.id)) });
271
+ }
245
272
  function TabButton({ tab, active, setTab, children }) {
246
273
  return /* @__PURE__ */ jsx(
247
274
  "button",
@@ -326,5 +353,5 @@ function buildCurlRecipe(session) {
326
353
  }
327
354
 
328
355
  export { AgentActivityHighlight, AgentCursor, AgentPanel, ShareControls, buildAgentPrompt };
329
- //# sourceMappingURL=chunk-HSI5RVF4.js.map
330
- //# sourceMappingURL=chunk-HSI5RVF4.js.map
356
+ //# sourceMappingURL=chunk-SJTLTITO.js.map
357
+ //# sourceMappingURL=chunk-SJTLTITO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/AgentPanel/AgentPanel.tsx","../src/components/AgentCursor/AgentCursor.tsx","../src/components/AgentActivityHighlight/AgentActivityHighlight.tsx","../src/components/ShareControls/ShareControls.tsx"],"names":["jsxs","jsx","useEffect","useState","useRef"],"mappings":"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","file":"chunk-SJTLTITO.js","sourcesContent":["import { type CSSProperties, type ReactNode, useEffect, useRef } from \"react\";\n\nexport type AgentActivity = {\n id: string;\n /** Wall-clock timestamp; component formats it. */\n at: number;\n /** \"tool\" for MCP tool invocations, \"message\" for chat, \"info\" for status. */\n kind: \"tool\" | \"message\" | \"info\" | \"error\";\n /** Short label, e.g. \"whiteboard_add_sticky\" or \"Agent\". */\n source: string;\n /** Body text. */\n text: string;\n /** Optional structured payload, rendered as collapsed JSON. */\n detail?: unknown;\n};\n\nexport type AgentPanelProps = {\n /** The agent's identity (name + color appears in the header). */\n agent?: { name?: string; color?: string };\n /** Activity stream. Most recent at the end. */\n activity: AgentActivity[];\n /** Optional chat composer — pass an onSubmit to enable. */\n onSubmit?: (message: string) => void;\n /** Disabled while a request is in flight. */\n busy?: boolean;\n /** Right-rail header actions. */\n actions?: ReactNode;\n className?: string;\n style?: CSSProperties;\n};\n\n/**\n * AgentPanel — sidebar showing the agent's identity, a tool-and-chat log,\n * and an optional input composer. Pure presentational: hosts feed it the\n * activity stream from their own state (typically the MCP transport log).\n */\nexport function AgentPanel({ agent, activity, onSubmit, busy, actions, className, style }: AgentPanelProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollTop = el.scrollHeight;\n }, [activity.length]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const value = inputRef.current?.value.trim();\n if (!value || !onSubmit) return;\n onSubmit(value);\n if (inputRef.current) inputRef.current.value = \"\";\n };\n\n const color = agent?.color ?? \"#a855f7\";\n const name = agent?.name ?? \"Agent\";\n\n return (\n <div className={[\"fai-panel\", className ?? \"\"].filter(Boolean).join(\" \")} style={style}>\n <header className=\"fai-panel__header\">\n <div\n className=\"fai-panel__avatar\"\n style={{ background: color }}\n aria-hidden\n >\n {name.slice(0, 1)}\n </div>\n <div className=\"fai-panel__title\">\n <strong>{name}</strong>\n <span className=\"fai-panel__subtitle\">\n {busy ? \"Working…\" : `${activity.length} event${activity.length === 1 ? \"\" : \"s\"}`}\n </span>\n </div>\n {actions && <div className=\"fai-panel__actions\">{actions}</div>}\n </header>\n\n <div ref={scrollRef} className=\"fai-panel__stream\">\n {activity.length === 0 ? (\n <p className=\"fai-panel__empty\">No activity yet.</p>\n ) : (\n activity.map((a) => <ActivityRow key={a.id} item={a} />)\n )}\n </div>\n\n {onSubmit && (\n <form className=\"fai-panel__composer\" onSubmit={handleSubmit}>\n <textarea\n ref={inputRef}\n className=\"fai-panel__input\"\n placeholder={busy ? \"Working…\" : \"Ask the agent…\"}\n disabled={busy}\n rows={2}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit(e as unknown as React.FormEvent);\n }\n }}\n />\n <button type=\"submit\" className=\"fai-panel__send\" disabled={busy}>\n Send\n </button>\n </form>\n )}\n </div>\n );\n}\n\nfunction ActivityRow({ item }: { item: AgentActivity }) {\n const time = formatTime(item.at);\n return (\n <div className={`fai-row fai-row--${item.kind}`}>\n <div className=\"fai-row__meta\">\n <span className=\"fai-row__source\">{item.source}</span>\n <span className=\"fai-row__time\">{time}</span>\n </div>\n <div className=\"fai-row__text\">{item.text}</div>\n {item.detail !== undefined && (\n <details className=\"fai-row__detail\">\n <summary>details</summary>\n <pre>{safeJson(item.detail)}</pre>\n </details>\n )}\n </div>\n );\n}\n\nfunction formatTime(at: number): string {\n const d = new Date(at);\n const hh = d.getHours().toString().padStart(2, \"0\");\n const mm = d.getMinutes().toString().padStart(2, \"0\");\n const ss = d.getSeconds().toString().padStart(2, \"0\");\n return `${hh}:${mm}:${ss}`;\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v, null, 2);\n } catch {\n return String(v);\n }\n}\n","import type { CSSProperties } from \"react\";\n\nexport type AgentCursorProps = {\n x: number;\n y: number;\n name?: string;\n color?: string;\n /** Optional caption shown under the name (e.g. current tool). */\n status?: string;\n className?: string;\n style?: CSSProperties;\n};\n\n/**\n * AgentCursor — on-canvas presence marker for the agent. Drop it inside\n * (or alongside) a fancy-whiteboard <Board> at screen coords matching\n * the agent's reported position.\n */\nexport function AgentCursor({ x, y, name, color = \"#a855f7\", status, className, style }: AgentCursorProps) {\n return (\n <div\n className={[\"fai-cursor\", className ?? \"\"].filter(Boolean).join(\" \")}\n style={{\n position: \"absolute\",\n left: x,\n top: y,\n pointerEvents: \"none\",\n transform: \"translate(-2px, -2px)\",\n ...style,\n }}\n >\n <svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden>\n <path\n d=\"M2 2 L2 17 L7 13 L10 19 L12 18 L9 12 L15 12 Z\"\n fill={color}\n stroke=\"white\"\n strokeWidth=\"1.2\"\n />\n </svg>\n {name && (\n <span\n className=\"fai-cursor__tag\"\n style={{ background: color }}\n >\n {name}\n {status ? <em className=\"fai-cursor__status\"> · {status}</em> : null}\n </span>\n )}\n </div>\n );\n}\n","import { type CSSProperties, useEffect, useState } from \"react\";\n\nexport type AgentActivityHighlightProps = {\n /** Bounds of the highlighted item in the parent's coord system. */\n x: number;\n y: number;\n width: number;\n height: number;\n /** Trigger token — change it (e.g. set to Date.now()) to re-fire the pulse. */\n pulseKey?: string | number;\n /** Highlight tint. */\n color?: string;\n /** Pulse duration in ms. Defaults 1200. */\n duration?: number;\n className?: string;\n style?: CSSProperties;\n};\n\n/**\n * AgentActivityHighlight — short pulsing outline that flashes around an\n * item the agent just touched. Position the parent so this can be placed\n * absolutely matching the item's bounds.\n */\nexport function AgentActivityHighlight({\n x,\n y,\n width,\n height,\n pulseKey,\n color = \"#a855f7\",\n duration = 1200,\n className,\n style,\n}: AgentActivityHighlightProps) {\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n if (pulseKey === undefined) return;\n setVisible(true);\n const t = setTimeout(() => setVisible(false), duration);\n return () => clearTimeout(t);\n }, [pulseKey, duration]);\n\n if (!visible) return null;\n\n return (\n <div\n className={[\"fai-highlight\", className ?? \"\"].filter(Boolean).join(\" \")}\n style={{\n position: \"absolute\",\n left: x - 4,\n top: y - 4,\n width: width + 8,\n height: height + 8,\n borderRadius: 8,\n boxShadow: `0 0 0 2px ${color}, 0 0 16px ${color}66`,\n pointerEvents: \"none\",\n animation: `fai-pulse ${duration}ms ease-out forwards`,\n ...style,\n }}\n />\n );\n}\n","import { type CSSProperties, useEffect, useRef, useState } from \"react\";\nimport type { SessionDescriptor } from \"../../sharing/token\";\nimport type { AgentActivity } from \"../AgentPanel\";\nimport { buildShareConfig, buildShareUrl } from \"../../sharing/token\";\n\nexport type ShareControlsProps = {\n /** The active session, or null when not sharing yet. */\n session: SessionDescriptor | null;\n onStart: () => void;\n onStop: () => void;\n /** Optional connection-state badge text. */\n status?: string;\n /**\n * True once an agent has actually connected to the session.\n *\n * Flips the panel from \"here is how to connect\" to \"here is what the agent is\n * doing\" — the paste-this-prompt UI is dead weight the moment it has been\n * pasted, and what the human needs then is visibility.\n */\n agentConnected?: boolean;\n /**\n * What the agent has done, newest last. Reuses `AgentActivity` from\n * `AgentPanel` rather than a second shape, so a host already collecting\n * presence events can pass the same array to both.\n */\n activity?: AgentActivity[];\n /** Override the URL base used in the share URL. */\n shareBaseUrl?: string;\n className?: string;\n style?: CSSProperties;\n};\n\ntype Tab = \"activity\" | \"prompt\" | \"url\" | \"json\" | \"curl\";\n\n/**\n * ShareControls — the host-facing UI for turning sharing on/off and\n * surfacing the resulting connection details (URL / JSON / cURL).\n */\nexport function ShareControls({\n session,\n onStart,\n onStop,\n status,\n agentConnected = false,\n activity,\n shareBaseUrl,\n className,\n style,\n}: ShareControlsProps) {\n const [tab, setTab] = useState<Tab>(\"prompt\");\n\n // Switch to the log the FIRST time an agent connects, then leave the tab\n // alone. Forcing it on every render would fight a human who deliberately went\n // back to copy the URL again — the panel should follow the session, not\n // overrule the person reading it.\n const wasConnected = useRef(false);\n useEffect(() => {\n if (agentConnected && !wasConnected.current) {\n wasConnected.current = true;\n setTab(\"activity\");\n }\n if (!agentConnected) wasConnected.current = false;\n }, [agentConnected]);\n\n if (!session) {\n return (\n <div className={[\"fai-share fai-share--idle\", className ?? \"\"].filter(Boolean).join(\" \")} style={style}>\n <button type=\"button\" className=\"fai-share__start\" onClick={onStart}>\n Start shared session\n </button>\n <p className=\"fai-share__hint\">\n Generates a session id + secret token. Hand the <strong>Agent prompt</strong> to an AI agent, share the URL with humans, or give the JSON config to an MCP-capable client.\n </p>\n </div>\n );\n }\n\n const url = buildShareUrl(session, shareBaseUrl);\n const config = buildShareConfig(session);\n const curl = buildCurlRecipe(session);\n const prompt = buildAgentPrompt(url);\n\n return (\n <div className={[\"fai-share fai-share--active\", className ?? \"\"].filter(Boolean).join(\" \")} style={style}>\n <div className=\"fai-share__header\">\n <div>\n <strong>Sharing</strong>\n <span className=\"fai-share__id\">\n session <code>{session.id}</code> · token <code>{session.display}…</code>\n </span>\n </div>\n <div className=\"fai-share__header-actions\">\n {status && <span className=\"fai-share__status\">{status}</span>}\n <button type=\"button\" className=\"fai-share__stop\" onClick={onStop}>\n Stop\n </button>\n </div>\n </div>\n\n <div className=\"fai-share__tabs\" role=\"tablist\">\n {agentConnected && (\n <TabButton tab=\"activity\" active={tab} setTab={setTab}>\n Activity\n </TabButton>\n )}\n <TabButton tab=\"prompt\" active={tab} setTab={setTab}>Agent prompt</TabButton>\n <TabButton tab=\"url\" active={tab} setTab={setTab}>URL</TabButton>\n <TabButton tab=\"json\" active={tab} setTab={setTab}>JSON</TabButton>\n <TabButton tab=\"curl\" active={tab} setTab={setTab}>cURL recipe</TabButton>\n </div>\n\n <div className=\"fai-share__panel\">\n {tab === \"activity\" && <ActivityLog activity={activity} />}\n {tab === \"prompt\" && (\n <CopyBox\n label=\"Paste this straight into an AI agent — it connects over MCP, no browser\"\n value={prompt}\n multiline\n />\n )}\n {tab === \"url\" && <CopyBox label=\"Open this URL in another tab to join the session\" value={url} />}\n {tab === \"json\" && (\n <CopyBox\n label=\"Paste into Claude Desktop / Cline MCP server config\"\n value={JSON.stringify(config, null, 2)}\n />\n )}\n {tab === \"curl\" && (\n <CopyBox\n label=\"Connect from a terminal (verifies the relay is reachable)\"\n value={curl}\n multiline\n />\n )}\n </div>\n </div>\n );\n}\n\n/**\n * What the agent has actually done, newest first.\n *\n * The panel used to keep showing the paste-this-prompt UI after the agent was\n * already connected and driving — dead weight at exactly the moment the human\n * needs the opposite: visibility. A connected agent with no visible activity is\n * indistinguishable from a broken one, which is precisely how a stalled session\n * goes unnoticed.\n */\nfunction ActivityLog({ activity }: { activity?: AgentActivity[] }) {\n const entries = (activity ?? []).slice().reverse();\n\n if (entries.length === 0) {\n return (\n <p className=\"fai-share__empty\">\n Connected. Nothing yet — actions appear here as the agent takes them.\n </p>\n );\n }\n\n return (\n <ol className=\"fai-share__activity\" aria-live=\"polite\">\n {entries.map((entry) => (\n <li key={entry.id} className=\"fai-share__activity-row\" data-kind={entry.kind}>\n <time className=\"fai-share__activity-time\" dateTime={new Date(entry.at).toISOString()}>\n {new Date(entry.at).toLocaleTimeString(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n })}\n </time>\n <span className=\"fai-share__activity-source\">{entry.source}</span>\n <span className=\"fai-share__activity-text\">{entry.text}</span>\n </li>\n ))}\n </ol>\n );\n}\n\nfunction TabButton({ tab, active, setTab, children }: { tab: Tab; active: Tab; setTab: (t: Tab) => void; children: React.ReactNode }) {\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={tab === active}\n className={`fai-share__tab${tab === active ? \" is-active\" : \"\"}`}\n onClick={() => setTab(tab)}\n >\n {children}\n </button>\n );\n}\n\nfunction CopyBox({ label, value, multiline }: { label: string; value: string; multiline?: boolean }) {\n const [copied, setCopied] = useState(false);\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(value);\n setCopied(true);\n setTimeout(() => setCopied(false), 1200);\n } catch {\n // ignore\n }\n };\n return (\n <div>\n <div className=\"fai-share__panel-label\">{label}</div>\n <div className=\"fai-share__copy\">\n <pre className={`fai-share__pre${multiline ? \" is-multi\" : \"\"}`}>{value}</pre>\n <button type=\"button\" className=\"fai-share__copy-btn\" onClick={copy}>\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * Build a copy-paste natural-language prompt to hand directly to an AI agent.\n * Agents handed a bare URL tend to try to *open it in a browser* — this spells\n * out that the link is a live MCP co-browsing session it should drive over MCP\n * tools (via the zero-install relay client), never visit.\n */\nexport function buildAgentPrompt(url: string): string {\n return [\n `You're joining a live Human+ co-browsing session on a web app. This is an MCP`,\n `(Model Context Protocol) session — NOT a website to visit. Do NOT open the URL`,\n `in a browser and do NOT use any browser/Playwright tool. You drive the real,`,\n `running app through MCP tools while a human watches and can take over.`,\n ``,\n `Session link (carries the session id + token — treat it as a secret):`,\n ` ${url}`,\n ``,\n `Connect by running the relay client as an MCP server (zero install):`,\n ` npx -y mcp-relay-client \"${url}\"`,\n ``,\n `That exposes the app's own tools (e.g. page_describe, page_read, nav_visit,`,\n `page_click, page_set_field, page_submit, plus surface tools like whiteboard_*).`,\n `Then:`,\n ` 1. Call page_describe first to see the current page and its interactive handles.`,\n ` 2. Act only on the STABLE HANDLES the tools return — never guess DOM selectors.`,\n ` 3. Navigate with nav_visit, type with page_set_field, click with page_click.`,\n ` Submits and destructive clicks are staged for the human to confirm.`,\n ``,\n `If you can't register an MCP server but can run a shell, drive it directly:`,\n ` curl -O https://raw.githubusercontent.com/Particle-Academy/mcp-relay-client/main/connect.sh`,\n ` bash connect.sh \"${url}\" tools`,\n ` bash connect.sh \"${url}\" call page_describe '{}'`,\n ].join(\"\\n\");\n}\n\n/** Build a copy-paste cURL recipe for connecting an external MCP client. */\nfunction buildCurlRecipe(session: SessionDescriptor): string {\n const base =\n typeof window !== \"undefined\"\n ? `${window.location.protocol}//${window.location.host}`\n : \"http://localhost\";\n const inbox = `${base}/agent-relay/${session.id}/inbox?token=${session.token}`;\n const events = `${base}/agent-relay/${session.id}/events?token=${session.token}`;\n return [\n `# 1) In one terminal, subscribe to server-pushed frames (SSE)`,\n `curl -N \"${events}\"`,\n ``,\n `# 2) In another terminal, send an initialize handshake`,\n `curl -X POST \"${inbox}\" \\\\`,\n ` -H 'content-type: application/json' \\\\`,\n ` -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}'`,\n ``,\n `# 3) List the tools the bridge exposes`,\n `curl -X POST \"${inbox}\" \\\\`,\n ` -H 'content-type: application/json' \\\\`,\n ` -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}'`,\n ``,\n `# 4) Add a sticky note`,\n `curl -X POST \"${inbox}\" \\\\`,\n ` -H 'content-type: application/json' \\\\`,\n ` -d '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"whiteboard_add_sticky\",\"arguments\":{\"x\":300,\"y\":300,\"text\":\"hello from curl\"}}}'`,\n ].join(\"\\n\");\n}\n"]}
@@ -980,11 +980,21 @@ function ShareControls({
980
980
  onStart,
981
981
  onStop,
982
982
  status,
983
+ agentConnected = false,
984
+ activity,
983
985
  shareBaseUrl,
984
986
  className,
985
987
  style
986
988
  }) {
987
989
  const [tab, setTab] = react.useState("prompt");
990
+ const wasConnected = react.useRef(false);
991
+ react.useEffect(() => {
992
+ if (agentConnected && !wasConnected.current) {
993
+ wasConnected.current = true;
994
+ setTab("activity");
995
+ }
996
+ if (!agentConnected) wasConnected.current = false;
997
+ }, [agentConnected]);
988
998
  if (!session) {
989
999
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["fai-share fai-share--idle", className ?? ""].filter(Boolean).join(" "), style, children: [
990
1000
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "fai-share__start", onClick: onStart, children: "Start shared session" }),
@@ -1019,12 +1029,14 @@ function ShareControls({
1019
1029
  ] })
1020
1030
  ] }),
1021
1031
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-share__tabs", role: "tablist", children: [
1032
+ agentConnected && /* @__PURE__ */ jsxRuntime.jsx(TabButton, { tab: "activity", active: tab, setTab, children: "Activity" }),
1022
1033
  /* @__PURE__ */ jsxRuntime.jsx(TabButton, { tab: "prompt", active: tab, setTab, children: "Agent prompt" }),
1023
1034
  /* @__PURE__ */ jsxRuntime.jsx(TabButton, { tab: "url", active: tab, setTab, children: "URL" }),
1024
1035
  /* @__PURE__ */ jsxRuntime.jsx(TabButton, { tab: "json", active: tab, setTab, children: "JSON" }),
1025
1036
  /* @__PURE__ */ jsxRuntime.jsx(TabButton, { tab: "curl", active: tab, setTab, children: "cURL recipe" })
1026
1037
  ] }),
1027
1038
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-share__panel", children: [
1039
+ tab === "activity" && /* @__PURE__ */ jsxRuntime.jsx(ActivityLog, { activity }),
1028
1040
  tab === "prompt" && /* @__PURE__ */ jsxRuntime.jsx(
1029
1041
  CopyBox,
1030
1042
  {
@@ -1052,6 +1064,21 @@ function ShareControls({
1052
1064
  ] })
1053
1065
  ] });
1054
1066
  }
1067
+ function ActivityLog({ activity }) {
1068
+ const entries = (activity ?? []).slice().reverse();
1069
+ if (entries.length === 0) {
1070
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fai-share__empty", children: "Connected. Nothing yet \u2014 actions appear here as the agent takes them." });
1071
+ }
1072
+ return /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "fai-share__activity", "aria-live": "polite", children: entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "fai-share__activity-row", "data-kind": entry.kind, children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsx("time", { className: "fai-share__activity-time", dateTime: new Date(entry.at).toISOString(), children: new Date(entry.at).toLocaleTimeString(void 0, {
1074
+ hour: "2-digit",
1075
+ minute: "2-digit",
1076
+ second: "2-digit"
1077
+ }) }),
1078
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fai-share__activity-source", children: entry.source }),
1079
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fai-share__activity-text", children: entry.text })
1080
+ ] }, entry.id)) });
1081
+ }
1055
1082
  function TabButton({ tab, active, setTab, children }) {
1056
1083
  return /* @__PURE__ */ jsxRuntime.jsx(
1057
1084
  "button",