@synap-core/sdk 1.3.0 → 1.5.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 (51) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +212 -12
  3. package/dist/{chunk-J7XHAAFJ.js → chunk-UK2U6SMA.js} +14 -2
  4. package/dist/chunk-UK2U6SMA.js.map +1 -0
  5. package/dist/{client-CVUT1Mcf.d.cts → client-B50KC2aJ.d.cts} +14 -9
  6. package/dist/{client-CVUT1Mcf.d.ts → client-B50KC2aJ.d.ts} +14 -9
  7. package/dist/connect.cjs +790 -161
  8. package/dist/connect.cjs.map +1 -1
  9. package/dist/connect.d.cts +610 -37
  10. package/dist/connect.d.ts +610 -37
  11. package/dist/connect.js +777 -162
  12. package/dist/connect.js.map +1 -1
  13. package/dist/public.cjs +9 -2
  14. package/dist/public.cjs.map +1 -1
  15. package/dist/public.d.cts +19 -4
  16. package/dist/public.d.ts +19 -4
  17. package/dist/public.js +8 -2
  18. package/dist/public.js.map +1 -1
  19. package/dist/testing.cjs +366 -17
  20. package/dist/testing.cjs.map +1 -1
  21. package/dist/testing.d.cts +190 -7
  22. package/dist/testing.d.ts +190 -7
  23. package/dist/testing.js +358 -17
  24. package/dist/testing.js.map +1 -1
  25. package/dist/{types-DpqPNaaa.d.cts → types-DZYNBCq7.d.cts} +47 -5
  26. package/dist/{types-DpqPNaaa.d.ts → types-DZYNBCq7.d.ts} +47 -5
  27. package/package.json +36 -12
  28. package/src/connect/auth.ts +314 -55
  29. package/src/connect/client.ts +472 -26
  30. package/src/connect/cloud.ts +210 -0
  31. package/src/connect/discovery.ts +79 -44
  32. package/src/connect/encoding.ts +1 -1
  33. package/src/connect/errors.ts +8 -1
  34. package/src/connect/flow.ts +80 -12
  35. package/src/connect/http.ts +42 -0
  36. package/src/connect/hub-types.ts +254 -0
  37. package/src/connect/index.ts +69 -23
  38. package/src/connect/rules.ts +18 -0
  39. package/src/connect/seal.ts +1 -1
  40. package/src/connect/store.ts +50 -8
  41. package/src/connect/types.ts +48 -4
  42. package/src/public/client.ts +14 -2
  43. package/src/public/safe-markdown.ts +13 -0
  44. package/src/public/share-model.ts +6 -2
  45. package/src/public/site-model.ts +5 -3
  46. package/src/testing/auth-server.ts +377 -20
  47. package/src/testing/cloud.ts +83 -0
  48. package/src/testing/fixtures.ts +26 -0
  49. package/src/testing/index.ts +10 -6
  50. package/src/testing/seed.ts +120 -0
  51. package/dist/chunk-J7XHAAFJ.js.map +0 -1
package/dist/connect.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as PodConnection, P as PodGrant, C as ClientMetadataDocument, A as AppRequest, c as PodIssuer } from './types-DpqPNaaa.js';
2
- export { a as PodUser } from './types-DpqPNaaa.js';
1
+ import { c as PodConnection, P as PodGrant, b as PodActsAs, d as PodSpace, C as ClientMetadataDocument, A as AppRequest, e as PodIssuer } from './types-DZYNBCq7.js';
2
+ export { a as PodUser } from './types-DZYNBCq7.js';
3
3
 
4
4
  /**
5
5
  * The one error type of `@synap-core/sdk/connect`. `code` is the contract a UI
@@ -15,7 +15,7 @@ type ConnectErrorCode =
15
15
  | "pod_unreachable"
16
16
  /** A Synap pod, but too old for app sign-in (no CIMD, no `iss`, no S256). */
17
17
  | "pod_unsupported"
18
- /** Connecting through Synap Cloud is not available yet. */
18
+ /** Connecting through Synap Cloud is not configured for this app (no Control Plane URL). */
19
19
  | "cp_unavailable"
20
20
  /** The person said no on the consent screen. */
21
21
  | "denied"
@@ -31,6 +31,12 @@ type ConnectErrorCode =
31
31
  | "expired"
32
32
  /** The pod refused the connection's refresh token while it should still live. */
33
33
  | "revoked"
34
+ /**
35
+ * The pod answered a redirect where it must answer itself (a moved pod
36
+ * answers 308 from its old name for a while): connect again with its new
37
+ * address. The new address is never taken from the redirect.
38
+ */
39
+ | "pod_moved"
34
40
  /** The pod answered something the SDK cannot use. */
35
41
  | "server_error";
