@synap-core/sdk 1.4.0 → 1.5.1
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/CHANGELOG.md +215 -0
- package/README.md +152 -10
- package/dist/{chunk-J7XHAAFJ.js → chunk-UK2U6SMA.js} +14 -2
- package/dist/chunk-UK2U6SMA.js.map +1 -0
- package/dist/{client-C5sF20f1.d.cts → client-B50KC2aJ.d.cts} +14 -9
- package/dist/{client-C5sF20f1.d.ts → client-B50KC2aJ.d.ts} +14 -9
- package/dist/connect.cjs +419 -64
- package/dist/connect.cjs.map +1 -1
- package/dist/connect.d.cts +416 -32
- package/dist/connect.d.ts +416 -32
- package/dist/connect.js +414 -65
- package/dist/connect.js.map +1 -1
- package/dist/public.cjs +9 -2
- package/dist/public.cjs.map +1 -1
- package/dist/public.d.cts +19 -4
- package/dist/public.d.ts +19 -4
- package/dist/public.js +8 -2
- package/dist/public.js.map +1 -1
- package/dist/testing.cjs +350 -15
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +171 -27
- package/dist/testing.d.ts +171 -27
- package/dist/testing.js +346 -15
- package/dist/testing.js.map +1 -1
- package/dist/{types-C2KGNXEn.d.cts → types-DZYNBCq7.d.cts} +28 -5
- package/dist/{types-C2KGNXEn.d.ts → types-DZYNBCq7.d.ts} +28 -5
- package/package.json +30 -6
- package/src/connect/auth.ts +169 -27
- package/src/connect/client.ts +377 -34
- package/src/connect/cloud.ts +14 -10
- package/src/connect/discovery.ts +66 -32
- package/src/connect/encoding.ts +1 -1
- package/src/connect/errors.ts +7 -0
- package/src/connect/flow.ts +42 -14
- package/src/connect/http.ts +42 -0
- package/src/connect/hub-types.ts +271 -0
- package/src/connect/index.ts +61 -42
- package/src/connect/rules.ts +18 -0
- package/src/connect/store.ts +9 -5
- package/src/connect/types.ts +28 -4
- package/src/public/client.ts +14 -2
- package/src/public/safe-markdown.ts +13 -0
- package/src/public/share-model.ts +6 -2
- package/src/public/site-model.ts +5 -3
- package/src/testing/auth-server.ts +350 -17
- package/src/testing/cloud.ts +3 -4
- package/src/testing/fixtures.ts +26 -0
- package/src/testing/index.ts +8 -6
- package/src/testing/roles.ts +27 -0
- package/src/testing/seed.ts +196 -0
- package/dist/chunk-J7XHAAFJ.js.map +0 -1
package/dist/connect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { a as PodUser } from './types-
|
|
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
|
|
@@ -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,276 @@ 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
|
-
|
|
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
|
+
/** The roles it wears, with `includeFacets`; absent when it wears none. */
|
|
98
|
+
facetSlugs?: string[];
|
|
99
|
+
/** Each role's overlay, with `includeFacets`; absent when it wears none. */
|
|
100
|
+
facets?: PodEntityFacet[];
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
/** One role an entity wears (`FacetRowAnnotation`). */
|
|
104
|
+
interface PodEntityFacet {
|
|
105
|
+
facetId: string;
|
|
106
|
+
/** The role, e.g. `lead`. */
|
|
107
|
+
slug: string;
|
|
108
|
+
properties: Record<string, unknown>;
|
|
109
|
+
status: string | null;
|
|
110
|
+
}
|
|
111
|
+
interface ListEntitiesOptions {
|
|
112
|
+
/** One granted space. Omitted: every space the connection can read. */
|
|
113
|
+
workspaceId?: string;
|
|
114
|
+
/** One kind, e.g. `post`. */
|
|
115
|
+
profileSlug?: string;
|
|
116
|
+
/** 1-1000 (the pod's bounds); the pod's default is 20. */
|
|
117
|
+
limit?: number;
|
|
118
|
+
offset?: number;
|
|
119
|
+
/** e.g. `updatedAt:desc`. */
|
|
120
|
+
sort?: string;
|
|
121
|
+
/** With `workspaceId`: also the person's pod-wide rows (the pod's default: true). */
|
|
122
|
+
includePodWide?: boolean;
|
|
123
|
+
/** Only the entities wearing this role, e.g. `lead`. */
|
|
124
|
+
facetSlug?: string;
|
|
125
|
+
/** Put `facetSlugs` and `facets` on each row that wears a role. */
|
|
126
|
+
includeFacets?: boolean;
|
|
127
|
+
}
|
|
128
|
+
type PodConnectionSource = "graph" | "property" | "thread" | "context_channel" | "focus_session";
|
|
129
|
+
/** One edge of `GET /entities/:id/connections` (`EntityConnection`). */
|
|
130
|
+
interface PodEntityConnection {
|
|
131
|
+
/** The other end: an entity, or the channel / session for those sources. */
|
|
132
|
+
entityId: string;
|
|
133
|
+
/** The other entity's row (title, `type` = its kind, workspaceId, facetSlugs, …); null for channels and sessions. */
|
|
134
|
+
entity: {
|
|
135
|
+
title?: string | null;
|
|
136
|
+
type?: string | null;
|
|
137
|
+
workspaceId?: string | null;
|
|
138
|
+
facetSlugs?: string[] | null;
|
|
139
|
+
[key: string]: unknown;
|
|
140
|
+
} | null;
|
|
141
|
+
label: string;
|
|
142
|
+
direction: "outgoing" | "incoming" | "structural";
|
|
143
|
+
source: PodConnectionSource;
|
|
144
|
+
relationId?: string;
|
|
145
|
+
relationType?: string;
|
|
146
|
+
storedAs?: "link";
|
|
147
|
+
propertySlug?: string;
|
|
148
|
+
propertyLabel?: string;
|
|
149
|
+
channelId?: string;
|
|
150
|
+
channelRelationshipType?: string;
|
|
151
|
+
channelTitle?: string | null;
|
|
152
|
+
channelWorkspaceId?: string | null;
|
|
153
|
+
focusSessionId?: string;
|
|
154
|
+
focusSessionGoal?: string;
|
|
155
|
+
focusSessionStatus?: string;
|
|
156
|
+
focusSessionWorkspaceId?: string | null;
|
|
157
|
+
/** ISO 8601, or null (a property link has no date). */
|
|
158
|
+
createdAt?: string | null;
|
|
159
|
+
[key: string]: unknown;
|
|
160
|
+
}
|
|
161
|
+
interface PodEntityConnections {
|
|
162
|
+
connections: PodEntityConnection[];
|
|
163
|
+
counts: {
|
|
164
|
+
total: number;
|
|
165
|
+
graph: number;
|
|
166
|
+
structural: number;
|
|
167
|
+
threads: number;
|
|
168
|
+
contextChannels: number;
|
|
169
|
+
focusSessions: number;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
interface GetEntityConnectionsOptions {
|
|
173
|
+
/** A space lens; omitted = everything the connection can read. */
|
|
174
|
+
workspaceId?: string;
|
|
175
|
+
/** Per source, 1-200 (the pod's default: 50). */
|
|
176
|
+
limit?: number;
|
|
177
|
+
}
|
|
178
|
+
/** A document's top-level section (`listDocumentSections`). */
|
|
179
|
+
interface PodDocumentSection {
|
|
180
|
+
id: string;
|
|
181
|
+
/** Who owns it (a person's section is not an agent's to change). */
|
|
182
|
+
owner: string;
|
|
183
|
+
title: string | null;
|
|
184
|
+
author: string | null;
|
|
185
|
+
}
|
|
186
|
+
/** `GET /documents/:id` → `document` (`projectAgentDocument`). */
|
|
187
|
+
interface PodDocument {
|
|
188
|
+
id: string;
|
|
189
|
+
title: string;
|
|
190
|
+
/** `markdown`, `text`, `html`, … */
|
|
191
|
+
type: string;
|
|
192
|
+
language: string | null;
|
|
193
|
+
workspaceId: string | null;
|
|
194
|
+
format: "raw" | "readable";
|
|
195
|
+
/** Markdown; `readable` replaces embeds by their fallback text. */
|
|
196
|
+
content: string;
|
|
197
|
+
/** Pass back as `baseRevision` on a guarded edit. */
|
|
198
|
+
revision: number;
|
|
199
|
+
version: number;
|
|
200
|
+
sections: PodDocumentSection[];
|
|
201
|
+
/** What will not render; null when the check could not run (`diagnosticsError`). */
|
|
202
|
+
diagnostics: unknown[] | null;
|
|
203
|
+
diagnosticsError?: string;
|
|
204
|
+
updatedAt: string;
|
|
205
|
+
createdAt: string;
|
|
206
|
+
[key: string]: unknown;
|
|
207
|
+
}
|
|
208
|
+
interface GetDocumentOptions {
|
|
209
|
+
/** `raw` (the stored markdown, the pod's default) or `readable`. */
|
|
210
|
+
format?: "raw" | "readable";
|
|
211
|
+
}
|
|
212
|
+
/** `focus_sessions.status` (`@synap-core/types/focus-sessions` SESSION_STATUSES). */
|
|
213
|
+
type PodSessionStatus = "active" | "paused" | "forming" | "scheduled" | "closed" | "failed" | "cancelled" | "stale";
|
|
214
|
+
/**
|
|
215
|
+
* One declared deliverable of a session (`expectedOutputWireSchema`). A slot
|
|
216
|
+
* with `owner: "human"` is waiting on the person: `blockedReason` and `why` say
|
|
217
|
+
* for what, `ask` how they can answer (confirm, choose, form, act, provide).
|
|
218
|
+
*/
|
|
219
|
+
interface PodSessionSlot {
|
|
220
|
+
kind: string;
|
|
221
|
+
label: string;
|
|
222
|
+
key?: string;
|
|
223
|
+
optional?: boolean;
|
|
224
|
+
status?: "pending" | "done";
|
|
225
|
+
/** Absent = `agent`. */
|
|
226
|
+
owner?: "human" | "agent";
|
|
227
|
+
blockedReason?: string;
|
|
228
|
+
why?: string;
|
|
229
|
+
/** ISO 8601, stamped by the pod with `owner: "human"`. */
|
|
230
|
+
owedSince?: string;
|
|
231
|
+
/** How the person can answer (`@synap-core/types/ask`), or null. */
|
|
232
|
+
ask?: unknown;
|
|
233
|
+
answer?: {
|
|
234
|
+
text: string;
|
|
235
|
+
messageId: string | null;
|
|
236
|
+
answeredBy: string;
|
|
237
|
+
answeredAt: string;
|
|
238
|
+
question?: string;
|
|
239
|
+
value?: unknown;
|
|
240
|
+
[key: string]: unknown;
|
|
241
|
+
};
|
|
242
|
+
retiredAt?: string;
|
|
243
|
+
[key: string]: unknown;
|
|
244
|
+
}
|
|
245
|
+
/** One row of `GET /focus-sessions`. `expectedOutputs` is always an array here (the SDK drops anything else). */
|
|
246
|
+
interface PodFocusSession {
|
|
247
|
+
id: string;
|
|
248
|
+
workspaceId: string | null;
|
|
249
|
+
projectId: string | null;
|
|
250
|
+
userId: string;
|
|
251
|
+
title: string | null;
|
|
252
|
+
goal: string;
|
|
253
|
+
status: PodSessionStatus | (string & {});
|
|
254
|
+
templateId: string | null;
|
|
255
|
+
expectedOutputs: PodSessionSlot[];
|
|
256
|
+
channelId: string | null;
|
|
257
|
+
progress: number | null;
|
|
258
|
+
currentStage: string | null;
|
|
259
|
+
agentIds: string[];
|
|
260
|
+
closedAt: string | null;
|
|
261
|
+
metadata: unknown;
|
|
262
|
+
startedAt: string;
|
|
263
|
+
createdAt: string;
|
|
264
|
+
updatedAt: string;
|
|
265
|
+
/** Projected by the list door: `work`, `run` (a playbook or automation ran it) or `receipt`. */
|
|
266
|
+
kind?: "work" | "run" | "receipt";
|
|
267
|
+
/** Projected by the list door: an agent-started session the person has not accepted yet. */
|
|
268
|
+
triage?: {
|
|
269
|
+
pending: boolean;
|
|
270
|
+
acceptedAt: string | null;
|
|
271
|
+
acceptedBy: string | null;
|
|
272
|
+
};
|
|
273
|
+
[key: string]: unknown;
|
|
274
|
+
}
|
|
275
|
+
interface ListFocusSessionsOptions {
|
|
276
|
+
/** A space; `workspaceId` or `projectId` is required (the pod's rule). */
|
|
277
|
+
workspaceId?: string;
|
|
278
|
+
projectId?: string;
|
|
279
|
+
/** Default `all` (the pod's default on this door). */
|
|
280
|
+
status?: PodSessionStatus | "all";
|
|
281
|
+
/** 1-50 (the pod's bounds); the pod's default is 20. */
|
|
282
|
+
limit?: number;
|
|
283
|
+
/** Triage lens: `all` (default here), `default` (hides unaccepted agent sessions), `triage`. */
|
|
284
|
+
lens?: "all" | "default" | "triage";
|
|
285
|
+
/** Population lens: `all` (default here), `work`, `run`, `receipt`. */
|
|
286
|
+
kind?: "all" | "work" | "run" | "receipt";
|
|
287
|
+
/** Sessions about one thing. */
|
|
288
|
+
subjectEntityId?: string;
|
|
289
|
+
}
|
|
290
|
+
/** `GET /files/:id/url`: a signed GET for a file entity's blob. */
|
|
291
|
+
interface PodFileUrl {
|
|
292
|
+
/** Served as an `attachment`, with the stored type. */
|
|
293
|
+
url: string;
|
|
294
|
+
/** ISO 8601; the signature's end (about an hour). */
|
|
295
|
+
expiresAt: string;
|
|
296
|
+
/** Decide drawability on this, never on the URL; null when unknown. */
|
|
297
|
+
mimeType: string | null;
|
|
298
|
+
}
|
|
299
|
+
/** `POST /proposals/:id/approve`. */
|
|
300
|
+
interface PodApproval {
|
|
301
|
+
proposalId: string;
|
|
302
|
+
/** The row the approval produced or linked, when it names one. */
|
|
303
|
+
primaryId?: string;
|
|
304
|
+
linked?: number;
|
|
305
|
+
/** The proposal after the approval (status, execution result), when the pod could read it back. */
|
|
306
|
+
proposal: Record<string, unknown> | null;
|
|
307
|
+
}
|
|
308
|
+
interface ApproveProposalOptions {
|
|
309
|
+
/**
|
|
310
|
+
* The proposal's space, when known: refused locally unless the connection
|
|
311
|
+
* holds `proposal.approve` there. Omitted: needs it in some space, and the
|
|
312
|
+
* pod checks the proposal's own space.
|
|
313
|
+
*/
|
|
314
|
+
workspaceId?: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type PodErrorCode = "expired" | "unauthorized" | "forbidden" | "door_unsupported" | "not_found" | "invalid" | "conflict" | "rate_limited" | "pod_unreachable"
|
|
318
|
+
/** The pod answered a redirect (a moved pod answers 308 from its old name): connect again at its new address. */
|
|
319
|
+
| "pod_moved" | "server";
|
|
320
|
+
/**
|
|
321
|
+
* Why the SDK refused before sending anything (`status: null`):
|
|
322
|
+
* - `agent_cannot_approve`: an agent connection asked to approve (the pod
|
|
323
|
+
* never lets an agent credential approve; `code: "forbidden"`);
|
|
324
|
+
* - `not_granted`: the connection holds no permission for it (`forbidden`);
|
|
325
|
+
* - `invalid_input`: an id, a path or an option the pod would refuse (`invalid`).
|
|
326
|
+
*/
|
|
327
|
+
type PodRefusalReason = "agent_cannot_approve" | "not_granted" | "invalid_input";
|
|
53
328
|
interface PodError {
|
|
54
329
|
code: PodErrorCode;
|
|
55
330
|
/** The HTTP status, or null when nothing was answered. */
|
|
@@ -57,6 +332,17 @@ interface PodError {
|
|
|
57
332
|
retryAfterSec?: number;
|
|
58
333
|
/** The pod's own error text, for logs only. */
|
|
59
334
|
detail?: string;
|
|
335
|
+
/** Set when the SDK refused locally (1.5). */
|
|
336
|
+
reason?: PodRefusalReason;
|
|
337
|
+
/**
|
|
338
|
+
* On `unauthorized` (1.5): the pod refused the access token, the connection
|
|
339
|
+
* still holds a refresh token, and this client could not renew it (no
|
|
340
|
+
* `renew`). A forced refresh may restore it (a narrow, or a pod-side "Revoke
|
|
341
|
+
* key", ends the key, not the connection): run `refreshIfNeeded(request,
|
|
342
|
+
* { force: true })` where cookies can be set. Absent = the pod refused the
|
|
343
|
+
* connection itself: connect again.
|
|
344
|
+
*/
|
|
345
|
+
refreshable?: true;
|
|
60
346
|
}
|
|
61
347
|
type PodResult<T> = {
|
|
62
348
|
ok: true;
|
|
@@ -108,15 +394,37 @@ interface ListPendingProposalsOptions {
|
|
|
108
394
|
/** 1-200, default 20. */
|
|
109
395
|
limit?: number;
|
|
110
396
|
offset?: number;
|
|
397
|
+
/**
|
|
398
|
+
* Only these proposals (at most 100 ids; the pod's `proposalIds` filter,
|
|
399
|
+
* pods that do not filter are filtered here). An empty list matches nothing (answered without a request).
|
|
400
|
+
*/
|
|
401
|
+
proposalIds?: readonly string[];
|
|
111
402
|
}
|
|
112
403
|
interface PodClientOptions {
|
|
113
404
|
fetch?: typeof fetch;
|
|
114
405
|
now?: () => number;
|
|
406
|
+
/** Per Hub request, default 10 000 ms; a request past it is `pod_unreachable`. */
|
|
407
|
+
timeoutMs?: number;
|
|
408
|
+
/** The most a Hub answer may weigh, default 8 MiB; a bigger one is `server` (the body is not read further). */
|
|
409
|
+
maxResponseBytes?: number;
|
|
410
|
+
/**
|
|
411
|
+
* Renew the connection after the pod refused its access token (401) while it
|
|
412
|
+
* holds a refresh token: rotate it, PERSIST the result (the old refresh token
|
|
413
|
+
* is spent once the pod answers), then return it; throw the refresh's
|
|
414
|
+
* `ConnectError` when it fails. The client calls it at most once (concurrent
|
|
415
|
+
* 401s share the call), retries the refused request once with the result, and
|
|
416
|
+
* uses it from then on. `PodAppAuth.client(connection, { persist })` builds
|
|
417
|
+
* one. Without it a 401 stays `unauthorized` (with `refreshable`).
|
|
418
|
+
*/
|
|
419
|
+
renew?: (connection: PodConnection) => Promise<PodConnection>;
|
|
115
420
|
}
|
|
116
421
|
interface PodClient {
|
|
117
422
|
podUrl: string;
|
|
118
|
-
|
|
119
|
-
|
|
423
|
+
/** The connection's, or the renewed one's after a 401 (`renew`). */
|
|
424
|
+
readonly user: PodConnection["user"];
|
|
425
|
+
readonly grant: PodGrant[];
|
|
426
|
+
/** Who the token acts as (`connectionActsAs`): `"agent"` unless the pod said `"person"`. */
|
|
427
|
+
readonly actsAs: PodActsAs;
|
|
120
428
|
/** Whether the connection was granted `permission` (in `workspaceId` when given). */
|
|
121
429
|
can(permission: string, workspaceId?: string): boolean;
|
|
122
430
|
/** The granted spaces, each with its permissions (`spaces(connection)`). */
|
|
@@ -129,12 +437,43 @@ interface PodClient {
|
|
|
129
437
|
* empty list, so this answers `forbidden` instead, without a request.
|
|
130
438
|
*/
|
|
131
439
|
listPendingProposals(opts?: ListPendingProposalsOptions): Promise<PodResult<PendingProposals>>;
|
|
440
|
+
/** `GET /entities` (no full-text `q`: a space-scoped token may list, never search). */
|
|
441
|
+
listEntities(opts?: ListEntitiesOptions): Promise<PodResult<PodEntity[]>>;
|
|
442
|
+
/** `GET /entities/:id`; `not_found` when it does not exist or is not readable. */
|
|
443
|
+
getEntity(id: string): Promise<PodResult<PodEntity>>;
|
|
444
|
+
/** `GET /entities/:id/connections`: its relations, property links, threads and sessions. */
|
|
445
|
+
getEntityConnections(id: string, opts?: GetEntityConnectionsOptions): Promise<PodResult<PodEntityConnections>>;
|
|
446
|
+
/** `GET /documents/:id`: content, revision, sections. */
|
|
447
|
+
getDocument(id: string, opts?: GetDocumentOptions): Promise<PodResult<PodDocument>>;
|
|
448
|
+
/**
|
|
449
|
+
* `GET /focus-sessions` in a space (or a project), newest first, with their
|
|
450
|
+
* slots (`expectedOutputs`; `humanSlots(session)` = what waits on the person).
|
|
451
|
+
* `invalid` without a request when neither `workspaceId` nor `projectId` is given.
|
|
452
|
+
*/
|
|
453
|
+
listFocusSessions(opts: ListFocusSessionsOptions): Promise<PodResult<PodFocusSession[]>>;
|
|
454
|
+
/** `GET /files/:id/url`: a signed download URL for a file entity (about an hour). */
|
|
455
|
+
getFileUrl(entityId: string): Promise<PodResult<PodFileUrl>>;
|
|
456
|
+
/**
|
|
457
|
+
* `POST /proposals/:id/approve`, as the person. Only a PERSON connection
|
|
458
|
+
* (`actsAs: "person"`) holding `proposal.approve` can: an agent connection
|
|
459
|
+
* gets `forbidden` / `agent_cannot_approve` and NOTHING is sent (the pod
|
|
460
|
+
* never lets an agent approve); without the permission, `forbidden` /
|
|
461
|
+
* `not_granted`, also without a request.
|
|
462
|
+
*/
|
|
463
|
+
approveProposal(id: string, opts?: ApproveProposalOptions): Promise<PodResult<PodApproval>>;
|
|
132
464
|
get<T = unknown>(path: string, query?: Record<string, QueryValue>): Promise<PodResult<T>>;
|
|
133
465
|
post<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
|
|
134
466
|
patch<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
|
|
135
467
|
put<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
|
|
136
468
|
delete<T = unknown>(path: string, body?: unknown): Promise<PodWriteResult<T>>;
|
|
137
469
|
}
|
|
470
|
+
/** Who `connection`'s token acts as: `"person"` only when the pod said so, else `"agent"`. */
|
|
471
|
+
declare function connectionActsAs(connection: Pick<PodConnection, "actsAs">): PodActsAs;
|
|
472
|
+
/**
|
|
473
|
+
* The slots of `session` that wait on the person (Home's "needs you"):
|
|
474
|
+
* `owner: "human"`, not done, not retired.
|
|
475
|
+
*/
|
|
476
|
+
declare function humanSlots(session: Pick<PodFocusSession, "expectedOutputs">): PodSessionSlot[];
|
|
138
477
|
/** Whether `connection` was granted `permission` (in `workspaceId` when given). Exact match: grants carry no wildcard. */
|
|
139
478
|
declare function can(connection: Pick<PodConnection, "grant">, permission: string, workspaceId?: string): boolean;
|
|
140
479
|
/**
|
|
@@ -249,7 +588,12 @@ declare const DEFAULT_PATHS: PodAppPaths;
|
|
|
249
588
|
/** Refresh only inside the last 5 minutes of the access token's life. */
|
|
250
589
|
declare const REFRESH_WINDOW_MS: number;
|
|
251
590
|
interface PodAppAuthConfig {
|
|
252
|
-
/**
|
|
591
|
+
/**
|
|
592
|
+
* The app's origin, e.g. https://studio.example.com. `http://localhost` is
|
|
593
|
+
* accepted for the SDK's MOCK pod only: every real pod, a local one
|
|
594
|
+
* included, refuses an http client_id and redirect_uri, so an app that signs
|
|
595
|
+
* in to a real pod runs on https (a tunnel in development).
|
|
596
|
+
*/
|
|
253
597
|
appUrl: string;
|
|
254
598
|
/** Shown on the pod's consent screen (at most 100 characters). */
|
|
255
599
|
clientName: string;
|
|
@@ -269,7 +613,11 @@ interface PodAppAuthConfig {
|
|
|
269
613
|
* "Continue with Synap Cloud" (`cloudConnectAvailable`). Unset = address only.
|
|
270
614
|
*/
|
|
271
615
|
cloudUrl?: string | null;
|
|
272
|
-
/**
|
|
616
|
+
/**
|
|
617
|
+
* Accept a pod (and a Control Plane) on http://localhost: development
|
|
618
|
+
* against the SDK's mock pod (`@synap-core/sdk/testing`). A real local pod
|
|
619
|
+
* still refuses an app that is not on https (see `appUrl`).
|
|
620
|
+
*/
|
|
273
621
|
allowInsecureLoopback?: boolean;
|
|
274
622
|
/** Developer log of every refused sign-in; messages never carry a token. Default: console.warn. */
|
|
275
623
|
onError?: (error: ConnectError, where: "connect" | "callback" | "disconnect" | "refresh") => void;
|
|
@@ -287,9 +635,9 @@ type RefreshStatus =
|
|
|
287
635
|
| "refreshed"
|
|
288
636
|
/** The refresh failed (a lost rotation race, the pod busy) but the access token still works: keep it, set nothing. */
|
|
289
637
|
| "kept"
|
|
290
|
-
/** The access token is spent and cannot be renewed: connect again. */
|
|
638
|
+
/** 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). */
|
|
291
639
|
| "expired"
|
|
292
|
-
/** The pod refused the refresh token (access removed): connect again. */
|
|
640
|
+
/** The pod refused the refresh token (access removed): connect again. The session cookie is cleared (the marker stays). */
|
|
293
641
|
| "revoked"
|
|
294
642
|
/** The pod could not be reached and the access token is spent: try later. */
|
|
295
643
|
| "unavailable";
|
|
@@ -311,6 +659,28 @@ interface RefreshOutcome {
|
|
|
311
659
|
podUrl: string;
|
|
312
660
|
};
|
|
313
661
|
}
|
|
662
|
+
interface RefreshOptions {
|
|
663
|
+
/**
|
|
664
|
+
* Rotate now, whatever the access token's expiry says: the pod refused it
|
|
665
|
+
* (a Hub call answered `unauthorized` with `refreshable`). Needs a refresh
|
|
666
|
+
* token. If the pod refuses the refresh too, the outcome is `revoked` (or
|
|
667
|
+
* `expired`), never `kept`: the access token is known dead.
|
|
668
|
+
*/
|
|
669
|
+
force?: boolean;
|
|
670
|
+
}
|
|
671
|
+
interface PodAppClientOptions extends PodClientOptions {
|
|
672
|
+
/**
|
|
673
|
+
* Where the client may set cookies (a route handler, a server action): after
|
|
674
|
+
* a 401 it then renews the connection ONCE (rotate, seal, `persist` the
|
|
675
|
+
* Set-Cookie values, retry the request once). The rotation spends the old
|
|
676
|
+
* refresh token, so it is never done where its result cannot be stored: a
|
|
677
|
+
* server component passes no `persist`, and a 401 there answers
|
|
678
|
+
* `unauthorized` with `refreshable` (see `RefreshOptions.force`).
|
|
679
|
+
*/
|
|
680
|
+
persist?: (setCookies: string[]) => void | Promise<void>;
|
|
681
|
+
/** The request (or its Cookie header) the connection came from: chunks a larger old cookie left are cleared. */
|
|
682
|
+
request?: Request | string | null;
|
|
683
|
+
}
|
|
314
684
|
interface PodAppAuth {
|
|
315
685
|
clientId: string;
|
|
316
686
|
redirectUri: string;
|
|
@@ -342,12 +712,17 @@ interface PodAppAuth {
|
|
|
342
712
|
readLapsed(source: Request | string | null): Promise<{
|
|
343
713
|
podUrl: string;
|
|
344
714
|
} | null>;
|
|
345
|
-
/** For `proxy.ts` / a route handler: rotate near expiry, lost-race safe. */
|
|
346
|
-
refreshIfNeeded(request: Request): Promise<RefreshOutcome>;
|
|
347
|
-
/** A Hub REST client for the connection. */
|
|
348
|
-
client(connection: PodConnection, opts?:
|
|
715
|
+
/** For `proxy.ts` / a route handler: rotate near expiry (or now, `force`), lost-race safe. */
|
|
716
|
+
refreshIfNeeded(request: Request, opts?: RefreshOptions): Promise<RefreshOutcome>;
|
|
717
|
+
/** A Hub REST client for the connection (renewing it after a 401 when it can `persist`). */
|
|
718
|
+
client(connection: PodConnection, opts?: PodAppClientOptions): PodClient;
|
|
349
719
|
}
|
|
350
|
-
/**
|
|
720
|
+
/**
|
|
721
|
+
* The app's client metadata document (its client_id URL serves it). Refuses,
|
|
722
|
+
* at setup, every document the pod would refuse at sign-in: the permissions
|
|
723
|
+
* (`assertAppPermission`), at most 32 requests counted as sent, duplicates
|
|
724
|
+
* included, an IP address origin, the 5 KB cap.
|
|
725
|
+
*/
|
|
351
726
|
declare function buildClientMetadata(opts: {
|
|
352
727
|
appUrl: string;
|
|
353
728
|
clientName: string;
|
|
@@ -387,8 +762,11 @@ declare function canonicalPodUrl(input: string, opts?: {
|
|
|
387
762
|
}): string;
|
|
388
763
|
/**
|
|
389
764
|
* A typed pod address (or a launch `iss`) → its validated authorization server.
|
|
390
|
-
* Tries the address, then
|
|
391
|
-
* "pod.
|
|
765
|
+
* Tries the address, then the pod names it may stand for: on Synap Cloud,
|
|
766
|
+
* "you.synap.live" finds "you.pod.synap.live" (and the older
|
|
767
|
+
* "pod.you.synap.live"); elsewhere, "example.com" finds "pod.example.com".
|
|
768
|
+
* A redirect is never followed: an old name that answers 308 counts as a
|
|
769
|
+
* miss (`pod_moved` when nothing else answers).
|
|
392
770
|
*/
|
|
393
771
|
declare function discoverPod(input: string, opts?: DiscoverOptions): Promise<PodIssuer>;
|
|
394
772
|
/**
|
|
@@ -398,11 +776,11 @@ declare function discoverPod(input: string, opts?: DiscoverOptions): Promise<Pod
|
|
|
398
776
|
*/
|
|
399
777
|
declare function safeNextPath(value: string | null | undefined, appOrigin: string): string | null;
|
|
400
778
|
interface LaunchHint {
|
|
401
|
-
/** The pod issuer the launcher named (`iss`), to pre-fill and CONFIRM
|
|
779
|
+
/** The pod issuer the launcher named (`iss`), to pre-fill and CONFIRM, never to auto-connect. */
|
|
402
780
|
pod: string | null;
|
|
403
781
|
/** Where to land after connecting (`target_link_uri`), same origin only. */
|
|
404
782
|
next: string | null;
|
|
405
|
-
/** A space
|
|
783
|
+
/** A space hint for the pod's consent (`synap_space_hint`); a pod may ignore it. */
|
|
406
784
|
spaceHint: string | null;
|
|
407
785
|
/**
|
|
408
786
|
* The `state` of a Synap Cloud return (the CP pod chooser answers
|
|
@@ -415,7 +793,7 @@ interface LaunchHint {
|
|
|
415
793
|
cloudState: string | null;
|
|
416
794
|
}
|
|
417
795
|
/**
|
|
418
|
-
* Read a third-party-initiated login (OIDC Core §4
|
|
796
|
+
* Read a third-party-initiated login (OIDC Core §4): the browser
|
|
419
797
|
* opens the app's `initiate_login_uri?iss=…&target_link_uri=…`. One parser for
|
|
420
798
|
* the browser launch and the Synap Cloud chooser's return (`cloudState`).
|
|
421
799
|
*/
|
|
@@ -456,15 +834,18 @@ declare function cloudConnectAvailable(config?: CloudConnectConfig | null): bool
|
|
|
456
834
|
interface StartCloudConnectOptions extends CloudConnectConfig {
|
|
457
835
|
/** The app's client_id (its CIMD URL): the CP reads `initiate_login_uri` from it. */
|
|
458
836
|
clientId: string;
|
|
459
|
-
/**
|
|
837
|
+
/**
|
|
838
|
+
* SERVER-ONLY app secret that seals the transaction. Required: without it the call throws `misconfigured`.
|
|
839
|
+
* Optional in the type only for 1.x semver; required in 2.0.
|
|
840
|
+
*/
|
|
460
841
|
secret?: AppSecret;
|
|
461
842
|
/** A same-origin path to land on afterwards (validate it first: `safeNextPath`). */
|
|
462
843
|
next?: string | null;
|
|
463
|
-
/** A space
|
|
844
|
+
/** A space hint for the pod's consent (`synap_space_hint`); a pod may ignore it. */
|
|
464
845
|
spaceHint?: string | null;
|
|
465
846
|
/** Show the pod's consent again even if this browser is already connected to the pod the person picks. */
|
|
466
847
|
prompt?: "consent" | null;
|
|
467
|
-
/** @deprecated Ignored: the SDK always draws its own 32-byte state. */
|
|
848
|
+
/** @deprecated Ignored: the SDK always draws its own 32-byte state. Removed in 2.0. */
|
|
468
849
|
state?: string;
|
|
469
850
|
now?: () => number;
|
|
470
851
|
}
|
|
@@ -478,7 +859,10 @@ interface StartedCloudConnect {
|
|
|
478
859
|
}
|
|
479
860
|
/**
|
|
480
861
|
* Start a Synap Cloud sign-in. Throws `cp_unavailable` at once when no CP URL
|
|
481
|
-
* is configured
|
|
862
|
+
* is configured, `misconfigured` without a secret: both SYNCHRONOUSLY, before
|
|
863
|
+
* a Promise exists, so wrap the call in try/catch rather than relying on
|
|
864
|
+
* `.catch` (kept from 1.3 for compatibility; 2.0 makes it a plain async
|
|
865
|
+
* function with a required secret).
|
|
482
866
|
*/
|
|
483
867
|
declare function startCloudConnect(opts: StartCloudConnectOptions): Promise<StartedCloudConnect>;
|
|
484
868
|
interface AcceptCloudReturnOptions {
|
|
@@ -507,9 +891,11 @@ interface CloudReturn {
|
|
|
507
891
|
*/
|
|
508
892
|
declare function acceptCloudReturn(opts: AcceptCloudReturnOptions): Promise<CloudReturn>;
|
|
509
893
|
|
|
894
|
+
declare function isBrowserRuntime(): boolean;
|
|
895
|
+
|
|
510
896
|
/** How long a started sign-in stays valid. */
|
|
511
897
|
declare const TRANSACTION_TTL_MS: number;
|
|
512
|
-
/** The refresh token's idle life when the pod does not say (
|
|
898
|
+
/** The refresh token's idle life when the pod does not say (the pod's: 30 days). */
|
|
513
899
|
declare const DEFAULT_REFRESH_IDLE_MS: number;
|
|
514
900
|
/** A sealed `ConnectTransaction`: opaque, safe to put in an HttpOnly cookie. */
|
|
515
901
|
type SealedTransaction = string & {
|
|
@@ -587,8 +973,8 @@ interface CallbackResult {
|
|
|
587
973
|
declare function handleCallback(opts: HandleCallbackOptions): Promise<CallbackResult>;
|
|
588
974
|
/**
|
|
589
975
|
* A token response → a connection. On a refresh, `previous` supplies what the
|
|
590
|
-
* response may omit (grant, user, app). `expires_in` is read as sent: a refresh
|
|
591
|
-
* answered within the pod's reuse grace
|
|
976
|
+
* response may omit (grant, user, app; never `actsAs`). `expires_in` is read as sent: a refresh
|
|
977
|
+
* answered within the pod's reuse grace hands back the live key the
|
|
592
978
|
* first redeem got, with less than an hour left. A refresh response WITHOUT a new
|
|
593
979
|
* refresh_token drops the old one: with single-use rotation, resending a
|
|
594
980
|
* consumed token reads as theft and revokes the whole family.
|
|
@@ -603,7 +989,7 @@ declare function parseTokenResponse(body: Record<string, unknown>, ctx: {
|
|
|
603
989
|
}): PodConnection;
|
|
604
990
|
/**
|
|
605
991
|
* How long a rotation's result is remembered for its SPENT refresh token.
|
|
606
|
-
* The pod
|
|
992
|
+
* The pod refuses a concurrent second redeem softly, but a token
|
|
607
993
|
* presented AGAIN after its use is a reuse: the whole family and the live
|
|
608
994
|
* access key are revoked. A browser easily sends that replay: parallel
|
|
609
995
|
* requests, a second tab, a response it had not stored yet, all still
|
|
@@ -613,7 +999,7 @@ declare function parseTokenResponse(body: Record<string, unknown>, ctx: {
|
|
|
613
999
|
declare const ROTATION_MEMORY_MS: number;
|
|
614
1000
|
/**
|
|
615
1001
|
* Exchange the refresh token for a new access token and the rotated refresh
|
|
616
|
-
* token (
|
|
1002
|
+
* token (single-use, 30 days from issue, its reach re-derived by the pod).
|
|
617
1003
|
*
|
|
618
1004
|
* SINGLE-FLIGHT per connection within this process: concurrent callers share
|
|
619
1005
|
* one request, and for `ROTATION_MEMORY_MS` after it a caller still holding
|
|
@@ -627,7 +1013,7 @@ declare const ROTATION_MEMORY_MS: number;
|
|
|
627
1013
|
* (invalid_grant while it should live: spent, revoked, the app removed or
|
|
628
1014
|
* narrowed to nothing, or a concurrent redeem lost), `misconfigured`
|
|
629
1015
|
* (invalid_client, unauthorized_client), `rate_limited`, `pod_unreachable`,
|
|
630
|
-
* `server_error`.
|
|
1016
|
+
* `pod_moved` (the token endpoint answered a redirect), `server_error`.
|
|
631
1017
|
*/
|
|
632
1018
|
declare function refreshConnection(connection: PodConnection, opts?: FlowOptions): Promise<PodConnection>;
|
|
633
1019
|
/**
|
|
@@ -642,6 +1028,4 @@ declare function revokeConnection(connection: PodConnection, opts?: FlowOptions)
|
|
|
642
1028
|
/** RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(code_verifier)))`. */
|
|
643
1029
|
declare function pkceChallenge(verifier: string): Promise<string>;
|
|
644
1030
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
export { type AcceptCloudReturnOptions, AppRequest, type AppSecret, 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 HandleCallbackOptions, type LaunchHint, type ListPendingProposalsOptions, MARKER_TTL_SEC, MIN_SECRET_LENGTH, type Opened, type PendingProposal, type PendingProposals, type PodAppAuth, type PodAppAuthConfig, type PodAppPaths, type PodClient, type PodClientOptions, PodConnection, type PodError, type PodErrorCode, PodGrant, PodIssuer, type PodResult, PodSpace, type PodWriteResult, type QueryValue, REFRESH_WINDOW_MS, ROTATION_MEMORY_MS, 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, connectionExpiresAt, cookieNames, createPodAppAuth, createPodClient, createSealedCookieStore, discoverPod, handleCallback, isBrowserRuntime, isConnectError, isPrefetchOrAsset, markerCookie, openTransaction, parseConnectErrorCode, parseTokenResponse, pkceChallenge, readMarker, refreshConnection, reviewUrlFor, revokeConnection, safeNextPath, seal, spaces, startCloudConnect, startConnect, unseal };
|
|
1031
|
+
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 PodEntityFacet, 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 };
|