@particle-academy/agent-integrations 0.45.0 → 0.47.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +835 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -6
  4. package/dist/bridges/artboard.d.cts +3 -3
  5. package/dist/bridges/artboard.d.ts +3 -3
  6. package/dist/bridges/catalog.d.cts +3 -3
  7. package/dist/bridges/catalog.d.ts +3 -3
  8. package/dist/bridges/charts.d.cts +3 -3
  9. package/dist/bridges/charts.d.ts +3 -3
  10. package/dist/bridges/cms.d.cts +3 -3
  11. package/dist/bridges/cms.d.ts +3 -3
  12. package/dist/bridges/code.d.cts +3 -3
  13. package/dist/bridges/code.d.ts +3 -3
  14. package/dist/bridges/doc.d.cts +3 -3
  15. package/dist/bridges/doc.d.ts +3 -3
  16. package/dist/bridges/features.d.cts +3 -3
  17. package/dist/bridges/features.d.ts +3 -3
  18. package/dist/bridges/files.d.cts +3 -3
  19. package/dist/bridges/files.d.ts +3 -3
  20. package/dist/bridges/flow.d.cts +3 -3
  21. package/dist/bridges/flow.d.ts +3 -3
  22. package/dist/bridges/forms.d.cts +3 -3
  23. package/dist/bridges/forms.d.ts +3 -3
  24. package/dist/bridges/git.d.cts +3 -3
  25. package/dist/bridges/git.d.ts +3 -3
  26. package/dist/bridges/grid.d.cts +3 -3
  27. package/dist/bridges/grid.d.ts +3 -3
  28. package/dist/bridges/map.d.cts +3 -3
  29. package/dist/bridges/map.d.ts +3 -3
  30. package/dist/bridges/navigation.d.cts +127 -0
  31. package/dist/bridges/navigation.d.ts +127 -0
  32. package/dist/bridges/passkeys.d.cts +4 -4
  33. package/dist/bridges/passkeys.d.ts +4 -4
  34. package/dist/bridges/scene.d.cts +3 -3
  35. package/dist/bridges/scene.d.ts +3 -3
  36. package/dist/bridges/screens.d.cts +3 -3
  37. package/dist/bridges/screens.d.ts +3 -3
  38. package/dist/bridges/sheets.d.cts +3 -3
  39. package/dist/bridges/sheets.d.ts +3 -3
  40. package/dist/bridges/slides.d.cts +3 -3
  41. package/dist/bridges/slides.d.ts +3 -3
  42. package/dist/bridges/terminal.d.cts +3 -3
  43. package/dist/bridges/terminal.d.ts +3 -3
  44. package/dist/bridges/tui.d.cts +2 -2
  45. package/dist/bridges/tui.d.ts +2 -2
  46. package/dist/bridges/whiteboard.d.cts +3 -3
  47. package/dist/bridges/whiteboard.d.ts +3 -3
  48. package/dist/bridges-navigation.cjs +391 -0
  49. package/dist/bridges-navigation.cjs.map +1 -0
  50. package/dist/bridges-navigation.js +7 -0
  51. package/dist/bridges-navigation.js.map +1 -0
  52. package/dist/bridges-passkeys.cjs.map +1 -1
  53. package/dist/bridges-passkeys.js +1 -1
  54. package/dist/{chunk-OZBUPO4K.js → chunk-5W34X4EY.js} +263 -58
  55. package/dist/chunk-5W34X4EY.js.map +1 -0
  56. package/dist/{chunk-PWYEW6NL.js → chunk-G6CRHFWU.js} +2 -2
  57. package/dist/{chunk-PWYEW6NL.js.map → chunk-G6CRHFWU.js.map} +1 -1
  58. package/dist/chunk-JGJHPH65.js +271 -0
  59. package/dist/chunk-JGJHPH65.js.map +1 -0
  60. package/dist/dom-navigation/index.d.cts +164 -0
  61. package/dist/dom-navigation/index.d.ts +164 -0
  62. package/dist/dom-navigation/script.d.cts +3 -0
  63. package/dist/dom-navigation/script.d.ts +3 -0
  64. package/dist/dom-navigation-script.cjs +11 -0
  65. package/dist/dom-navigation-script.cjs.map +1 -0
  66. package/dist/dom-navigation-script.js +9 -0
  67. package/dist/dom-navigation-script.js.map +1 -0
  68. package/dist/dom-navigation.cjs +387 -0
  69. package/dist/dom-navigation.cjs.map +1 -0
  70. package/dist/dom-navigation.js +370 -0
  71. package/dist/dom-navigation.js.map +1 -0
  72. package/dist/index.cjs +31 -31
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +10 -125
  75. package/dist/index.d.ts +10 -125
  76. package/dist/index.js +12 -275
  77. package/dist/index.js.map +1 -1
  78. package/dist/mcp/index.d.cts +5 -5
  79. package/dist/mcp/index.d.ts +5 -5
  80. package/dist/mcp/stdio.d.cts +3 -3
  81. package/dist/mcp/stdio.d.ts +3 -3
  82. package/dist/presence/index.d.cts +1 -1
  83. package/dist/presence/index.d.ts +1 -1
  84. package/dist/relay-server/index.d.cts +110 -26
  85. package/dist/relay-server/index.d.ts +110 -26
  86. package/dist/relay-server-cli.cjs +374 -89
  87. package/dist/relay-server-cli.cjs.map +1 -1
  88. package/dist/relay-server-cli.js +120 -38
  89. package/dist/relay-server-cli.js.map +1 -1
  90. package/dist/relay-server.cjs +255 -51
  91. package/dist/relay-server.cjs.map +1 -1
  92. package/dist/relay-server.js +1 -1
  93. package/dist/{server-BIcA3ZtK.d.cts → server-1y0n61R5.d.cts} +2 -2
  94. package/dist/{server-DnUyeavY.d.ts → server-vq6qVl3T.d.ts} +2 -2
  95. package/dist/sharing/index.d.cts +3 -3
  96. package/dist/sharing/index.d.ts +3 -3
  97. package/dist/sheets-adapter.d.cts +3 -3
  98. package/dist/sheets-adapter.d.ts +3 -3
  99. package/dist/{tool-host-LVBgCN5m.d.cts → tool-host-D67N2a2h.d.cts} +1 -1
  100. package/dist/{tool-host-Wo0pkY83.d.ts → tool-host-LYfdPE8u.d.ts} +1 -1
  101. package/dist/{types-EcElKOlk.d.cts → types-DP5yQZQU.d.cts} +1 -1
  102. package/dist/{types-BxNRKSVd.d.ts → types-VX_9je7w.d.ts} +1 -1
  103. package/dist/{types-C2DtnHIn.d.cts → types-gzIpsUDE.d.cts} +1 -1
  104. package/dist/{types-C2DtnHIn.d.ts → types-gzIpsUDE.d.ts} +1 -1
  105. package/dist/undo/index.d.cts +2 -2
  106. package/dist/undo/index.d.ts +2 -2
  107. package/docs/relay-protocol.md +41 -5
  108. package/docs/relay-server.md +102 -15
  109. package/package.json +41 -3
  110. package/dist/chunk-OZBUPO4K.js.map +0 -1
