@particle-academy/agent-integrations 0.33.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.
package/README.md CHANGED
@@ -58,6 +58,7 @@ Every bridge has a matching subpath if you'd rather import surgically:
58
58
  | charts | `…/bridges/charts` |
59
59
  | scene | `…/bridges/scene` |
60
60
  | screens | `…/bridges/screens` |
61
+ | passkeys | `…/bridges/passkeys` |
61
62
  | sheets adapter hook | `…/sheets-adapter` |
62
63
  | presence | `…/presence` |
63
64
  | undo | `…/undo` |
@@ -253,6 +254,14 @@ to you:
253
254
  - **Navigation / forms are trust-but-verify.** `nav_visit` allows only
254
255
  `http(s)`/relative URLs; `form_submit` stages on `adapter.confirm` when
255
256
  `pendingMode` is on.
257
+ - **Passkeys are management-only, and that is not a gap.** `registerPasskeyBridge`
258
+ exposes no tool that completes a WebAuthn ceremony — no `passkey_authenticate`,
259
+ no `passkey_sign_in` — because a ceremony needs a user gesture and a biometric,
260
+ and a tool that performed one would turn a phishing-resistant factor back into
261
+ a bearer token. `passkey_revoke` **stages** a revoke for the human and has no
262
+ `confirm` argument that would apply it (revoking the last passkey is a lockout).
263
+ `passkey_list` re-projects onto the public summary fields, so handing it your
264
+ ORM model cannot leak a public key, a user handle, or a signature counter.
256
265
  - **The relay fans tool results + activity to all peers.** The terminal bridge
257
266
  redacts raw command bytes from broadcast meta, but any `structuredContent` you
258
267
  return is visible to every connected peer — don't return secrets.
