@recursiv/sdk 0.7.121 → 0.7.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,493 @@
1
+ /**
2
+ * The Sealed frame's protocol, version 1: the messages between a host page (minds.com/ai, build.minds.com) and the
3
+ * Sealed frame it embeds (https://sealed.minds.com), and between a host and the frame's sign-out page (/wipe.html).
4
+ * Both sides use these types, builders and validators, so there is one definition of what may cross.
5
+ *
6
+ * How the two talk (the Sealed frame design of 2026-10-13, section 5):
7
+ * 1. On every load event of the frame's iframe, the host posts `sealed/hello` with a transferred MessagePort to the
8
+ * frame origin only (`targetOrigin`). The frame must add its window message listener as soon as its module runs, and
9
+ * accepts the first hello whose `event.origin` is one of its compiled hosts and whose `event.source` is
10
+ * `window.parent`. After that both sides use the port only.
11
+ * 2. The frame answers `ready` on the port, naming the protocol it speaks; a host that speaks another shows "Reload
12
+ * the page". Nothing else counts before `ready`.
13
+ * 3. Host to frame, on the port: `token`, `theme`, `layout`, `account`, `signed-out`, `new-chat`.
14
+ * Frame to host, on the port: `need-token`, `state`, `navigate`, `wiped`.
15
+ *
16
+ * Every field is an enum, a boolean, an id, a time or the opaque token. Never sent, either way: typed words, answers,
17
+ * chat ids, titles or counts, failure details, or when sends happen. There are no size messages: the host sizes the
18
+ * frame with CSS, since a height would say how long the transcript is.
19
+ *
20
+ * Every message is checked before it is read. A validator returns a fresh copy holding only the message's own fields,
21
+ * or null for anything else: a type it does not know, a missing, extra or wrongly typed field, a string outside its
22
+ * enum or pattern, a getter or symbol key, or a message over 4 KB of JSON. The receiver ignores null. Builders check
23
+ * their own output the same way and throw a TypeError rather than send a message the other side would drop.
24
+ */
25
+ /** The protocol this SDK speaks, which `ready` names. */
26
+ export declare const SEALED_FRAME_PROTOCOL = 1;
27
+ /** Every message's format version (`v`). */
28
+ export declare const SEALED_FRAME_MESSAGE_VERSION = 1;
29
+ /** The most a message may be, as UTF-8 JSON. Anything larger is dropped unread. */
30
+ export declare const SEALED_FRAME_MESSAGE_MAX_BYTES = 4096;
31
+ /**
32
+ * The frame's iframe sandbox. It protects the host, not the frame: the frame's protection is the same-origin policy and
33
+ * its own CSP. `allow-same-origin` keeps the frame's own storage; popups let crisis and help links open in a new tab.
34
+ * There is no `allow-forms`, in line with the frame CSP's `form-action 'none'`: embedded, the browser stops a form
35
+ * submission before its `submit` event fires, so the frame must not use `<form>` submission (handle Enter and the send
36
+ * button with key and click handlers). Standalone at its own origin a form would work, which is why tests miss it.
37
+ */
38
+ export declare const SEALED_FRAME_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox";
39
+ /** The sign-out page's sandbox: it runs a script against the frame origin's storage and opens nothing. */
40
+ export declare const SEALED_WIPE_SANDBOX = "allow-scripts allow-same-origin";
41
+ /** The frame's permissions policy: only writing to the clipboard (copy an answer). */
42
+ export declare const SEALED_FRAME_ALLOW = "clipboard-write";
43
+ /** No referrer leaves the host for the frame. */
44
+ export declare const SEALED_FRAME_REFERRER_POLICY = "no-referrer";
45
+ /** The frame's sign-out page, on the frame origin. */
46
+ export declare const SEALED_WIPE_PATH = "/wipe.html";
47
+ /** Which host embeds the frame. */
48
+ export declare const SEALED_FRAME_HOST_KINDS: readonly ["minds", "build"];
49
+ export type SealedFrameHostKind = (typeof SEALED_FRAME_HOST_KINDS)[number];
50
+ export declare const SEALED_FRAME_SCHEMES: readonly ["light", "dark"];
51
+ export type SealedFrameScheme = (typeof SEALED_FRAME_SCHEMES)[number];
52
+ /** `column`: a phone-width column. `workspace`: the desktop thread area, where the frame lists its own Sealed chats. */
53
+ export declare const SEALED_FRAME_LAYOUTS: readonly ["column", "workspace"];
54
+ export type SealedFrameLayout = (typeof SEALED_FRAME_LAYOUTS)[number];
55
+ /** Why the frame cannot run Sealed now (`state`, `unavailable`). */
56
+ export declare const SEALED_FRAME_UNAVAILABLE_REASONS: readonly ["unsupported", "tab_busy", "off", "words", "signed_out", "update", "no_account"];
57
+ export type SealedFrameUnavailableReason = (typeof SEALED_FRAME_UNAVAILABLE_REASONS)[number];
58
+ /** Where the frame asks the host to take the member (`navigate`). */
59
+ export declare const SEALED_FRAME_NAVIGATE_TARGETS: readonly ["plans", "settings_ai", "sign_in", "get_app"];
60
+ export type SealedFrameNavigateTarget = (typeof SEALED_FRAME_NAVIGATE_TARGETS)[number];
61
+ /**
62
+ * Host to frame, by window.postMessage to the frame origin with one transferred MessagePort, on every load of the
63
+ * frame. `account` is the signed-in member's user id, and null exactly when `signedIn` is false: a hello signed out
64
+ * wipes every account's Sealed data in that browser (the shared-computer rule).
65
+ */
66
+ export interface SealedFrameHello {
67
+ t: 'sealed/hello';
68
+ v: 1;
69
+ host: SealedFrameHostKind;
70
+ scheme: SealedFrameScheme;
71
+ layout: SealedFrameLayout;
72
+ signedIn: boolean;
73
+ account: string | null;
74
+ }
75
+ /** Host to frame, on the port. */
76
+ export type SealedHostMessage =
77
+ /** In answer to `need-token`: the frame token (memory only), when it expires (ms since the epoch), whose it is. */
78
+ {
79
+ t: 'token';
80
+ v: 1;
81
+ token: string;
82
+ expiresAt: number;
83
+ account: string;
84
+ } | {
85
+ t: 'theme';
86
+ v: 1;
87
+ scheme: SealedFrameScheme;
88
+ } | {
89
+ t: 'layout';
90
+ v: 1;
91
+ layout: SealedFrameLayout;
92
+ }
93
+ /** The member switched accounts: the frame drops its session and token. */
94
+ | {
95
+ t: 'account';
96
+ v: 1;
97
+ account: string;
98
+ }
99
+ /** The member signed out: the frame wipes that account's Sealed data and answers `wiped`. */
100
+ | {
101
+ t: 'signed-out';
102
+ v: 1;
103
+ } | {
104
+ t: 'new-chat';
105
+ v: 1;
106
+ };
107
+ /** The frame's state, as the host shows it. */
108
+ export type SealedFrameState = {
109
+ state: 'ready';
110
+ } | {
111
+ state: 'unavailable';
112
+ reason: SealedFrameUnavailableReason;
113
+ };
114
+ /** Frame to host, on the port. */
115
+ export type SealedFrameMessage =
116
+ /** The first answer: the protocol the frame speaks and its release id. */
117
+ {
118
+ t: 'ready';
119
+ v: 1;
120
+ protocol: number;
121
+ release: string;
122
+ }
123
+ /** On start, 60 s before the token expires, and after the API refused the token. */
124
+ | {
125
+ t: 'need-token';
126
+ v: 1;
127
+ } | ({
128
+ t: 'state';
129
+ v: 1;
130
+ } & SealedFrameState) | {
131
+ t: 'navigate';
132
+ v: 1;
133
+ to: SealedFrameNavigateTarget;
134
+ }
135
+ /** The account's Sealed data is gone (after `signed-out`, or the sign-out page's `sealed/wipe`). */
136
+ | {
137
+ t: 'wiped';
138
+ v: 1;
139
+ };
140
+ /**
141
+ * Host to the sign-out page (/wipe.html), by window.postMessage to the frame origin with one transferred MessagePort.
142
+ * `account` null wipes every account's Sealed data in that browser. The page answers `wiped` on the port.
143
+ */
144
+ export interface SealedWipeRequest {
145
+ t: 'sealed/wipe';
146
+ v: 1;
147
+ account: string | null;
148
+ }
149
+ /** Whether a value is a frame token in the API's format. */
150
+ export declare function isSealedFrameToken(value: unknown): value is string;
151
+ /** A `sealed/hello`, as the frame reads it; null for anything else. */
152
+ export declare function parseSealedFrameHello(data: unknown): SealedFrameHello | null;
153
+ /** A host-to-frame port message, as the frame reads it; null for anything else. */
154
+ export declare function parseSealedHostMessage(data: unknown): SealedHostMessage | null;
155
+ /** A frame-to-host port message, as the host reads it; null for anything else. */
156
+ export declare function parseSealedFrameMessage(data: unknown): SealedFrameMessage | null;
157
+ /** A `sealed/wipe`, as the sign-out page reads it; null for anything else. */
158
+ export declare function parseSealedWipeRequest(data: unknown): SealedWipeRequest | null;
159
+ /** The host's `sealed/hello`. `account` is the signed-in member's user id, or null when no one is signed in. */
160
+ export declare function sealedFrameHello(input: {
161
+ host: SealedFrameHostKind;
162
+ scheme: SealedFrameScheme;
163
+ layout: SealedFrameLayout;
164
+ account: string | null;
165
+ }): SealedFrameHello;
166
+ /** The host's port messages. */
167
+ export declare const sealedHostMessage: {
168
+ token: (token: string, expiresAt: number, account: string) => {
169
+ t: "token";
170
+ v: 1;
171
+ token: string;
172
+ expiresAt: number;
173
+ account: string;
174
+ } | {
175
+ t: "theme";
176
+ v: 1;
177
+ scheme: SealedFrameScheme;
178
+ } | {
179
+ t: "layout";
180
+ v: 1;
181
+ layout: SealedFrameLayout;
182
+ } | {
183
+ t: "account";
184
+ v: 1;
185
+ account: string;
186
+ } | {
187
+ t: "signed-out";
188
+ v: 1;
189
+ } | {
190
+ t: "new-chat";
191
+ v: 1;
192
+ };
193
+ theme: (scheme: SealedFrameScheme) => {
194
+ t: "token";
195
+ v: 1;
196
+ token: string;
197
+ expiresAt: number;
198
+ account: string;
199
+ } | {
200
+ t: "theme";
201
+ v: 1;
202
+ scheme: SealedFrameScheme;
203
+ } | {
204
+ t: "layout";
205
+ v: 1;
206
+ layout: SealedFrameLayout;
207
+ } | {
208
+ t: "account";
209
+ v: 1;
210
+ account: string;
211
+ } | {
212
+ t: "signed-out";
213
+ v: 1;
214
+ } | {
215
+ t: "new-chat";
216
+ v: 1;
217
+ };
218
+ layout: (layout: SealedFrameLayout) => {
219
+ t: "token";
220
+ v: 1;
221
+ token: string;
222
+ expiresAt: number;
223
+ account: string;
224
+ } | {
225
+ t: "theme";
226
+ v: 1;
227
+ scheme: SealedFrameScheme;
228
+ } | {
229
+ t: "layout";
230
+ v: 1;
231
+ layout: SealedFrameLayout;
232
+ } | {
233
+ t: "account";
234
+ v: 1;
235
+ account: string;
236
+ } | {
237
+ t: "signed-out";
238
+ v: 1;
239
+ } | {
240
+ t: "new-chat";
241
+ v: 1;
242
+ };
243
+ account: (account: string) => {
244
+ t: "token";
245
+ v: 1;
246
+ token: string;
247
+ expiresAt: number;
248
+ account: string;
249
+ } | {
250
+ t: "theme";
251
+ v: 1;
252
+ scheme: SealedFrameScheme;
253
+ } | {
254
+ t: "layout";
255
+ v: 1;
256
+ layout: SealedFrameLayout;
257
+ } | {
258
+ t: "account";
259
+ v: 1;
260
+ account: string;
261
+ } | {
262
+ t: "signed-out";
263
+ v: 1;
264
+ } | {
265
+ t: "new-chat";
266
+ v: 1;
267
+ };
268
+ signedOut: () => {
269
+ t: "token";
270
+ v: 1;
271
+ token: string;
272
+ expiresAt: number;
273
+ account: string;
274
+ } | {
275
+ t: "theme";
276
+ v: 1;
277
+ scheme: SealedFrameScheme;
278
+ } | {
279
+ t: "layout";
280
+ v: 1;
281
+ layout: SealedFrameLayout;
282
+ } | {
283
+ t: "account";
284
+ v: 1;
285
+ account: string;
286
+ } | {
287
+ t: "signed-out";
288
+ v: 1;
289
+ } | {
290
+ t: "new-chat";
291
+ v: 1;
292
+ };
293
+ newChat: () => {
294
+ t: "token";
295
+ v: 1;
296
+ token: string;
297
+ expiresAt: number;
298
+ account: string;
299
+ } | {
300
+ t: "theme";
301
+ v: 1;
302
+ scheme: SealedFrameScheme;
303
+ } | {
304
+ t: "layout";
305
+ v: 1;
306
+ layout: SealedFrameLayout;
307
+ } | {
308
+ t: "account";
309
+ v: 1;
310
+ account: string;
311
+ } | {
312
+ t: "signed-out";
313
+ v: 1;
314
+ } | {
315
+ t: "new-chat";
316
+ v: 1;
317
+ };
318
+ };
319
+ /**
320
+ * The frame's port messages. Each takes only enums, numbers and the frame's compiled release id, so nothing the member
321
+ * typed or read can be put in one.
322
+ */
323
+ export declare const sealedFrameMessage: {
324
+ ready: (release: string) => {
325
+ t: "ready";
326
+ v: 1;
327
+ protocol: number;
328
+ release: string;
329
+ } | {
330
+ t: "need-token";
331
+ v: 1;
332
+ } | ({
333
+ t: "state";
334
+ v: 1;
335
+ } & {
336
+ state: "ready";
337
+ }) | ({
338
+ t: "state";
339
+ v: 1;
340
+ } & {
341
+ state: "unavailable";
342
+ reason: SealedFrameUnavailableReason;
343
+ }) | {
344
+ t: "navigate";
345
+ v: 1;
346
+ to: SealedFrameNavigateTarget;
347
+ } | {
348
+ t: "wiped";
349
+ v: 1;
350
+ };
351
+ needToken: () => {
352
+ t: "ready";
353
+ v: 1;
354
+ protocol: number;
355
+ release: string;
356
+ } | {
357
+ t: "need-token";
358
+ v: 1;
359
+ } | ({
360
+ t: "state";
361
+ v: 1;
362
+ } & {
363
+ state: "ready";
364
+ }) | ({
365
+ t: "state";
366
+ v: 1;
367
+ } & {
368
+ state: "unavailable";
369
+ reason: SealedFrameUnavailableReason;
370
+ }) | {
371
+ t: "navigate";
372
+ v: 1;
373
+ to: SealedFrameNavigateTarget;
374
+ } | {
375
+ t: "wiped";
376
+ v: 1;
377
+ };
378
+ stateReady: () => {
379
+ t: "ready";
380
+ v: 1;
381
+ protocol: number;
382
+ release: string;
383
+ } | {
384
+ t: "need-token";
385
+ v: 1;
386
+ } | ({
387
+ t: "state";
388
+ v: 1;
389
+ } & {
390
+ state: "ready";
391
+ }) | ({
392
+ t: "state";
393
+ v: 1;
394
+ } & {
395
+ state: "unavailable";
396
+ reason: SealedFrameUnavailableReason;
397
+ }) | {
398
+ t: "navigate";
399
+ v: 1;
400
+ to: SealedFrameNavigateTarget;
401
+ } | {
402
+ t: "wiped";
403
+ v: 1;
404
+ };
405
+ unavailable: (reason: SealedFrameUnavailableReason) => {
406
+ t: "ready";
407
+ v: 1;
408
+ protocol: number;
409
+ release: string;
410
+ } | {
411
+ t: "need-token";
412
+ v: 1;
413
+ } | ({
414
+ t: "state";
415
+ v: 1;
416
+ } & {
417
+ state: "ready";
418
+ }) | ({
419
+ t: "state";
420
+ v: 1;
421
+ } & {
422
+ state: "unavailable";
423
+ reason: SealedFrameUnavailableReason;
424
+ }) | {
425
+ t: "navigate";
426
+ v: 1;
427
+ to: SealedFrameNavigateTarget;
428
+ } | {
429
+ t: "wiped";
430
+ v: 1;
431
+ };
432
+ navigate: (to: SealedFrameNavigateTarget) => {
433
+ t: "ready";
434
+ v: 1;
435
+ protocol: number;
436
+ release: string;
437
+ } | {
438
+ t: "need-token";
439
+ v: 1;
440
+ } | ({
441
+ t: "state";
442
+ v: 1;
443
+ } & {
444
+ state: "ready";
445
+ }) | ({
446
+ t: "state";
447
+ v: 1;
448
+ } & {
449
+ state: "unavailable";
450
+ reason: SealedFrameUnavailableReason;
451
+ }) | {
452
+ t: "navigate";
453
+ v: 1;
454
+ to: SealedFrameNavigateTarget;
455
+ } | {
456
+ t: "wiped";
457
+ v: 1;
458
+ };
459
+ wiped: () => {
460
+ t: "ready";
461
+ v: 1;
462
+ protocol: number;
463
+ release: string;
464
+ } | {
465
+ t: "need-token";
466
+ v: 1;
467
+ } | ({
468
+ t: "state";
469
+ v: 1;
470
+ } & {
471
+ state: "ready";
472
+ }) | ({
473
+ t: "state";
474
+ v: 1;
475
+ } & {
476
+ state: "unavailable";
477
+ reason: SealedFrameUnavailableReason;
478
+ }) | {
479
+ t: "navigate";
480
+ v: 1;
481
+ to: SealedFrameNavigateTarget;
482
+ } | {
483
+ t: "wiped";
484
+ v: 1;
485
+ };
486
+ };
487
+ /** The host's `sealed/wipe` for the sign-out page. `account` null wipes every account's Sealed data in the browser. */
488
+ export declare function sealedWipeRequest(account: string | null): SealedWipeRequest;
489
+ /**
490
+ * The frame origin as an exact origin (`https://sealed.minds.com`), or a TypeError. Only https, or http on localhost
491
+ * for development; no path, query, fragment or credentials.
492
+ */
493
+ export declare function sealedFrameOrigin(value: string): string;