@@ -40,12 +40,34 @@ type Subscriber = {
40
40
  * of everyone else's tool calls without making one.
41
41
  */
42
42
  client?: string;
43
+ /**
44
+ * Present only for a LONG-POLL subscriber, which outlives any one request:
45
+ * it is read by a series of `GET /poll` calls rather than one open stream,
46
+ * so it needs its own liveness record.
47
+ */
48
+ poll?: {
49
+ /** Last time a poll started or finished (ms since epoch). */
50
+ lastSeen: number;
51
+ /** Polls currently parked on this subscriber. Never pruned while > 0. */
52
+ parked: number;
53
+ /** Wake every parked poll — a frame landed, or the session ended. */
54
+ wakers: Set<() => void>;
55
+ };
56
+ /** Set once the subscriber has been ended (unsubscribed, pruned, or its session is gone). */
57
+ ended?: boolean;
43
58
  };
44
59
  type RelayBrokerOptions = {
45
60
  /** Sessions auto-expire after this many ms of inactivity. Default 4h. */
46
61
  ttlMs?: number;
47
62
  /** Cleanup tick interval in ms. Default 60 000. */
48
63
  reapIntervalMs?: number;
64
+ /**
65
+ * A long-poll subscriber that has not polled for this long is dropped (and,
66
+ * for an outbound one, the page is told the peer left). Checked on the reap
67
+ * tick. Default 60 000 — the PHP relay's window, and comfortably longer than
68
+ * the 25 s maximum park, so a healthy poller is never pruned between polls.
69
+ */
70
+ pollIdleMs?: number;
49
71
  /** Bring-your-own storage layer (redis, etc.). Defaults to in-memory. */
50
72
  store?: Store;
51
73
  };