@@ -0,0 +1,185 @@
1
+ import { T as ToolHost } from '../tool-host-EuYHYSSH.cjs';
2
+ import { B as Bridge } from '../types-NFGP769G.cjs';
3
+ import '../types-C58ZXs4K.cjs';
4
+
5
+ /**
6
+ * Bridge for `@particle-academy/fancy-passkeys-ui` — the **management** half of
7
+ * a passkey (WebAuthn) surface, and only that half.
8
+ *
9
+ * ## The absence is the design
10
+ *
11
+ * There is **no tool here that completes an authentication or registration
12
+ * ceremony**, and there must never be one. Not `passkey_authenticate`, not
13
+ * `passkey_sign_in`, not `passkey_complete`, not a "headless" variant behind a
14
+ * flag.
15
+ *
16
+ * The reason is construction, not policy. `navigator.credentials.get()` requires
17
+ * a user gesture and a biometric or PIN — both things only the person at the
18
+ * keyboard has. An agent-drivable passkey login would be a bypass of the exact
19
+ * property that makes a passkey worth more than a password: that possession of
20
+ * the credential cannot be delegated, replayed, or phished. A bridge offering
21
+ * one would quietly turn a phishing-resistant factor back into a bearer token.
22
+ *
23
+ * So the boundary sits where the cryptography already puts it, and the tool set
24
+ * is drawn to match: **read the management surface, rename, propose, ask the
25
+ * human to start a ceremony.** `bridges/__tests__/passkeys.test.ts` asserts the
26
+ * registered tool names against a closed list, so "finishing" this bridge with a
27
+ * sign-in tool fails CI rather than shipping.
28
+ *
29
+ * ## What it does do
30
+ *
31
+ * passkey_list the user's credentials (whitelisted fields only)
32
+ * passkey_status browser support + what the surface currently shows
33
+ * passkey_rename relabel one credential, by credential ID (undoable)
34
+ * passkey_revoke **STAGES** a revoke for the human to confirm
35
+ * passkey_begin_enrollment opens the enrollment prompt; returns immediately
36
+ *
37
+ * `passkey_revoke` never revokes. Revoking a passkey is destructive and revoking
38
+ * the last one is a lockout, so the confirming click comes from the human at the
39
+ * surface — there is deliberately **no `pendingMode: false`**, no `confirm: true`
40
+ * argument, and no host hook that turns staging off. On this bridge staging is
41
+ * not a mode.
42
+ *
43
+ * The adapter type is declared LOCALLY (like `features` / `catalog`) so the
44
+ * bridge builds and type-checks with the sibling package absent. A host wiring a
45
+ * live `PasskeyManager` satisfies it structurally, with no import.
46
+ */
47
+ /**
48
+ * One credential as the bridge is willing to describe it — the `PasskeySummary`
49
+ * shape both passkey backends already serialise.
50
+ *
51
+ * Everything the server stores that is NOT on this list — the COSE public key,
52
+ * the user handle, the signature counter, the attestation statement and trust
53
+ * path — is absent on purpose, and {@link toSummary} re-projects onto exactly
54
+ * these keys rather than trusting the adapter to have withheld them. A host that
55
+ * hands `list()` its raw ORM model (the obvious thing to do) therefore cannot
56
+ * leak credential material through an MCP tool by accident.
57
+ */
58
+ type PasskeySummaryLike = {
59
+ /** base64url credential ID — the handle every action here is keyed by. */
60
+ id: string;
61
+ /** Human-chosen label, or null when the user never named it. */
62
+ name: string | null;
63
+ /** ISO-8601. */
64
+ createdAt: string;
65
+ /** ISO-8601, or null when never used to sign in. */
66
+ lastUsedAt: string | null;
67
+ /** Transport hints ("internal", "hybrid", "usb", …). */
68
+ transports: string[];
69
+ /** True for a synced passkey (BE/BS flags); false means this device only. */
70
+ backedUp: boolean;
71
+ /** Authenticator model id. Stored, never a trust decision. */
72
+ aaguid: string;
73
+ /** ISO-8601 when the signature counter regressed — a suspected clone. */
74
+ clonedAt: string | null;
75
+ };
76
+ /** What the browser can do, as the three `./client` predicates report it. */
77
+ type PasskeySupport = {
78
+ supported: boolean;
79
+ /** Touch ID / Windows Hello present? `null` while unknown. */
80
+ platformAuthenticator?: boolean | null;
81
+ /** Can the browser offer a passkey from a username field? `null` while unknown. */
82
+ conditionalUi?: boolean | null;
83
+ };
84
+ /** A read of what the management surface is currently showing. */
85
+ type PasskeySurfaceState = {
86
+ status?: "idle" | "busy" | "error";
87
+ /** `{ code, message }` as carried in surface state — never an Error. */
88
+ error?: {
89
+ code: string;
90
+ message: string;
91
+ } | null;
92
+ /** The revoke awaiting a human's confirmation, if any. */
93
+ pendingRevoke?: {
94
+ id: string;
95
+ isLastPasskey: boolean;
96
+ } | null;
97
+ /** Credential ID currently being renamed, if any. */
98
+ renamingId?: string | null;
99
+ };
100
+ /**
101
+ * Host-provided access to the management surface. Mirrors the props a
102
+ * `PasskeyManager` is already wired with, so a host usually passes the same four
103
+ * callbacks it passed the component.
104
+ */
105
+ type PasskeyBridgeAdapter = {
106
+ /** Stable id for this surface instance. Surfaces in activity logs. */
107
+ id?: string;
108
+ title?: string;
109
+ screenId?: string;
110
+ /** The signed-in user's credentials. */
111
+ list(): PasskeySummaryLike[] | Promise<PasskeySummaryLike[]>;
112
+ /** Relabel one credential. Cosmetic, reversible, and undoable here. */
113
+ rename(input: {
114
+ id: string;
115
+ name: string;
116
+ }): void | Promise<void>;
117
+ /**
118
+ * **Stage** a revoke for human confirmation — the `pendingRevoke` the surface
119
+ * renders a confirm/cancel dialog for. It must NOT revoke; a host that wires
120
+ * this to a delete call has removed the human from the loop the bridge was
121
+ * built around.
122
+ */
123
+ proposeRevoke(input: {
124
+ id: string;
125
+ }): PasskeyProposal | Promise<PasskeyProposal>;
126
+ /**
127
+ * Open the enrollment prompt on the human's screen and return immediately.
128
+ * The ceremony completes on their authenticator or not at all.
129
+ */
130
+ beginEnrollment?(input: {
131
+ name?: string;
132
+ }): void | Promise<void>;
133
+ /** Browser capability flags, if the host tracks them. */
134
+ support?(): PasskeySupport | Promise<PasskeySupport>;
135
+ /** What the surface currently shows. */
136
+ state?(): PasskeySurfaceState | Promise<PasskeySurfaceState>;
137
+ };
138
+ type PasskeyProposal = {
139
+ staged: true;
140
+ isLastPasskey: boolean;
141
+ };
142
+ type PasskeyBridgeOptions = {
143
+ adapter: PasskeyBridgeAdapter;
144
+ agent?: {
145
+ id: string;
146
+ name?: string;
147
+ color?: string;
148
+ };
149
+ /**
150
+ * Also route `passkey_rename` through {@link confirm}. Off by default: a
151
+ * rename is cosmetic, reversible and pushed onto the undo stack. Worth turning
152
+ * on where the label itself carries trust — a rogue credential relabelled
153
+ * "Glenn's iPhone" survives a human's audit of the list.
154
+ */
155
+ confirmRename?: boolean;
156
+ /**
157
+ * Host confirmation hook for renames, when {@link confirmRename} is on.
158
+ *
159
+ * Note there is no revoke case: a revoke is staged into the surface and
160
+ * confirmed there, never through a hook the agent's own host supplies.
161
+ */
162
+ confirm?: (req: {
163
+ action: "passkey_rename";
164
+ id: string;
165
+ name: string;
166
+ }) => boolean | Promise<boolean>;
167
+ };
168
+ /**
169
+ * The complete set of tools this bridge may ever register.
170
+ *
171
+ * Exported so the test suite — and anyone auditing what an agent can reach —
172
+ * can assert the registered names against it rather than eyeballing the file.
173
+ */
174
+ declare const PASSKEY_BRIDGE_TOOLS: readonly ["passkey_list", "passkey_status", "passkey_rename", "passkey_revoke", "passkey_begin_enrollment"];
175
+ /**
176
+ * registerPasskeyBridge — MCP tools over a passkey **management** surface.
177
+ *
178
+ * Read the credential list, read browser support and surface state, rename a
179
+ * credential, propose a revoke for the human to confirm, and ask for an
180
+ * enrollment prompt. Nothing signs anyone in; see the file header for why that
181
+ * is a property rather than a gap.
182
+ */
183
+ declare function registerPasskeyBridge(host: ToolHost, options: PasskeyBridgeOptions): Bridge;
184
+
185
+ export { PASSKEY_BRIDGE_TOOLS, type PasskeyBridgeAdapter, type PasskeyBridgeOptions, type PasskeyProposal, type PasskeySummaryLike, type PasskeySupport, type PasskeySurfaceState, registerPasskeyBridge };
@@ -0,0 +1,185 @@
1
+ import { T as ToolHost } from '../tool-host-CWTOJEm2.js';
2
+ import { B as Bridge } from '../types-BKD6uf86.js';
3
+ import '../types-C58ZXs4K.js';
4
+
5
+ /**
6
+ * Bridge for `@particle-academy/fancy-passkeys-ui` — the **management** half of
7
+ * a passkey (WebAuthn) surface, and only that half.
8
+ *
9
+ * ## The absence is the design
10
+ *
11
+ * There is **no tool here that completes an authentication or registration
12
+ * ceremony**, and there must never be one. Not `passkey_authenticate`, not
13
+ * `passkey_sign_in`, not `passkey_complete`, not a "headless" variant behind a
14
+ * flag.
15
+ *
16
+ * The reason is construction, not policy. `navigator.credentials.get()` requires
17
+ * a user gesture and a biometric or PIN — both things only the person at the
18
+ * keyboard has. An agent-drivable passkey login would be a bypass of the exact
19
+ * property that makes a passkey worth more than a password: that possession of
20
+ * the credential cannot be delegated, replayed, or phished. A bridge offering
21
+ * one would quietly turn a phishing-resistant factor back into a bearer token.
22
+ *
23
+ * So the boundary sits where the cryptography already puts it, and the tool set
24
+ * is drawn to match: **read the management surface, rename, propose, ask the
25
+ * human to start a ceremony.** `bridges/__tests__/passkeys.test.ts` asserts the
26
+ * registered tool names against a closed list, so "finishing" this bridge with a
27
+ * sign-in tool fails CI rather than shipping.
28
+ *
29
+ * ## What it does do
30
+ *
31
+ * passkey_list the user's credentials (whitelisted fields only)
32
+ * passkey_status browser support + what the surface currently shows
33
+ * passkey_rename relabel one credential, by credential ID (undoable)
34
+ * passkey_revoke **STAGES** a revoke for the human to confirm
35
+ * passkey_begin_enrollment opens the enrollment prompt; returns immediately
36
+ *
37
+ * `passkey_revoke` never revokes. Revoking a passkey is destructive and revoking
38
+ * the last one is a lockout, so the confirming click comes from the human at the
39
+ * surface — there is deliberately **no `pendingMode: false`**, no `confirm: true`
40
+ * argument, and no host hook that turns staging off. On this bridge staging is
41
+ * not a mode.
42
+ *
43
+ * The adapter type is declared LOCALLY (like `features` / `catalog`) so the
44
+ * bridge builds and type-checks with the sibling package absent. A host wiring a
45
+ * live `PasskeyManager` satisfies it structurally, with no import.
46
+ */
47
+ /**
48
+ * One credential as the bridge is willing to describe it — the `PasskeySummary`
49
+ * shape both passkey backends already serialise.
50
+ *
51
+ * Everything the server stores that is NOT on this list — the COSE public key,
52
+ * the user handle, the signature counter, the attestation statement and trust
53
+ * path — is absent on purpose, and {@link toSummary} re-projects onto exactly
54
+ * these keys rather than trusting the adapter to have withheld them. A host that
55
+ * hands `list()` its raw ORM model (the obvious thing to do) therefore cannot
56
+ * leak credential material through an MCP tool by accident.
57
+ */
58
+ type PasskeySummaryLike = {
59
+ /** base64url credential ID — the handle every action here is keyed by. */
60
+ id: string;
61
+ /** Human-chosen label, or null when the user never named it. */
62
+ name: string | null;
63
+ /** ISO-8601. */
64
+ createdAt: string;
65
+ /** ISO-8601, or null when never used to sign in. */
66
+ lastUsedAt: string | null;
67
+ /** Transport hints ("internal", "hybrid", "usb", …). */
68
+ transports: string[];
69
+ /** True for a synced passkey (BE/BS flags); false means this device only. */
70
+ backedUp: boolean;
71
+ /** Authenticator model id. Stored, never a trust decision. */
72
+ aaguid: string;
73
+ /** ISO-8601 when the signature counter regressed — a suspected clone. */
74
+ clonedAt: string | null;
75
+ };
76
+ /** What the browser can do, as the three `./client` predicates report it. */
77
+ type PasskeySupport = {
78
+ supported: boolean;
79
+ /** Touch ID / Windows Hello present? `null` while unknown. */
80
+ platformAuthenticator?: boolean | null;
81
+ /** Can the browser offer a passkey from a username field? `null` while unknown. */
82
+ conditionalUi?: boolean | null;
83
+ };
84
+ /** A read of what the management surface is currently showing. */
85
+ type PasskeySurfaceState = {
86
+ status?: "idle" | "busy" | "error";
87
+ /** `{ code, message }` as carried in surface state — never an Error. */
88
+ error?: {
89
+ code: string;
90
+ message: string;
91
+ } | null;
92
+ /** The revoke awaiting a human's confirmation, if any. */
93
+ pendingRevoke?: {
94
+ id: string;
95
+ isLastPasskey: boolean;
96
+ } | null;
97
+ /** Credential ID currently being renamed, if any. */
98
+ renamingId?: string | null;
99
+ };
100
+ /**
101
+ * Host-provided access to the management surface. Mirrors the props a
102
+ * `PasskeyManager` is already wired with, so a host usually passes the same four
103
+ * callbacks it passed the component.
104
+ */
105
+ type PasskeyBridgeAdapter = {
106
+ /** Stable id for this surface instance. Surfaces in activity logs. */
107
+ id?: string;
108
+ title?: string;
109
+ screenId?: string;
110
+ /** The signed-in user's credentials. */
111
+ list(): PasskeySummaryLike[] | Promise<PasskeySummaryLike[]>;
112
+ /** Relabel one credential. Cosmetic, reversible, and undoable here. */
113
+ rename(input: {
114
+ id: string;
115
+ name: string;
116
+ }): void | Promise<void>;
117
+ /**
118
+ * **Stage** a revoke for human confirmation — the `pendingRevoke` the surface
119
+ * renders a confirm/cancel dialog for. It must NOT revoke; a host that wires
120
+ * this to a delete call has removed the human from the loop the bridge was
121
+ * built around.
122
+ */
123
+ proposeRevoke(input: {
124
+ id: string;
125
+ }): PasskeyProposal | Promise<PasskeyProposal>;
126
+ /**
127
+ * Open the enrollment prompt on the human's screen and return immediately.
128
+ * The ceremony completes on their authenticator or not at all.
129
+ */
130
+ beginEnrollment?(input: {
131
+ name?: string;
132
+ }): void | Promise<void>;
133
+ /** Browser capability flags, if the host tracks them. */
134
+ support?(): PasskeySupport | Promise<PasskeySupport>;
135
+ /** What the surface currently shows. */
136
+ state?(): PasskeySurfaceState | Promise<PasskeySurfaceState>;
137
+ };
138
+ type PasskeyProposal = {
139
+ staged: true;
140
+ isLastPasskey: boolean;
141
+ };
142
+ type PasskeyBridgeOptions = {
143
+ adapter: PasskeyBridgeAdapter;
144
+ agent?: {
145
+ id: string;
146
+ name?: string;
147
+ color?: string;
148
+ };
149
+ /**
150
+ * Also route `passkey_rename` through {@link confirm}. Off by default: a
151
+ * rename is cosmetic, reversible and pushed onto the undo stack. Worth turning
152
+ * on where the label itself carries trust — a rogue credential relabelled
153
+ * "Glenn's iPhone" survives a human's audit of the list.
154
+ */
155
+ confirmRename?: boolean;
156
+ /**
157
+ * Host confirmation hook for renames, when {@link confirmRename} is on.
158
+ *
159
+ * Note there is no revoke case: a revoke is staged into the surface and
160
+ * confirmed there, never through a hook the agent's own host supplies.
161
+ */
162
+ confirm?: (req: {
163
+ action: "passkey_rename";
164
+ id: string;
165
+ name: string;
166
+ }) => boolean | Promise<boolean>;
167
+ };
168
+ /**
169
+ * The complete set of tools this bridge may ever register.
170
+ *
171
+ * Exported so the test suite — and anyone auditing what an agent can reach —
172
+ * can assert the registered names against it rather than eyeballing the file.
173
+ */
174
+ declare const PASSKEY_BRIDGE_TOOLS: readonly ["passkey_list", "passkey_status", "passkey_rename", "passkey_revoke", "passkey_begin_enrollment"];
175
+ /**
176
+ * registerPasskeyBridge — MCP tools over a passkey **management** surface.
177
+ *
178
+ * Read the credential list, read browser support and surface state, rename a
179
+ * credential, propose a revoke for the human to confirm, and ask for an
180
+ * enrollment prompt. Nothing signs anyone in; see the file header for why that
181
+ * is a property rather than a gap.
182
+ */
183
+ declare function registerPasskeyBridge(host: ToolHost, options: PasskeyBridgeOptions): Bridge;
184
+
185
+ export { PASSKEY_BRIDGE_TOOLS, type PasskeyBridgeAdapter, type PasskeyBridgeOptions, type PasskeyProposal, type PasskeySummaryLike, type PasskeySupport, type PasskeySurfaceState, registerPasskeyBridge };