36
42
  declare class ConnectError extends Error {
@@ -49,7 +55,260 @@ declare const CONNECT_ERROR_CODES: readonly ConnectErrorCode[];
49
55
  /** Parse an untrusted `?error=` value back into a code (null when unknown). */
50
56
  declare function parseConnectErrorCode(value: string | null | undefined): ConnectErrorCode | null;
51
57
 
52
- type PodErrorCode = "expired" | "unauthorized" | "forbidden" | "door_unsupported" | "not_found" | "invalid" | "conflict" | "rate_limited" | "pod_unreachable" | "server";
58
+ /**
59
+ * Wire shapes of the Hub REST doors `createPodClient`'s typed methods read.
60
+ *
61
+ * DRIFT RISK: hand-written from synap-backend main (2026-10-22), each from the
62
+ * route itself, never from `@synap-core/hub-rest-client` alone:
63
+ * - entities: `routers/hub-protocol/rest/entities.ts` (`GET /entities`,
64
+ * `GET /entities/:id` → `entityToWire`, `_codecs/entity.ts`
65
+ * `WireEntitySchema`; `GET /entities/:id/connections` → `relations.ts`
66
+ * `getConnections`, `services/object-graph/entity-connections.ts`);
67
+ * - documents: `rest/documents.ts` `GET /documents/:id` →
68
+ * `services/document-patch/read-document.ts` `projectAgentDocument`;
69
+ * - focus sessions: `rest/focus-sessions.ts` `GET /focus-sessions` (raw rows +
70
+ * `triage` + `kind`), slots `services/focus-sessions/update-session.ts`
71
+ * `expectedOutputWireSchema`;
72
+ * - files: `rest/entities.ts` `GET /files/:id/url` → `file-upload.ts`
73
+ * `signFileEntityUrl`;
74
+ * - approve: `rest/proposals.ts` `POST /proposals/:id/approve`.
75
+ * Fields the SDK does not read stay on the objects (index signatures): the pod
76
+ * sends more than is typed here. The mock pod (`@synap-core/sdk/testing`)
77
+ * answers these shapes; `src/__tests__/connect.typed.test.ts` drives both.
78
+ */
79
+ /** One entity, as `GET /entities` and `GET /entities/:id` answer it (`WireEntitySchema`). */
80
+ interface PodEntity {
81
+ id: string;
82
+ profileSlug: string;
83
+ /** Deprecated alias of `profileSlug`, still sent. */
84
+ type?: string;
85
+ title?: string | null;
86
+ preview?: string | null;
87
+ description?: string | null;
88
+ properties: Record<string, unknown>;
89
+ systemData?: Record<string, unknown>;
90
+ userId: string;
91
+ workspaceId?: string | null;
92
+ documentId?: string | null;
93
+ version?: number;
94
+ /** ISO 8601. */
95
+ createdAt?: string;
96
+ updatedAt?: string;
97
+ [key: string]: unknown;
98
+ }
99
+ interface ListEntitiesOptions {
100
+ /** One granted space. Omitted: every space the connection can read. */
101
+ workspaceId?: string;
102
+ /** One kind, e.g. `post`. */
103
+ profileSlug?: string;
104
+ /** 1-1000 (the pod's bounds); the pod's default is 20. */
105
+ limit?: number;
106
+ offset?: number;
107
+ /** e.g. `updatedAt:desc`. */
108
+ sort?: string;
109
+ /** With `workspaceId`: also the person's pod-wide rows (the pod's default: true). */
110
+ includePodWide?: boolean;
111
+ }
112
+ type PodConnectionSource = "graph" | "property" | "thread" | "context_channel" | "focus_session";
113
+ /** One edge of `GET /entities/:id/connections` (`EntityConnection`). */
114
+ interface PodEntityConnection {
115
+ /** The other end: an entity, or the channel / session for those sources. */
116
+ entityId: string;
117
+ /** The other entity's row (title, `type` = its kind, workspaceId, facetSlugs, …); null for channels and sessions. */
118
+ entity: {
119
+ title?: string | null;
120
+ type?: string | null;
121
+ workspaceId?: string | null;
122
+ facetSlugs?: string[] | null;
123
+ [key: string]: unknown;
124
+ } | null;
125
+ label: string;
126
+ direction: "outgoing" | "incoming" | "structural";
127
+ source: PodConnectionSource;
128
+ relationId?: string;
129
+ relationType?: string;
130
+ storedAs?: "link";
131
+ propertySlug?: string;
132
+ propertyLabel?: string;
133
+ channelId?: string;
134
+ channelRelationshipType?: string;
135
+ channelTitle?: string | null;
136
+ channelWorkspaceId?: string | null;
137
+ focusSessionId?: string;
138
+ focusSessionGoal?: string;
139
+ focusSessionStatus?: string;
140
+ focusSessionWorkspaceId?: string | null;
141
+ /** ISO 8601, or null (a property link has no date). */
142
+ createdAt?: string | null;
143
+ [key: string]: unknown;
144
+ }
145
+ interface PodEntityConnections {
146
+ connections: PodEntityConnection[];
147
+ counts: {
148
+ total: number;
149
+ graph: number;
150
+ structural: number;
151
+ threads: number;
152
+ contextChannels: number;
153
+ focusSessions: number;
154
+ };
155
+ }
156
+ interface GetEntityConnectionsOptions {
157
+ /** A space lens; omitted = everything the connection can read. */
158
+ workspaceId?: string;
159
+ /** Per source, 1-200 (the pod's default: 50). */
160
+ limit?: number;
161
+ }
162
+ /** A document's top-level section (`listDocumentSections`). */
163
+ interface PodDocumentSection {
164
+ id: string;
165
+ /** Who owns it (a person's section is not an agent's to change). */
166
+ owner: string;
167
+ title: string | null;
168
+ author: string | null;
169
+ }
170
+ /** `GET /documents/:id` → `document` (`projectAgentDocument`). */
171
+ interface PodDocument {
172
+ id: string;
173
+ title: string;
174
+ /** `markdown`, `text`, `html`, … */
175
+ type: string;
176
+ language: string | null;
177
+ workspaceId: string | null;
178
+ format: "raw" | "readable";
179
+ /** Markdown; `readable` replaces embeds by their fallback text. */
180
+ content: string;
181
+ /** Pass back as `baseRevision` on a guarded edit. */
182
+ revision: number;
183
+ version: number;
184
+ sections: PodDocumentSection[];
185
+ /** What will not render; null when the check could not run (`diagnosticsError`). */
186
+ diagnostics: unknown[] | null;
187
+ diagnosticsError?: string;
188
+ updatedAt: string;
189
+ createdAt: string;
190
+ [key: string]: unknown;
191
+ }
192
+ interface GetDocumentOptions {
193
+ /** `raw` (the stored markdown, the pod's default) or `readable`. */
194
+ format?: "raw" | "readable";
195
+ }
196
+ /** `focus_sessions.status` (`@synap-core/types/focus-sessions` SESSION_STATUSES). */
197
+ type PodSessionStatus = "active" | "paused" | "forming" | "scheduled" | "closed" | "failed" | "cancelled" | "stale";
198
+ /**
199
+ * One declared deliverable of a session (`expectedOutputWireSchema`). A slot
200
+ * with `owner: "human"` is waiting on the person: `blockedReason` and `why` say
201
+ * for what, `ask` how they can answer (confirm, choose, form, act, provide).
202
+ */
203
+ interface PodSessionSlot {
204
+ kind: string;
205
+ label: string;
206
+ key?: string;
207
+ optional?: boolean;
208
+ status?: "pending" | "done";
209
+ /** Absent = `agent`. */
210
+ owner?: "human" | "agent";
211
+ blockedReason?: string;
212
+ why?: string;
213
+ /** ISO 8601, stamped by the pod with `owner: "human"`. */
214
+ owedSince?: string;
215
+ /** How the person can answer (`@synap-core/types/ask`), or null. */
216
+ ask?: unknown;
217
+ answer?: {
218
+ text: string;
219
+ messageId: string | null;
220
+ answeredBy: string;
221
+ answeredAt: string;
222
+ question?: string;
223
+ value?: unknown;
224
+ [key: string]: unknown;
225
+ };
226
+ retiredAt?: string;
227
+ [key: string]: unknown;
228
+ }
229
+ /** One row of `GET /focus-sessions`. `expectedOutputs` is always an array here (the SDK drops anything else). */
230
+ interface PodFocusSession {
231
+ id: string;
232
+ workspaceId: string | null;
233
+ projectId: string | null;
234
+ userId: string;
235
+ title: string | null;
236
+ goal: string;
237
+ status: PodSessionStatus | (string & {});
238
+ templateId: string | null;
239
+ expectedOutputs: PodSessionSlot[];
240
+ channelId: string | null;
241
+ progress: number | null;
242
+ currentStage: string | null;
243
+ agentIds: string[];
244
+ closedAt: string | null;
245
+ metadata: unknown;
246
+ startedAt: string;
247
+ createdAt: string;
248
+ updatedAt: string;
249
+ /** Projected by the list door: `work`, `run` (a playbook or automation ran it) or `receipt`. */
250
+ kind?: "work" | "run" | "receipt";
251
+ /** Projected by the list door: an agent-started session the person has not accepted yet. */
252
+ triage?: {
253
+ pending: boolean;
254
+ acceptedAt: string | null;
255
+ acceptedBy: string | null;
256
+ };
257
+ [key: string]: unknown;
258
+ }
259
+ interface ListFocusSessionsOptions {
260
+ /** A space; `workspaceId` or `projectId` is required (the pod's rule). */
261
+ workspaceId?: string;
262
+ projectId?: string;
263
+ /** Default `all` (the pod's default on this door). */
264
+ status?: PodSessionStatus | "all";
265
+ /** 1-50 (the pod's bounds); the pod's default is 20. */
266
+ limit?: number;
267
+ /** Triage lens: `all` (default here), `default` (hides unaccepted agent sessions), `triage`. */
268
+ lens?: "all" | "default" | "triage";
269
+ /** Population lens: `all` (default here), `work`, `run`, `receipt`. */
270
+ kind?: "all" | "work" | "run" | "receipt";
271
+ /** Sessions about one thing. */
272
+ subjectEntityId?: string;
273
+ }
274
+ /** `GET /files/:id/url`: a signed GET for a file entity's blob. */
275
+ interface PodFileUrl {
276
+ /** Served as an `attachment`, with the stored type. */
277
+ url: string;
278
+ /** ISO 8601; the signature's end (about an hour). */
279
+ expiresAt: string;
280
+ /** Decide drawability on this, never on the URL; null when unknown. */
281
+ mimeType: string | null;
282
+ }
283
+ /** `POST /proposals/:id/approve`. */
284
+ interface PodApproval {
285
+ proposalId: string;
286
+ /** The row the approval produced or linked, when it names one. */
287
+ primaryId?: string;
288
+ linked?: number;
289
+ /** The proposal after the approval (status, execution result), when the pod could read it back. */
290
+ proposal: Record<string, unknown> | null;
291
+ }
292
+ interface ApproveProposalOptions {
293
+ /**
294
+ * The proposal's space, when known: refused locally unless the connection
295
+ * holds `proposal.approve` there. Omitted: needs it in some space, and the
296
+ * pod checks the proposal's own space.
297
+ */
298
+ workspaceId?: string;
299
+ }
300
+
301
+ type PodErrorCode = "expired" | "unauthorized" | "forbidden" | "door_unsupported" | "not_found" | "invalid" | "conflict" | "rate_limited" | "pod_unreachable"
302
+ /** The pod answered a redirect (a moved pod answers 308 from its old name): connect again at its new address. */
303
+ | "pod_moved" | "server";
304
+ /**
305
+ * Why the SDK refused before sending anything (`status: null`):
306
+ * - `agent_cannot_approve`: an agent connection asked to approve (the pod
307
+ * never lets an agent credential approve; `code: "forbidden"`);
308
+ * - `not_granted`: the connection holds no permission for it (`forbidden`);
309
+ * - `invalid_input`: an id, a path or an option the pod would refuse (`invalid`).
310
+ */
311
+ type PodRefusalReason = "agent_cannot_approve" | "not_granted" | "invalid_input";
53
312
  interface PodError {
54
313
  code: PodErrorCode;
55
314
  /** The HTTP status, or null when nothing was answered. */
@@ -57,6 +316,17 @@ interface PodError {
57
316
  retryAfterSec?: number;
58
317
  /** The pod's own error text, for logs only. */
59
318
  detail?: string;
319
+ /** Set when the SDK refused locally (1.5). */
320
+ reason?: PodRefusalReason;
321
+ /**
322
+ * On `unauthorized` (1.5): the pod refused the access token, the connection
323
+ * still holds a refresh token, and this client could not renew it (no
324
+ * `renew`). A forced refresh may restore it (a narrow, or a pod-side "Revoke
325
+ * key", ends the key, not the connection): run `refreshIfNeeded(request,
326
+ * { force: true })` where cookies can be set. Absent = the pod refused the
327
+ * connection itself: connect again.
328
+ */
329
+ refreshable?: true;
60
330
  }
61
331
  type PodResult<T> = {
62
332
  ok: true;
@@ -80,24 +350,121 @@ type PodWriteResult<T> = {
80
350
  error: PodError;
81
351
  };
82
352
  type QueryValue = string | number | boolean | null | undefined | ReadonlyArray<string | number | boolean>;
353
+ /** One pending proposal, the pod's summary row (`GET /proposals?view=basic`). */
354
+ interface PendingProposal {
355
+ id: string;
356
+ proposalType: string;
357
+ targetType: string;
358
+ targetId: string | null;
359
+ workspaceId: string | null;
360
+ /** ISO 8601, as the pod sent it. */
361
+ createdAt: string;
362
+ /** The agent that filed it (this app's agent, or another one), when known. */
363
+ agentUserId: string | null;
364
+ sessionId: string | null;
365
+ summary?: string;
366
+ /** Where the person reviews it: the pod's deep link. */
367
+ reviewUrl: string;
368
+ }
369
+ interface PendingProposals {
370
+ /** Every pending proposal the connection can see, not only this page (the pod COUNTs it). */
371
+ total: number;
372
+ proposals: PendingProposal[];
373
+ hasMore: boolean;
374
+ }
375
+ interface ListPendingProposalsOptions {
376
+ /** One granted space only. */
377
+ workspaceId?: string;
378
+ /** 1-200, default 20. */
379
+ limit?: number;
380
+ offset?: number;
381
+ /**
382
+ * Only these proposals (at most 100 ids; the pod's `proposalIds` filter,
383
+ * pods that do not filter are filtered here). An empty list matches nothing (answered without a request).
384
+ */
385
+ proposalIds?: readonly string[];
386
+ }
83
387
  interface PodClientOptions {
84
388
  fetch?: typeof fetch;
85
389
  now?: () => number;
390
+ /** Per Hub request, default 10 000 ms; a request past it is `pod_unreachable`. */
391
+ timeoutMs?: number;
392
+ /** The most a Hub answer may weigh, default 8 MiB; a bigger one is `server` (the body is not read further). */
393
+ maxResponseBytes?: number;
394
+ /**
395
+ * Renew the connection after the pod refused its access token (401) while it
396
+ * holds a refresh token: rotate it, PERSIST the result (the old refresh token
397
+ * is spent once the pod answers), then return it; throw the refresh's
398
+ * `ConnectError` when it fails. The client calls it at most once (concurrent
399
+ * 401s share the call), retries the refused request once with the result, and
400
+ * uses it from then on. `PodAppAuth.client(connection, { persist })` builds
401
+ * one. Without it a 401 stays `unauthorized` (with `refreshable`).
402
+ */
403
+ renew?: (connection: PodConnection) => Promise<PodConnection>;
86
404
  }
87
405
  interface PodClient {
88
406
  podUrl: string;
89
- user: PodConnection["user"];
90
- grant: PodGrant[];
407
+ /** The connection's, or the renewed one's after a 401 (`renew`). */
408
+ readonly user: PodConnection["user"];
409
+ readonly grant: PodGrant[];
410
+ /** Who the token acts as (`connectionActsAs`): `"agent"` unless the pod said `"person"`. */
411
+ readonly actsAs: PodActsAs;
91
412
  /** Whether the connection was granted `permission` (in `workspaceId` when given). */
92
413
  can(permission: string, workspaceId?: string): boolean;
414
+ /** The granted spaces, each with its permissions (`spaces(connection)`). */
415
+ spaces(): PodSpace[];
416
+ /**
417
+ * "Waiting for review: N". The person's pending proposals in the spaces this
418
+ * connection may read proposals in (any agent's, not only this app's), from
419
+ * the pod's seamed `GET /proposals` door. Needs the `proposal.read`
420
+ * permission (ask for it in `requests`): without it the pod would answer an
421
+ * empty list, so this answers `forbidden` instead, without a request.
422
+ */
423
+ listPendingProposals(opts?: ListPendingProposalsOptions): Promise<PodResult<PendingProposals>>;
424
+ /** `GET /entities` (no full-text `q`: a space-scoped token may list, never search). */
425
+ listEntities(opts?: ListEntitiesOptions): Promise<PodResult<PodEntity[]>>;
426
+ /** `GET /entities/:id`; `not_found` when it does not exist or is not readable. */
427
+ getEntity(id: string): Promise<PodResult<PodEntity>>;
428
+ /** `GET /entities/:id/connections`: its relations, property links, threads and sessions. */
429
+ getEntityConnections(id: string, opts?: GetEntityConnectionsOptions): Promise<PodResult<PodEntityConnections>>;
430
+ /** `GET /documents/:id`: content, revision, sections. */
431
+ getDocument(id: string, opts?: GetDocumentOptions): Promise<PodResult<PodDocument>>;
432
+ /**
433
+ * `GET /focus-sessions` in a space (or a project), newest first, with their
434
+ * slots (`expectedOutputs`; `humanSlots(session)` = what waits on the person).
435
+ * `invalid` without a request when neither `workspaceId` nor `projectId` is given.
436
+ */
437
+ listFocusSessions(opts: ListFocusSessionsOptions): Promise<PodResult<PodFocusSession[]>>;
438
+ /** `GET /files/:id/url`: a signed download URL for a file entity (about an hour). */
439
+ getFileUrl(entityId: string): Promise<PodResult<PodFileUrl>>;
440
+ /**
441
+ * `POST /proposals/:id/approve`, as the person. Only a PERSON connection
442
+ * (`actsAs: "person"`) holding `proposal.approve` can: an agent connection
443
+ * gets `forbidden` / `agent_cannot_approve` and NOTHING is sent (the pod
444
+ * never lets an agent approve); without the permission, `forbidden` /
445
+ * `not_granted`, also without a request.
446
+ */
447
+ approveProposal(id: string, opts?: ApproveProposalOptions): Promise<PodResult<PodApproval>>;
93
448
  get<T = unknown>(path: string, query?: Record<string, QueryValue>): Promise<PodResult<T>>;
94
449
  post<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
95
450
  patch<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
96
451
  put<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
97
452
  delete<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
98
453
  }
454
+ /** Who `connection`'s token acts as: `"person"` only when the pod said so, else `"agent"`. */
455
+ declare function connectionActsAs(connection: Pick<PodConnection, "actsAs">): PodActsAs;
456
+ /**
457
+ * The slots of `session` that wait on the person (Home's "needs you"):
458
+ * `owner: "human"`, not done, not retired.
459
+ */
460
+ declare function humanSlots(session: Pick<PodFocusSession, "expectedOutputs">): PodSessionSlot[];
99
461
  /** Whether `connection` was granted `permission` (in `workspaceId` when given). Exact match: grants carry no wildcard. */
100
462
  declare function can(connection: Pick<PodConnection, "grant">, permission: string, workspaceId?: string): boolean;
463
+ /**
464
+ * The grant by space, in the grant's order, each permission once. The pod
465
+ * sends one row per permission per space; a space switcher wants one per space.
466
+ */
467
+ declare function spaces(connection: Pick<PodConnection, "grant">): PodSpace[];
101
468
  /**
102
469
  * Where to review a proposal: the pod's answer, resolved against the pod and
103
470
  * kept only on the pod's own origin; else the pod's deep link `/open/<id>`.
@@ -105,7 +472,7 @@ declare function can(connection: Pick<PodConnection, "grant">, permission: strin
105
472
  declare function reviewUrlFor(podUrl: string, reviewUrl: unknown, proposalId: string | null): string;
106
473
  declare function createPodClient(connection: PodConnection, opts?: PodClientOptions): PodClient;
107
474
 
108
- type SealPurpose = "synap-connect/transaction" | "synap-connect/session";
475
+ type SealPurpose = "synap-connect/transaction" | "synap-connect/session" | "synap-connect/cloud";
109
476
  /** The app secret: one value, or several to rotate (the first seals, all open). */
110
477
  type AppSecret = string | readonly string[];
111
478
  declare const MIN_SECRET_LENGTH = 32;
@@ -143,9 +510,32 @@ interface ConnectionStore {
143
510
  interface CookieNames {
144
511
  session: string;
145
512
  transaction: string;
513
+ /** A Synap Cloud sign-in in progress (sealed state, 10 minutes). */
514
+ cloud: string;
515
+ /** "This browser was connected to <pod>": the pod's URL only, no secret (`markerCookie`). */
516
+ marker: string;
146
517
  }
147
518
  /** `__Host-` names on https (Secure, Path=/, no Domain: bound to this exact host). */
148
519
  declare function cookieNames(secure: boolean): CookieNames;
520
+ /** The marker outlives the connection: as long as browsers keep a cookie (400 days). */
521
+ declare const MARKER_TTL_SEC: number;
522
+ /**
523
+ * The marker cookie: which pod this browser was connected to, so a connection
524
+ * that lapsed (idle past the refresh token's life, its sealed cookie gone)
525
+ * reads as "expired, connect again to <pod>" rather than "never connected".
526
+ * It holds the pod's public URL and NOTHING else: no token, no user, nothing
527
+ * sealed. A forged value can only pre-fill the address the person confirms.
528
+ */
529
+ declare function markerCookie(podUrl: string, opts: {
530
+ secure: boolean;
531
+ name?: string;
532
+ }): string;
533
+ /** The marker's pod URL, or null (absent, or not a canonical pod address). */
534
+ declare function readMarker(cookieHeader: string | null, opts: {
535
+ secure: boolean;
536
+ name?: string;
537
+ allowInsecureLoopback?: boolean;
538
+ }): string | null;
149
539
  /** When the stored value stops being useful. */
150
540
  declare function connectionExpiresAt(c: PodConnection): number;
151
541
  interface SealedCookieStoreOptions {
@@ -169,6 +559,8 @@ interface PodAppPaths {
169
559
  connect: string;
170
560
  callback: string;
171
561
  disconnect: string;
562
+ /** Synap Cloud: POST starts it, GET is the pod chooser's return (forwarded from the connect page). */
563
+ cloud: string;
172
564
  /** The app's connect page (also the CIMD `initiate_login_uri`). */
173
565
  connectPage: string;
174
566
  /** Where a failed callback lands, with `?error=<code>`. */
@@ -180,7 +572,12 @@ declare const DEFAULT_PATHS: PodAppPaths;
180
572
  /** Refresh only inside the last 5 minutes of the access token's life. */
181
573
  declare const REFRESH_WINDOW_MS: number;
182
574
  interface PodAppAuthConfig {
183
- /** The app's origin, e.g. https://studio.example.com (http only on localhost). */
575
+ /**
576
+ * The app's origin, e.g. https://studio.example.com. `http://localhost` is
577
+ * accepted for the SDK's MOCK pod only: every real pod, a local one
578
+ * included, refuses an http client_id and redirect_uri, so an app that signs
579
+ * in to a real pod runs on https (a tunnel in development).
580
+ */
184
581
  appUrl: string;
185
582
  /** Shown on the pod's consent screen (at most 100 characters). */
186
583
  clientName: string;
@@ -195,7 +592,16 @@ interface PodAppAuthConfig {
195
592
  store?: ConnectionStore;
196
593
  /** RFC 8707 resource asked for (default the Hub API). */
197
594
  resource?: "api/hub" | "mcp" | null;
198
- /** Accept pods on http://localhost (development against a local pod). */
595
+ /**
596
+ * The Synap Control Plane's URL (e.g. https://api.synap.live): offers
597
+ * "Continue with Synap Cloud" (`cloudConnectAvailable`). Unset = address only.
598
+ */
599
+ cloudUrl?: string | null;
600
+ /**
601
+ * Accept a pod (and a Control Plane) on http://localhost: development
602
+ * against the SDK's mock pod (`@synap-core/sdk/testing`). A real local pod
603
+ * still refuses an app that is not on https (see `appUrl`).
604
+ */
199
605
  allowInsecureLoopback?: boolean;
200
606
  /** Developer log of every refused sign-in; messages never carry a token. Default: console.warn. */
201
607
  onError?: (error: ConnectError, where: "connect" | "callback" | "disconnect" | "refresh") => void;
@@ -213,9 +619,9 @@ type RefreshStatus =
213
619
  | "refreshed"
214
620
  /** The refresh failed (a lost rotation race, the pod busy) but the access token still works: keep it, set nothing. */
215
621
  | "kept"
216
- /** The access token is spent and cannot be renewed: connect again. */
622
+ /** The access token is spent and cannot be renewed (lapsed, or the pod moved: `error.code`): connect again. The session cookie is cleared (the marker stays). */
217
623
  | "expired"
218
- /** The pod refused the refresh token (access removed): connect again. */
624
+ /** The pod refused the refresh token (access removed): connect again. The session cookie is cleared (the marker stays). */
219
625
  | "revoked"
220
626
  /** The pod could not be reached and the access token is spent: try later. */
221
627
  | "unavailable";
@@ -228,17 +634,53 @@ interface RefreshOutcome {
228
634
  /** The request's Cookie header with the refreshed value, to forward to the render (null = unchanged). */
229
635
  cookieHeader: string | null;
230
636
  error?: ConnectError;
637
+ /**
638
+ * With `none`: this browser WAS connected to that pod and the connection
639
+ * lapsed (its sealed cookie is gone, idle past its life). Say "expired,
640
+ * connect again" with the pod pre-filled, not "connect your pod".
641
+ */
642
+ lapsed?: {
643
+ podUrl: string;
644
+ };
645
+ }
646
+ interface RefreshOptions {
647
+ /**
648
+ * Rotate now, whatever the access token's expiry says: the pod refused it
649
+ * (a Hub call answered `unauthorized` with `refreshable`). Needs a refresh
650
+ * token. If the pod refuses the refresh too, the outcome is `revoked` (or
651
+ * `expired`), never `kept`: the access token is known dead.
652
+ */
653
+ force?: boolean;
654
+ }
655
+ interface PodAppClientOptions extends PodClientOptions {
656
+ /**
657
+ * Where the client may set cookies (a route handler, a server action): after
658
+ * a 401 it then renews the connection ONCE (rotate, seal, `persist` the
659
+ * Set-Cookie values, retry the request once). The rotation spends the old
660
+ * refresh token, so it is never done where its result cannot be stored: a
661
+ * server component passes no `persist`, and a 401 there answers
662
+ * `unauthorized` with `refreshable` (see `RefreshOptions.force`).
663
+ */
664
+ persist?: (setCookies: string[]) => void | Promise<void>;
665
+ /** The request (or its Cookie header) the connection came from: chunks a larger old cookie left are cleared. */
666
+ request?: Request | string | null;
231
667
  }
232
668
  interface PodAppAuth {
233
669
  clientId: string;
234
670
  redirectUri: string;
235
671
  metadata: ClientMetadataDocument;
236
672
  paths: PodAppPaths;
673
+ /** Whether "Continue with Synap Cloud" can be offered (`cloudUrl` is configured). */
674
+ cloudConnectAvailable: boolean;
237
675
  handlers: {
238
676
  clientMetadata(request: Request): Promise<Response>;
239
677
  connect(request: Request): Promise<Response>;
240
678
  callback(request: Request): Promise<Response>;
241
679
  disconnect(request: Request): Promise<Response>;
680
+ /** POST: start a Synap Cloud sign-in (same-origin, form { next?, space?, prompt? }). */
681
+ cloudConnect(request: Request): Promise<Response>;
682
+ /** GET: the Synap Cloud pod chooser's return (`?iss&state`), checked against this browser's cookie. */
683
+ cloudReturn(request: Request): Promise<Response>;
242
684
  };
243
685
  /**
244
686
  * The stored connection (no refresh, no expiry check), or null. Takes the
@@ -246,12 +688,25 @@ interface PodAppAuth {
246
688
  * pass `(await headers()).get("cookie")`).
247
689
  */
248
690
  readConnection(source: Request | string | null): Promise<PodConnection | null>;
249
- /** For `proxy.ts` / a route handler: rotate near expiry, lost-race safe. */
250
- refreshIfNeeded(request: Request): Promise<RefreshOutcome>;
251
- /** A Hub REST client for the connection. */
252
- client(connection: PodConnection, opts?: PodClientOptions): PodClient;
691
+ /**
692
+ * The pod this browser WAS connected to, when no connection is stored any
693
+ * more (it lapsed); null when one is stored or this browser never connected
694
+ * (or disconnected). Same sources as `readConnection`.
695
+ */
696
+ readLapsed(source: Request | string | null): Promise<{
697
+ podUrl: string;
698
+ } | null>;
699
+ /** For `proxy.ts` / a route handler: rotate near expiry (or now, `force`), lost-race safe. */
700
+ refreshIfNeeded(request: Request, opts?: RefreshOptions): Promise<RefreshOutcome>;
701
+ /** A Hub REST client for the connection (renewing it after a 401 when it can `persist`). */
702
+ client(connection: PodConnection, opts?: PodAppClientOptions): PodClient;
253
703
  }
254
- /** The CIMD document (spec 17 §6.1), validated the way the pod will validate it. */
704
+ /**
705
+ * The app's client metadata document (its client_id URL serves it). Refuses,
706
+ * at setup, every document the pod would refuse at sign-in: the permissions
707
+ * (`assertAppPermission`), at most 32 requests counted as sent, duplicates
708
+ * included, an IP address origin, the 5 KB cap.
709
+ */
255
710
  declare function buildClientMetadata(opts: {
256
711
  appUrl: string;
257
712
  clientName: string;
@@ -259,6 +714,21 @@ declare function buildClientMetadata(opts: {
259
714
  templates?: readonly string[];
260
715
  paths?: Partial<PodAppPaths>;
261
716
  }): ClientMetadataDocument;
717
+ /**
718
+ * A prefetch or a static asset: `refreshIfNeeded` answers `skipped` for these.
719
+ *
720
+ * LIMIT (Next.js 16 `proxy.ts` on the Node runtime): Next strips its router
721
+ * headers (`next-router-prefetch`, `rsc`, …, `FLIGHT_HEADERS` in
722
+ * next/dist/server/web/adapter.js) and the `_rsc` query BEFORE the proxy runs,
723
+ * so a router prefetch is invisible here. Skip prefetches in the proxy
724
+ * MATCHER, which still sees the raw headers:
725
+ *
726
+ * export const config = { matcher: [{ source: "/((?!_next/static|_next/image|.*\\.\\w+$).*)",
727
+ * missing: [{ type: "header", key: "next-router-prefetch" }, { type: "header", key: "purpose", value: "prefetch" }] }] };
728
+ *
729
+ * Route handlers and other frameworks see the headers, and this still checks them.
730
+ */
731
+ declare function isPrefetchOrAsset(request: Request): boolean;
262
732
  declare function createPodAppAuth(config: PodAppAuthConfig): PodAppAuth;
263
733
 
264
734
  interface DiscoverOptions {
@@ -276,17 +746,13 @@ declare function canonicalPodUrl(input: string, opts?: {
276
746
  }): string;
277
747
  /**
278
748
  * A typed pod address (or a launch `iss`) → its validated authorization server.
279
- * Tries the address, then `pod.<host>` (so "antoine.synap.live" finds
280
- * "pod.antoine.synap.live").
749
+ * Tries the address, then the pod names it may stand for: on Synap Cloud,
750
+ * "you.synap.live" finds "you.pod.synap.live" (and the older
751
+ * "pod.you.synap.live"); elsewhere, "example.com" finds "pod.example.com".
752
+ * A redirect is never followed: an old name that answers 308 counts as a
753
+ * miss (`pod_moved` when nothing else answers).
281
754
  */
282
755
  declare function discoverPod(input: string, opts?: DiscoverOptions): Promise<PodIssuer>;
283
- /** Whether "Continue with Synap Cloud" can be offered. False until the CP pod chooser (C1) ships. */
284
- declare function cloudConnectAvailable(): boolean;
285
- /** The Synap Cloud pod chooser (C1). Not available yet: always throws `cp_unavailable`. */
286
- declare function startCloudConnect(_opts: {
287
- clientId: string;
288
- state?: string;
289
- }): never;
290
756
  /**
291
757
  * A same-origin path to return to after connecting, or null. Accepts a
292
758
  * relative path (never `//host` or `/\host`) or an absolute URL on the app's
@@ -294,25 +760,126 @@ declare function startCloudConnect(_opts: {
294
760
  */
295
761
  declare function safeNextPath(value: string | null | undefined, appOrigin: string): string | null;
296
762
  interface LaunchHint {
297
- /** The pod issuer the launcher named (`iss`), to pre-fill and CONFIRM (T6), never to auto-connect. */
763
+ /** The pod issuer the launcher named (`iss`), to pre-fill and CONFIRM, never to auto-connect. */
298
764
  pod: string | null;
299
765
  /** Where to land after connecting (`target_link_uri`), same origin only. */
300
766
  next: string | null;
301
- /** A space to preselect on consent (`synap_space_hint`). */
767
+ /** A space hint for the pod's consent (`synap_space_hint`); a pod may ignore it. */
302
768
  spaceHint: string | null;
769
+ /**
770
+ * The `state` of a Synap Cloud return (the CP pod chooser answers
771
+ * `initiate_login_uri?iss=…&state=…`, or `?state=…&error=access_denied` when
772
+ * the person cancelled), shape-checked only. NOT verified here:
773
+ * send the request's query to the cloud handler (GET `paths.cloud`), which
774
+ * checks it against this browser's sealed cookie. Never act on `pod` because
775
+ * a state is present.
776
+ */
777
+ cloudState: string | null;
303
778
  }
304
779
  /**
305
- * Read a third-party-initiated login (OIDC Core §4, spec 17 §7.2): the browser
780
+ * Read a third-party-initiated login (OIDC Core §4): the browser
306
781
  * opens the app's `initiate_login_uri?iss=…&target_link_uri=…`. One parser for
307
- * the browser launch and, later, the Synap Cloud chooser's return.
782
+ * the browser launch and the Synap Cloud chooser's return (`cloudState`).
308
783
  */
309
784
  declare function acceptLaunch(url: string | URL, appOrigin: string, opts?: {
310
785
  allowInsecureLoopback?: boolean;
311
786
  }): LaunchHint;
312
787
 
788
+ /** How long a Synap Cloud sign-in may take, from the click to the pod chooser's return. */
789
+ declare const CLOUD_TRANSACTION_TTL_MS: number;
790
+ /** A sealed `CloudTransaction`: opaque, safe to put in an HttpOnly cookie. */
791
+ type SealedCloudTransaction = string & {
792
+ readonly __sealedCloudTransaction: true;
793
+ };
794
+ /** What a started Synap Cloud sign-in remembers (sealed; never sent to the CP). */
795
+ interface CloudTransaction {
796
+ v: 1;
797
+ state: string;
798
+ clientId: string;
799
+ /** The CP base it was sent to. */
800
+ cloudUrl: string;
801
+ next: string | null;
802
+ spaceHint: string | null;
803
+ prompt: "consent" | null;
804
+ createdAt: number;
805
+ }
806
+ interface CloudConnectConfig {
807
+ /** The Control Plane's base URL, e.g. https://api.synap.live. Unset = no Synap Cloud option. */
808
+ cloudUrl?: string | null;
809
+ /** Accept a CP on http://localhost (development). */
810
+ allowInsecureLoopback?: boolean;
811
+ }
812
+ /** The CP base (no trailing slash), or a ConnectError (`misconfigured`). */
813
+ declare function cloudBaseUrl(cloudUrl: string, opts?: {
814
+ allowInsecureLoopback?: boolean;
815
+ }): string;
816
+ /** Whether "Continue with Synap Cloud" can be offered: a valid Control Plane URL is configured. */
817
+ declare function cloudConnectAvailable(config?: CloudConnectConfig | null): boolean;
818
+ interface StartCloudConnectOptions extends CloudConnectConfig {
819
+ /** The app's client_id (its CIMD URL): the CP reads `initiate_login_uri` from it. */
820
+ clientId: string;
821
+ /**
822
+ * SERVER-ONLY app secret that seals the transaction. Required: without it the call throws `misconfigured`.
823
+ * Optional in the type only for 1.x semver; required in 2.0.
824
+ */
825
+ secret?: AppSecret;
826
+ /** A same-origin path to land on afterwards (validate it first: `safeNextPath`). */
827
+ next?: string | null;
828
+ /** A space hint for the pod's consent (`synap_space_hint`); a pod may ignore it. */
829
+ spaceHint?: string | null;
830
+ /** Show the pod's consent again even if this browser is already connected to the pod the person picks. */
831
+ prompt?: "consent" | null;
832
+ /** @deprecated Ignored: the SDK always draws its own 32-byte state. Removed in 2.0. */
833
+ state?: string;
834
+ now?: () => number;
835
+ }
836
+ interface StartedCloudConnect {
837
+ /** Redirect the browser here (303): the CP's pod chooser. */
838
+ url: string;
839
+ /** Store in an HttpOnly cookie until the CP returns. */
840
+ transaction: SealedCloudTransaction;
841
+ /** ms since epoch. */
842
+ expiresAt: number;
843
+ }
844
+ /**
845
+ * Start a Synap Cloud sign-in. Throws `cp_unavailable` at once when no CP URL
846
+ * is configured, `misconfigured` without a secret: both SYNCHRONOUSLY, before
847
+ * a Promise exists, so wrap the call in try/catch rather than relying on
848
+ * `.catch` (kept from 1.3 for compatibility; 2.0 makes it a plain async
849
+ * function with a required secret).
850
+ */
851
+ declare function startCloudConnect(opts: StartCloudConnectOptions): Promise<StartedCloudConnect>;
852
+ interface AcceptCloudReturnOptions {
853
+ /** The URL the CP sent the browser back to (`?iss=…&state=…`). */
854
+ url: string | URL;
855
+ transaction: string | null | undefined;
856
+ clientId: string;
857
+ secret: AppSecret;
858
+ allowInsecureLoopback?: boolean;
859
+ now?: () => number;
860
+ }
861
+ interface CloudReturn {
862
+ /** The pod the person picked (canonical issuer), still to be discovered and validated. */
863
+ pod: string;
864
+ next: string | null;
865
+ spaceHint: string | null;
866
+ prompt: "consent" | null;
867
+ }
868
+ /**
869
+ * Check the CP's return against the sealed transaction of THIS browser.
870
+ * Throws `expired_transaction` (none, foreign, tampered or older than 10
871
+ * minutes), `mismatch` (the state is not this browser's, or no pod is named),
872
+ * `denied` (the person cancelled on the chooser: `error=access_denied`, no
873
+ * `iss`; nothing is started), `invalid_pod_address`.
874
+ * `state` is compared BEFORE anything else in the query is used.
875
+ */
876
+ declare function acceptCloudReturn(opts: AcceptCloudReturnOptions): Promise<CloudReturn>;
877
+
878
+ declare function isBrowserRuntime(): boolean;
879
+
313
880
  /** How long a started sign-in stays valid. */
314
881
  declare const TRANSACTION_TTL_MS: number;
315
- /** The refresh token's idle life when the pod does not say (spec 17 F5: 30 days). */
882
+ /** The refresh token's idle life when the pod does not say (the pod's: 30 days). */
316
883
  declare const DEFAULT_REFRESH_IDLE_MS: number;
317
884
  /** A sealed `ConnectTransaction`: opaque, safe to put in an HttpOnly cookie. */
318
885
  type SealedTransaction = string & {
@@ -351,6 +918,12 @@ interface StartConnectOptions extends FlowOptions {
351
918
  spaceHint?: string | null;
352
919
  /** A same-origin path to land on afterwards (validate it first: `safeNextPath`). */
353
920
  next?: string | null;
921
+ /**
922
+ * `"consent"`: the person asked to see the consent screen again (to widen or
923
+ * narrow what the app may touch). Sent as OIDC `prompt=consent`; the sign-in
924
+ * is otherwise the same (fresh PKCE verifier and state).
925
+ */
926
+ prompt?: "consent" | null;
354
927
  allowInsecureLoopback?: boolean;
355
928
  }
356
929
  interface StartedConnect {
@@ -384,7 +957,9 @@ interface CallbackResult {
384
957
  declare function handleCallback(opts: HandleCallbackOptions): Promise<CallbackResult>;
385
958
  /**
386
959
  * A token response → a connection. On a refresh, `previous` supplies what the
387
- * response may omit (grant, user). A refresh response WITHOUT a new
960
+ * response may omit (grant, user, app; never `actsAs`). `expires_in` is read as sent: a refresh
961
+ * answered within the pod's reuse grace hands back the live key the
962
+ * first redeem got, with less than an hour left. A refresh response WITHOUT a new
388
963
  * refresh_token drops the old one: with single-use rotation, resending a
389
964
  * consumed token reads as theft and revokes the whole family.
390
965
  */
@@ -398,7 +973,7 @@ declare function parseTokenResponse(body: Record<string, unknown>, ctx: {
398
973
  }): PodConnection;
399
974
  /**
400
975
  * How long a rotation's result is remembered for its SPENT refresh token.
401
- * The pod (PB3) refuses a concurrent second redeem softly, but a token
976
+ * The pod refuses a concurrent second redeem softly, but a token
402
977
  * presented AGAIN after its use is a reuse: the whole family and the live
403
978
  * access key are revoked. A browser easily sends that replay: parallel
404
979
  * requests, a second tab, a response it had not stored yet, all still
@@ -408,7 +983,7 @@ declare function parseTokenResponse(body: Record<string, unknown>, ctx: {
408
983
  declare const ROTATION_MEMORY_MS: number;
409
984
  /**
410
985
  * Exchange the refresh token for a new access token and the rotated refresh
411
- * token (PB3: single-use, 30 days from issue, reach re-derived by the pod).
986
+ * token (single-use, 30 days from issue, its reach re-derived by the pod).
412
987
  *
413
988
  * SINGLE-FLIGHT per connection within this process: concurrent callers share
414
989
  * one request, and for `ROTATION_MEMORY_MS` after it a caller still holding
@@ -422,7 +997,7 @@ declare const ROTATION_MEMORY_MS: number;
422
997
  * (invalid_grant while it should live: spent, revoked, the app removed or
423
998
  * narrowed to nothing, or a concurrent redeem lost), `misconfigured`
424
999
  * (invalid_client, unauthorized_client), `rate_limited`, `pod_unreachable`,
425
- * `server_error`.
1000
+ * `pod_moved` (the token endpoint answered a redirect), `server_error`.
426
1001
  */
427
1002
  declare function refreshConnection(connection: PodConnection, opts?: FlowOptions): Promise<PodConnection>;
428
1003
  /**
@@ -437,6 +1012,4 @@ declare function revokeConnection(connection: PodConnection, opts?: FlowOptions)
437
1012
  /** RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(code_verifier)))`. */
438
1013
  declare function pkceChallenge(verifier: string): Promise<string>;
439
1014
 
440
- declare function isBrowserRuntime(): boolean;
441
-
442
- export { AppRequest, type AppSecret, CONNECT_ERROR_CODES, type CallbackResult, ClientMetadataDocument, ConnectError, type ConnectErrorCode, type ConnectTransaction, type ConnectionStore, type CookieNames, DEFAULT_PATHS, DEFAULT_REFRESH_IDLE_MS, type DiscoverOptions, type FlowOptions, type HandleCallbackOptions, type LaunchHint, MIN_SECRET_LENGTH, type Opened, type PodAppAuth, type PodAppAuthConfig, type PodAppPaths, type PodClient, type PodClientOptions, PodConnection, type PodError, type PodErrorCode, PodGrant, PodIssuer, type PodResult, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, type RefreshOutcome, type RefreshStatus, type SealPurpose, type SealedCookieStoreOptions, type SealedTransaction, type StartConnectOptions, type StartedConnect, TRANSACTION_TTL_MS, acceptLaunch, applySetCookies, buildClientMetadata, can, canonicalPodUrl, cloudConnectAvailable, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, isBrowserRuntime, isConnectError, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, startCloudConnect, startConnect, unseal };
1015
+ export { type AcceptCloudReturnOptions, AppRequest, type AppSecret, type ApproveProposalOptions, CLOUD_TRANSACTION_TTL_MS, CONNECT_ERROR_CODES, type CallbackResult, ClientMetadataDocument, type CloudConnectConfig, type CloudReturn, type CloudTransaction, ConnectError, type ConnectErrorCode, type ConnectTransaction, type ConnectionStore, type CookieNames, DEFAULT_PATHS, DEFAULT_REFRESH_IDLE_MS, type DiscoverOptions, type FlowOptions, type GetDocumentOptions, type GetEntityConnectionsOptions, type HandleCallbackOptions, type LaunchHint, type ListEntitiesOptions, type ListFocusSessionsOptions, type ListPendingProposalsOptions, MARKER_TTL_SEC, MIN_SECRET_LENGTH, type Opened, type PendingProposal, type PendingProposals, PodActsAs, type PodAppAuth, type PodAppAuthConfig, type PodAppClientOptions, type PodAppPaths, type PodApproval, type PodClient, type PodClientOptions, PodConnection, type PodConnectionSource, type PodDocument, type PodDocumentSection, type PodEntity, type PodEntityConnection, type PodEntityConnections, type PodError, type PodErrorCode, type PodFileUrl, type PodFocusSession, PodGrant, PodIssuer, type PodRefusalReason, type PodResult, type PodSessionSlot, type PodSessionStatus, PodSpace, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, type RefreshOptions, type RefreshOutcome, type RefreshStatus, type SealPurpose, type SealedCloudTransaction, type SealedCookieStoreOptions, type SealedTransaction, type StartCloudConnectOptions, type StartConnectOptions, type StartedCloudConnect, type StartedConnect, TRANSACTION_TTL_MS, acceptCloudReturn, acceptLaunch, applySetCookies, buildClientMetadata, can, canonicalPodUrl, cloudBaseUrl, cloudConnectAvailable, connectionActsAs, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, humanSlots, isBrowserRuntime, isConnectError, isPrefetchOrAsset, markerCookie, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, readMarker, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, spaces, startCloudConnect, startConnect, unseal };