@@ -58,6 +80,7 @@ interface Store {
58
80
  }
59
81
  declare class RelayBroker {
60
82
  private readonly ttlMs;
83
+ private readonly pollIdleMs;
61
84
  private readonly store;
62
85
  /** Per-session, per-direction subscriber list. */
63
86
  private subs;
@@ -125,6 +148,30 @@ declare class RelayBroker {
125
148
  subscribe(id: string, token: string, direction: Direction, opts?: {
126
149
  client?: string;
127
150
  }): SubscribeResult;
151
+ /**
152
+ * Attach a LONG-POLL subscriber, or re-attach the one a previous poll created.
153
+ *
154
+ * The CDN-safe receive leg: a series of short requests instead of one open
155
+ * stream, because Cloudflare's HTTP/3 edge resets long-lived SSE. Same
156
+ * contract as the PHP relay in px-ui-sandbox and the one
157
+ * `@particle-academy/fancy-cf-relay` and `mcp-relay-client` speak:
158
+ *
159
+ * - The first poll gets a fresh 16-hex subscriber id; the client echoes it back
160
+ * as `subscriber` on every later poll and reads the same queue.
161
+ * - An id in that shape that this session does not know (pruned, say) is
162
+ * adopted as a NEW subscriber under that id, as the PHP relay does, so a
163
+ * client that went quiet and came back keeps its identity.
164
+ * - An id belonging to a STREAMING subscriber is never adopted: one queue read
165
+ * by two consumers splits its frames between them.
166
+ *
167
+ * Frames queue on the subscriber between polls, so nothing is lost in the gap
168
+ * between one request ending and the next arriving. A poller that stops is
169
+ * pruned after `pollIdleMs`.
170
+ */
171
+ poll(id: string, token: string, direction: Direction, opts?: {
172
+ subscriber?: string;
173
+ client?: string;
174
+ }): PollResult;
128
175
  private getDirSubs;
129
176
  /**
130
177
  * Record which client asked, keyed by JSON-RPC id.
@@ -154,6 +201,33 @@ declare class RelayBroker {
154
201
  /** Deliver to the subscribers of one client only. */
155
202
  private fanOutTo;
156
203
  private fanOut;
204
+ /**
205
+ * Hand one frame to one subscriber.
206
+ *
207
+ * The resolver is CLEARED before it is called. It used to stay set until the
208
+ * generator resumed on a later microtask, so a second frame in the same tick
209
+ * called an already-settled resolver — a no-op — after shifting its frame out
210
+ * of the queue, and that frame was lost. Cleared first, the second frame just
211
+ * waits in the queue for the generator to come back for it.
212
+ */
213
+ private deliver;
214
+ /** Tell the page (the inbound side) that an agent arrived or left. */
215
+ private announcePeer;
216
+ /** End one subscriber: close its stream, or release its parked polls. Idempotent. */
217
+ private endSubscriber;
218
+ /**
219
+ * End every subscriber of a session whose session is going away.
220
+ *
221
+ * Streams used to be dropped from the map WITHOUT being woken, so an SSE leg
222
+ * stayed open, heartbeating, for a session that no longer existed — and its
223
+ * client could not learn the page had closed. Ended, the stream closes, the
224
+ * client reconnects, and `events` answers 410.
225
+ */
226
+ private endSubscribers;
227
+ /** Remove a subscriber from its session, announcing an agent's departure once. */
228
+ private removeSubscriber;
229
+ /** Drop long-poll subscribers that stopped polling. Never one with a poll parked. */
230
+ private prunePollers;
157
231
  /**
158
232
  * Validate that a peer-supplied payload is a well-formed JSON-RPC 2.0 frame
159
233
  * (or batch) before it is fanned out. Previously this was a substring match on
@@ -168,6 +242,24 @@ declare class RelayBroker {
168
242
  private isValidFrame;
169
243
  private reap;
170
244
  }
245
+ type PollResult = {
246
+ ok: false;
247
+ reason: "invalid_token" | "session_gone";
248
+ } | {
249
+ ok: true;
250
+ /** The id the client must echo back as `subscriber` on its next poll. */
251
+ subscriberId: string;
252
+ /** Whether this poll created the subscriber (and, outbound, announced it). */
253
+ created: boolean;
254
+ /**
255
+ * Park up to `ms` for frames, then take everything queued. Returns at once
256
+ * when frames are already waiting, when `ms` is 0, or when the session
257
+ * ends. An aborted `signal` returns `[]` and takes nothing.
258
+ */
259
+ wait: (ms: number, signal?: AbortSignal) => Promise<string[]>;
260
+ /** Put frames back at the head of the queue — a response that could not be written. */
261
+ requeue: (frames: string[]) => void;
262
+ };
171
263
  type SubscribeResult = {
172
264
  ok: false;
173
265
  reason: string;
@@ -178,37 +270,27 @@ type SubscribeResult = {
178
270
  unsubscribe: () => void;
179
271
  };
180
272
 
181
- /**
182
- * Node HTTP adapter for {@link RelayBroker}. Returns a single request
183
- * handler plus per-route handlers, so you can either drop it into
184
- * `http.createServer(...)` directly or mount the individual handlers
185
- * onto your existing Node HTTP framework (Express, Hono w/ node-adapter,
186
- * native http).
187
- *
188
- * const relay = createNodeRelay({ pathPrefix: "/mcp-relay" });
189
- * http.createServer(relay.handler).listen(8787);
190
- *
191
- * // Or piecemeal:
192
- * app.post("/mcp-relay/register", relay.register);
193
- * app.post("/mcp-relay/:s/inbox", relay.inbox);
194
- * app.post("/mcp-relay/:s/outbox", relay.outbox);
195
- * app.get ("/mcp-relay/:s/events", relay.events);
196
- * app.post("/mcp-relay/:s/unregister", relay.unregister);
197
- */
198
273
  type NodeRelayOptions = RelayBrokerOptions & {
199
274
  /** URL path prefix (without trailing slash). Default `""` — handlers
200
275
  * expect paths like `/register`, `/{id}/inbox`, etc. directly. */
201
276
  pathPrefix?: string;
202
- /** Comma-separated origins (or `*`) for CORS. Default `*` — relays
203
- * are typically called cross-origin from the demo host. */
277
+ /**
278
+ * Browser origins allowed to read relay responses: `*`, or a comma-separated
279
+ * list of origins (`"https://example.com,https://www.example.com"`).
280
+ * Default `*` — relays are typically called cross-origin from the demo host.
281
+ *
282
+ * A list is enforced per request: a listed `Origin` is echoed back as
283
+ * `Access-Control-Allow-Origin` with `Vary: Origin`, and an unlisted one
284
+ * gets no allow-origin header at all. `*` mixed into a list, an empty value,
285
+ * `null` or an entry with a path throws here, at construction.
286
+ */
204
287
  corsAllowOrigin?: string;
205
288
  /**
206
- * Strict browser-origin allow-list. When set, only these Origins get a
207
- * matching `Access-Control-Allow-Origin` (the request's own Origin is
208
- * reflected, never `*`), which blunts DNS-rebinding + hostile cross-origin
209
- * pages from reading relay responses. Recommended for any browser-facing
210
- * deployment. When unset, `corsAllowOrigin` (default `*`) is used and the
211
- * session token is the only auth — pair that with a loopback bind.
289
+ * The same allow-list as an array. When non-empty it takes precedence over
290
+ * `corsAllowOrigin`. Recommended for any browser-facing deployment: it blunts
291
+ * DNS rebinding and hostile cross-origin pages reading relay responses. When
292
+ * neither names origins, `*` is used and the session token is the only auth
293
+ * — pair that with a loopback bind.
212
294
  */
213
295
  allowedOrigins?: string[];
214
296
  };
@@ -224,8 +306,10 @@ type NodeRelay = {
224
306
  inbox: NodeHandler;
225
307
  outbox: NodeHandler;
226
308
  events: NodeHandler;
309
+ /** Long-poll receive leg — the CDN-safe alternative to `events`. */
310
+ poll: NodeHandler;
227
311
  unregister: NodeHandler;
228
312
  };
229
313
  declare function createNodeRelay(opts?: NodeRelayOptions): NodeRelay;
230
314
 
231
- export { type Direction, type NodeHandler, type NodeRelay, type NodeRelayOptions, RelayBroker, type RelayBrokerOptions, type Session, type Store, type Subscriber, createNodeRelay };
315
+ export { type Direction, type NodeHandler, type NodeRelay, type NodeRelayOptions, type PollResult, RelayBroker, type RelayBrokerOptions, type Session, type Store, type SubscribeResult, type Subscriber, createNodeRelay };
@@ -40,12 +40,34 @@ type Subscriber = {
40
40
  * of everyone else's tool calls without making one.
41
41
  */
42
42
  client?: string;
43
+ /**
44
+ * Present only for a LONG-POLL subscriber, which outlives any one request:
45
+ * it is read by a series of `GET /poll` calls rather than one open stream,
46
+ * so it needs its own liveness record.
47
+ */
48
+ poll?: {
49
+ /** Last time a poll started or finished (ms since epoch). */
50
+ lastSeen: number;
51
+ /** Polls currently parked on this subscriber. Never pruned while > 0. */
52
+ parked: number;
53
+ /** Wake every parked poll — a frame landed, or the session ended. */
54
+ wakers: Set<() => void>;
55
+ };
56
+ /** Set once the subscriber has been ended (unsubscribed, pruned, or its session is gone). */
57
+ ended?: boolean;
43
58
  };
44
59
  type RelayBrokerOptions = {
45
60
  /** Sessions auto-expire after this many ms of inactivity. Default 4h. */
46
61
  ttlMs?: number;
47
62
  /** Cleanup tick interval in ms. Default 60 000. */
48
63
  reapIntervalMs?: number;
64
+ /**
65
+ * A long-poll subscriber that has not polled for this long is dropped (and,
66
+ * for an outbound one, the page is told the peer left). Checked on the reap
67
+ * tick. Default 60 000 — the PHP relay's window, and comfortably longer than
68
+ * the 25 s maximum park, so a healthy poller is never pruned between polls.
69
+ */
70
+ pollIdleMs?: number;
49
71
  /** Bring-your-own storage layer (redis, etc.). Defaults to in-memory. */
50
72
  store?: Store;
51
73
  };
@@ -58,6 +80,7 @@ interface Store {
58
80
  }
59
81
  declare class RelayBroker {
60
82
  private readonly ttlMs;
83
+ private readonly pollIdleMs;
61
84
  private readonly store;
62
85
  /** Per-session, per-direction subscriber list. */
63
86
  private subs;
@@ -125,6 +148,30 @@ declare class RelayBroker {
125
148
  subscribe(id: string, token: string, direction: Direction, opts?: {
126
149
  client?: string;
127
150
  }): SubscribeResult;
151
+ /**
152
+ * Attach a LONG-POLL subscriber, or re-attach the one a previous poll created.
153
+ *
154
+ * The CDN-safe receive leg: a series of short requests instead of one open
155
+ * stream, because Cloudflare's HTTP/3 edge resets long-lived SSE. Same
156
+ * contract as the PHP relay in px-ui-sandbox and the one
157
+ * `@particle-academy/fancy-cf-relay` and `mcp-relay-client` speak:
158
+ *
159
+ * - The first poll gets a fresh 16-hex subscriber id; the client echoes it back
160
+ * as `subscriber` on every later poll and reads the same queue.
161
+ * - An id in that shape that this session does not know (pruned, say) is
162
+ * adopted as a NEW subscriber under that id, as the PHP relay does, so a
163
+ * client that went quiet and came back keeps its identity.
164
+ * - An id belonging to a STREAMING subscriber is never adopted: one queue read
165
+ * by two consumers splits its frames between them.
166
+ *
167
+ * Frames queue on the subscriber between polls, so nothing is lost in the gap
168
+ * between one request ending and the next arriving. A poller that stops is
169
+ * pruned after `pollIdleMs`.
170
+ */
171
+ poll(id: string, token: string, direction: Direction, opts?: {
172
+ subscriber?: string;
173
+ client?: string;
174
+ }): PollResult;
128
175
  private getDirSubs;
129
176
  /**
130
177
  * Record which client asked, keyed by JSON-RPC id.
@@ -154,6 +201,33 @@ declare class RelayBroker {
154
201
  /** Deliver to the subscribers of one client only. */
155
202
  private fanOutTo;
156
203
  private fanOut;
204
+ /**
205
+ * Hand one frame to one subscriber.
206
+ *
207
+ * The resolver is CLEARED before it is called. It used to stay set until the
208
+ * generator resumed on a later microtask, so a second frame in the same tick
209
+ * called an already-settled resolver — a no-op — after shifting its frame out
210
+ * of the queue, and that frame was lost. Cleared first, the second frame just
211
+ * waits in the queue for the generator to come back for it.
212
+ */
213
+ private deliver;
214
+ /** Tell the page (the inbound side) that an agent arrived or left. */
215
+ private announcePeer;
216
+ /** End one subscriber: close its stream, or release its parked polls. Idempotent. */
217
+ private endSubscriber;
218
+ /**
219
+ * End every subscriber of a session whose session is going away.
220
+ *
221
+ * Streams used to be dropped from the map WITHOUT being woken, so an SSE leg
222
+ * stayed open, heartbeating, for a session that no longer existed — and its
223
+ * client could not learn the page had closed. Ended, the stream closes, the
224
+ * client reconnects, and `events` answers 410.
225
+ */
226
+ private endSubscribers;
227
+ /** Remove a subscriber from its session, announcing an agent's departure once. */
228
+ private removeSubscriber;
229
+ /** Drop long-poll subscribers that stopped polling. Never one with a poll parked. */
230
+ private prunePollers;
157
231
  /**
158
232
  * Validate that a peer-supplied payload is a well-formed JSON-RPC 2.0 frame
159
233
  * (or batch) before it is fanned out. Previously this was a substring match on
@@ -168,6 +242,24 @@ declare class RelayBroker {
168
242
  private isValidFrame;
169
243
  private reap;
170
244
  }
245
+ type PollResult = {
246
+ ok: false;
247
+ reason: "invalid_token" | "session_gone";
248
+ } | {
249
+ ok: true;
250
+ /** The id the client must echo back as `subscriber` on its next poll. */
251
+ subscriberId: string;
252
+ /** Whether this poll created the subscriber (and, outbound, announced it). */
253
+ created: boolean;
254
+ /**
255
+ * Park up to `ms` for frames, then take everything queued. Returns at once
256
+ * when frames are already waiting, when `ms` is 0, or when the session
257
+ * ends. An aborted `signal` returns `[]` and takes nothing.
258
+ */
259
+ wait: (ms: number, signal?: AbortSignal) => Promise<string[]>;
260
+ /** Put frames back at the head of the queue — a response that could not be written. */
261
+ requeue: (frames: string[]) => void;
262
+ };
171
263
  type SubscribeResult = {
172
264
  ok: false;
173
265
  reason: string;
@@ -178,37 +270,27 @@ type SubscribeResult = {
178
270
  unsubscribe: () => void;
179
271
  };
180
272
 
181
- /**
182
- * Node HTTP adapter for {@link RelayBroker}. Returns a single request
183
- * handler plus per-route handlers, so you can either drop it into
184
- * `http.createServer(...)` directly or mount the individual handlers
185
- * onto your existing Node HTTP framework (Express, Hono w/ node-adapter,
186
- * native http).
187
- *
188
- * const relay = createNodeRelay({ pathPrefix: "/mcp-relay" });
189
- * http.createServer(relay.handler).listen(8787);
190
- *
191
- * // Or piecemeal:
192
- * app.post("/mcp-relay/register", relay.register);
193
- * app.post("/mcp-relay/:s/inbox", relay.inbox);
194
- * app.post("/mcp-relay/:s/outbox", relay.outbox);
195
- * app.get ("/mcp-relay/:s/events", relay.events);
196
- * app.post("/mcp-relay/:s/unregister", relay.unregister);
197
- */
198
273
  type NodeRelayOptions = RelayBrokerOptions & {
199
274
  /** URL path prefix (without trailing slash). Default `""` — handlers
200
275
  * expect paths like `/register`, `/{id}/inbox`, etc. directly. */
201
276
  pathPrefix?: string;
202
- /** Comma-separated origins (or `*`) for CORS. Default `*` — relays
203
- * are typically called cross-origin from the demo host. */
277
+ /**
278
+ * Browser origins allowed to read relay responses: `*`, or a comma-separated
279
+ * list of origins (`"https://example.com,https://www.example.com"`).
280
+ * Default `*` — relays are typically called cross-origin from the demo host.
281
+ *
282
+ * A list is enforced per request: a listed `Origin` is echoed back as
283
+ * `Access-Control-Allow-Origin` with `Vary: Origin`, and an unlisted one
284
+ * gets no allow-origin header at all. `*` mixed into a list, an empty value,
285
+ * `null` or an entry with a path throws here, at construction.
286
+ */
204
287
  corsAllowOrigin?: string;
205
288
  /**
206
- * Strict browser-origin allow-list. When set, only these Origins get a
207
- * matching `Access-Control-Allow-Origin` (the request's own Origin is
208
- * reflected, never `*`), which blunts DNS-rebinding + hostile cross-origin
209
- * pages from reading relay responses. Recommended for any browser-facing
210
- * deployment. When unset, `corsAllowOrigin` (default `*`) is used and the
211
- * session token is the only auth — pair that with a loopback bind.
289
+ * The same allow-list as an array. When non-empty it takes precedence over
290
+ * `corsAllowOrigin`. Recommended for any browser-facing deployment: it blunts
291
+ * DNS rebinding and hostile cross-origin pages reading relay responses. When
292
+ * neither names origins, `*` is used and the session token is the only auth
293
+ * — pair that with a loopback bind.
212
294
  */
213
295
  allowedOrigins?: string[];
214
296
  };
@@ -224,8 +306,10 @@ type NodeRelay = {
224
306
  inbox: NodeHandler;
225
307
  outbox: NodeHandler;
226
308
  events: NodeHandler;
309
+ /** Long-poll receive leg — the CDN-safe alternative to `events`. */
310
+ poll: NodeHandler;
227
311
  unregister: NodeHandler;
228
312
  };
229
313
  declare function createNodeRelay(opts?: NodeRelayOptions): NodeRelay;
230
314
 
231
- export { type Direction, type NodeHandler, type NodeRelay, type NodeRelayOptions, RelayBroker, type RelayBrokerOptions, type Session, type Store, type Subscriber, createNodeRelay };
315
+ export { type Direction, type NodeHandler, type NodeRelay, type NodeRelayOptions, type PollResult, RelayBroker, type RelayBrokerOptions, type Session, type Store, type SubscribeResult, type Subscriber, createNodeRelay };