@muhkoo/connect 0.2.0-alpha.2 → 0.4.0-alpha.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/dist/connect.d.ts CHANGED
@@ -936,6 +936,22 @@ declare class EncryptedSession {
936
936
  interface WSTransportOptions {
937
937
  /** WebSocket URL, e.g. `ws://localhost:8787/foo`. */
938
938
  url: string;
939
+ /**
940
+ * Optional async provider for a FRESH connection URL, called before each
941
+ * RECONNECT. Use it when the URL carries a single-use/expiring credential
942
+ * (e.g. a one-time WS upgrade ticket): on reconnect the baked-in ticket is
943
+ * already consumed/expired, so without this the reconnect is rejected and the
944
+ * transport burns through its attempts. The initial connect uses `url` as-is.
945
+ */
946
+ urlProvider?: () => string | Promise<string>;
947
+ /**
948
+ * Stable identifier for this connection (a channel / space / personal-space
949
+ * id). Lifecycle + MESSAGE events are namespaced by it on the shared
950
+ * `EventCore` bus so concurrent transports (chat room A, room B, the storage
951
+ * feed, …) never receive each other's frames. Optional — a unique id is
952
+ * generated when omitted; pass a semantic one for clearer debugging.
953
+ */
954
+ id?: string;
939
955
  /** Reconnect automatically after unexpected disconnect (default `true`). */
940
956
  autoReconnect?: boolean;
941
957
  /** Delay between reconnect attempts in ms (default `3000`). */
@@ -947,12 +963,43 @@ interface WSTransportOptions {
947
963
  * throws synchronously. Default `100`.
948
964
  */
949
965
  maxQueueSize?: number;
966
+ /**
967
+ * Heartbeat interval in ms. While connected, a `pingFrame` is sent every
968
+ * interval to keep the socket warm (long-lived WebSockets get dropped by CF
969
+ * / intermediaries when idle) and to detect a silently-dead connection.
970
+ * Default `30000`. Set `0` to disable the heartbeat entirely.
971
+ */
972
+ heartbeatInterval?: number;
973
+ /**
974
+ * How long (ms) to wait for a `pongFrame` after sending a ping before
975
+ * treating the connection as dead and forcing a reconnect. Should be well
976
+ * under `heartbeatInterval`. Default `10000`.
977
+ */
978
+ heartbeatTimeout?: number;
979
+ /** Outbound keep-alive frame. Default `"ping"`. */
980
+ pingFrame?: string;
981
+ /**
982
+ * Inbound keep-alive ack. Matching frames are swallowed (never surfaced as a
983
+ * `MESSAGE`) and reset the liveness timer. Must match the server's
984
+ * auto-response. Default `"pong"`.
985
+ */
986
+ pongFrame?: string;
950
987
  }
951
988
  declare class WSTransport extends EventCore {
952
- emit: typeof EventCore.emit;
953
- on: typeof EventCore.on;
954
- off: typeof EventCore.off;
989
+ /**
990
+ * Per-instance event scope. `EventCore` is a process-global static emitter,
991
+ * so without namespacing every WSTransport would hear every other one's
992
+ * frames. We prefix each event with this transport's `id` and expose
993
+ * `on`/`off`/`emit` that scope transparently — callers still use the plain
994
+ * `EventCoreEvents` enum and only ever see their own connection's events.
995
+ */
996
+ readonly id: string;
997
+ readonly emit: (event: EventCoreEvents, data: unknown) => void;
998
+ readonly on: (event: EventCoreEvents, handler: EventListener | CallableFunction) => void;
999
+ readonly off: (event: EventCoreEvents, handler: EventListener | CallableFunction) => void;
1000
+ private scopedEvent;
955
1001
  private url;
1002
+ private urlProvider?;
956
1003
  private socket;
957
1004
  private _isConnected;
958
1005
  private _isConnecting;
@@ -963,6 +1010,12 @@ declare class WSTransport extends EventCore {
963
1010
  private reconnectTimeout;
964
1011
  private outboundQueue;
965
1012
  private maxQueueSize;
1013
+ private heartbeatInterval;
1014
+ private heartbeatTimeout;
1015
+ private pingFrame;
1016
+ private pongFrame;
1017
+ private heartbeatTimer;
1018
+ private pongTimer;
966
1019
  constructor(opts: WSTransportOptions);
967
1020
  /** Open the socket. Resolves when CONNECTED fires. Rejects on first error. */
968
1021
  connect(): Promise<void>;
@@ -977,6 +1030,25 @@ declare class WSTransport extends EventCore {
977
1030
  isConnecting(): boolean;
978
1031
  queuedFrames(): number;
979
1032
  reconnectAttemptCount(): number;
1033
+ /** Begin the keep-alive loop (no-op when disabled). */
1034
+ private startHeartbeat;
1035
+ /** Stop the keep-alive loop and clear any pending liveness deadline. */
1036
+ private stopHeartbeat;
1037
+ /**
1038
+ * Send one ping and arm a liveness deadline. If no `pongFrame` arrives before
1039
+ * `heartbeatTimeout`, the connection is considered dead and we force a
1040
+ * reconnect — this is what catches a silently-dropped TCP connection that
1041
+ * never fired `onclose` (the failure mode that "loses connection with the
1042
+ * Accelerator" on long sessions).
1043
+ */
1044
+ private sendHeartbeat;
1045
+ /**
1046
+ * Tear down a connection we believe is dead and kick off reconnect, without
1047
+ * waiting on the OS/browser to eventually fire `onclose` (which a half-open
1048
+ * socket may never do). We detach the handlers first so the normal close path
1049
+ * can't double-fire the reconnect.
1050
+ */
1051
+ private forceReconnect;
980
1052
  private scheduleReconnect;
981
1053
  private flushQueue;
982
1054
  }
@@ -1382,123 +1454,50 @@ declare class ShardClient {
1382
1454
  }
1383
1455
 
1384
1456
  /**
1385
- * HTTP client for the multi-user `SharedSpaceDO` (Phase B).
1386
- *
1387
- * Mirrors the proven shape of `PersonalSpaceClient` — every gated call:
1388
- * 1. POSTs `/challenge` to get a fresh nonce keyed to the caller's commitment
1389
- * 2. Generates a Groth16 proof for the `preimagePoK` circuit with the nonce
1390
- * reduced into a BN254 field element as the public signal
1391
- * 3. POSTs the operation with `{ challengeId, proof, publicSignals, ... }`
1392
- *
1393
- * What's different from `PersonalSpaceClient`:
1394
- * - Addressed by space ID, not user commitment. Multiple users can be
1395
- * members of the same space.
1396
- * - The space carries a participants list + ACL (shared types from
1397
- * `src/types/permissions.ts`). The server checks that the proving
1398
- * commitment is a participant with the requested permission before each
1399
- * mutation.
1400
- * - Stores file manifests (not generic KV blobs). The shape is constrained
1401
- * by `FileManifest` from `../types`.
1402
- *
1403
- * Wire protocol (this client implements the client side; the server side is
1404
- * the next slice of work in accelerator):
1405
- *
1406
- * POST /api/spaces create a new space (gated by caller proof)
1407
- * body: { challengeId, proof, publicSignals, initialAcl? }
1408
- * returns: { spaceId }
1409
- *
1410
- * POST /api/spaces/:spaceId/challenge issue a nonce for the caller
1411
- * body: { commitment }
1412
- * returns: { challengeId, nonce }
1413
- *
1414
- * POST /api/spaces/:spaceId/files write a file manifest
1415
- * body: { challengeId, proof, publicSignals, manifest }
1416
- * returns: { ok: true, fileId }
1417
- *
1418
- * POST /api/spaces/:spaceId/files/:fileId/get read a file manifest
1419
- * body: { challengeId, proof, publicSignals }
1420
- * returns: { manifest }
1421
- *
1422
- * DELETE /api/spaces/:spaceId/files/:fileId delete a file manifest
1423
- * body: { challengeId, proof, publicSignals }
1424
- * returns: { ok: true, existed: boolean }
1425
- *
1426
- * POST /api/spaces/:spaceId/files/list list manifests in space
1427
- * body: { challengeId, proof, publicSignals }
1428
- * returns: { files: FileStat[] }
1457
+ * HTTP client for the gated file-manifest store on a SharedSpace
1458
+ * (`/api/spaces/:spaceId/files`).
1429
1459
  *
1430
- * POST /api/spaces/:spaceId/participants add a participant
1431
- * body: { challengeId, proof, publicSignals, participant: Participant }
1432
- * returns: { ok: true }
1460
+ * A "file" in a space is its **manifest** (per-chunk keys + the SHA-256 of each
1461
+ * global shard). The shard bytes live in the open `/api/shards` store; the
1462
+ * manifest is the capability. This client only moves manifests in/out of the
1463
+ * space's gated store.
1433
1464
  *
1434
- * DELETE /api/spaces/:spaceId/participants/:commitment remove a participant
1435
- * body: { challengeId, proof, publicSignals }
1436
- * returns: { ok: true, existed: boolean }
1465
+ * Authorization is **session-based**, consistent with the rest of the
1466
+ * SharedSpace (keyring, websocket): the SDK's credential-stamping `fetch`
1467
+ * (from {@link HttpClient}) attaches the app key + session token, the platform
1468
+ * validates the session and injects a trusted `X-Muhkoo-User-Context`, and the
1469
+ * DO authorizes on that + channel membership. There is no per-op Groth16 proof.
1437
1470
  *
1438
- * POST /api/spaces/:spaceId/acl set / replace an ACL entry
1439
- * body: { challengeId, proof, publicSignals, entry: ACLEntry }
1440
- * returns: { ok: true }
1471
+ * Wire protocol (server: `SharedSpaceDO`):
1472
+ * POST /api/spaces/:spaceId/files body: { manifest } → { ok, fileId }
1473
+ * GET /api/spaces/:spaceId/files → { files: FileStat[] }
1474
+ * GET /api/spaces/:spaceId/files/:fileId → { manifest }
1475
+ * DELETE /api/spaces/:spaceId/files/:fileId → { ok, existed }
1441
1476
  */
1442
1477
 
1443
1478
  interface SharedSpaceClientOptions {
1444
- /** Base URL of the accelerator. */
1479
+ /** Base URL of the accelerator (e.g. `https://api.muhkoo.dev`). */
1445
1480
  baseUrl: string;
1446
- /** This caller's Poseidon commitment (decimal string). */
1447
- commitment: string;
1448
- /** Private witnesses for the `preimagePoK` circuit. */
1449
- secret: string;
1450
- salt: string;
1451
- ecdsaPub: string;
1452
- /** Public signal: Poseidon hash of `ecdsaPub`. */
1453
- ecdsaPubHash: string;
1454
- /** Compiled circuit assets — same files PersonalSpaceClient uses. */
1455
- circuits: {
1456
- wasmUrl: string;
1457
- zkeyUrl: string;
1458
- };
1459
- /** Optional custom fetch (defaults to `globalThis.fetch`). */
1481
+ /**
1482
+ * Credential-stamping fetch — pass {@link HttpClient.fetch} so requests
1483
+ * carry the app key + session token. Defaults to `globalThis.fetch`
1484
+ * (unauthenticated; only useful in tests).
1485
+ */
1460
1486
  fetch?: typeof fetch;
1461
1487
  }
1462
1488
  declare class SharedSpaceClient {
1463
1489
  private readonly baseUrl;
1464
- private readonly commitment;
1465
- private readonly secret;
1466
- private readonly salt;
1467
- private readonly ecdsaPub;
1468
- private readonly ecdsaPubHash;
1469
- private readonly wasmUrl;
1470
- private readonly zkeyUrl;
1471
1490
  private readonly fetchFn;
1472
1491
  constructor(opts: SharedSpaceClientOptions);
1473
- /**
1474
- * Create a brand new space. The caller becomes the owner; `initialAcl`
1475
- * adds any additional ACL entries up front (e.g. inviting a co-owner at
1476
- * creation time).
1477
- */
1478
- createSpace(initialAcl?: ACLEntry[]): Promise<{
1479
- spaceId: string;
1480
- }>;
1481
- /** Write a file manifest into the space. Requires `write` permission. */
1492
+ /** Write a file manifest into the space. Requires space membership. */
1482
1493
  writeFileManifest(spaceId: string, manifest: FileManifest): Promise<void>;
1483
- /** Read a file manifest by id. Requires `read` permission. */
1494
+ /** Read a file manifest by id. Requires space membership. */
1484
1495
  readFileManifest(spaceId: string, fileId: string): Promise<FileManifest>;
1485
- /** Delete a file manifest by id. Requires `delete` permission. */
1496
+ /** Delete a file manifest by id. Requires space membership. */
1486
1497
  deleteFileManifest(spaceId: string, fileId: string): Promise<boolean>;
1487
- /** List file summaries in the space. Requires `read` permission. */
1498
+ /** List file summaries in the space. Requires space membership. */
1488
1499
  listFiles(spaceId: string): Promise<FileStat[]>;
1489
- /** Add a participant. Requires `invite` or `admin`. */
1490
- addParticipant(spaceId: string, participant: Participant): Promise<void>;
1491
- /** Remove a participant by their public-key identity. Requires `admin`. */
1492
- removeParticipant(spaceId: string, participantCommitment: string): Promise<boolean>;
1493
- /** Set / replace an ACL entry. Requires `admin`. */
1494
- setACLEntry(spaceId: string, entry: ACLEntry): Promise<void>;
1495
- /** Request a challenge for `spaceId` and generate the matching proof. */
1496
- private proveFreshChallenge;
1497
- /**
1498
- * Hit `/challenge`. The body includes the caller's commitment so the
1499
- * server can scope the issued nonce to this principal.
1500
- */
1501
- private requestChallenge;
1500
+ private filesBase;
1502
1501
  private jsonRequest;
1503
1502
  }
1504
1503
 
@@ -1596,6 +1595,12 @@ interface WriteToShardsInput {
1596
1595
  * resumable uploads: the same id always maps to the same manifest slot.
1597
1596
  */
1598
1597
  fileId?: string;
1598
+ /**
1599
+ * Optional upload-progress callback, invoked after each chunk finishes
1600
+ * (`completed` of `total` chunks). Lets a UI show a progress bar. Called once
1601
+ * with `(0, total)` before the first chunk so a bar can render immediately.
1602
+ */
1603
+ onProgress?: (completed: number, total: number) => void;
1599
1604
  }
1600
1605
  declare class FileStorage {
1601
1606
  private readonly space;
@@ -2158,6 +2163,30 @@ declare class ZkAuth {
2158
2163
  * both the session and the identity are stored on the client.
2159
2164
  */
2160
2165
  login(username: string, password: string, opts?: LoginOptions): Promise<AuthUser>;
2166
+ /**
2167
+ * Silently re-authenticate the current user without prompting for a
2168
+ * password. Only possible while the client is **unlocked** — i.e. the
2169
+ * derived {@link ZkIdentity} (secret + keypairs) is still in memory from a
2170
+ * prior `login()`/`unlock()`. Re-runs the full challenge→proof→authenticate
2171
+ * dance with that in-memory identity and swaps in the fresh token.
2172
+ *
2173
+ * Returns `true` if a new session was minted, `false` if recovery isn't
2174
+ * possible (no active user, or locked — the identity was never derived or
2175
+ * was lost on reload). A `false` result is the app's cue to send the user
2176
+ * back to the login screen.
2177
+ *
2178
+ * Used automatically by the {@link Client} when a token-gated request comes
2179
+ * back `401` (stale/expired token), so transient session expiry self-heals
2180
+ * instead of surfacing errors mid-session.
2181
+ */
2182
+ recover(): Promise<boolean>;
2183
+ /**
2184
+ * Shared sign-in core: prove knowledge of `identity` against a fresh
2185
+ * challenge and persist the resulting session + identity. Used by both
2186
+ * `login()` (identity derived from a password) and `recover()` (identity
2187
+ * already in memory).
2188
+ */
2189
+ private proveAndStore;
2161
2190
  /**
2162
2191
  * Restore a persisted session on boot. Validates the stored token with the
2163
2192
  * accelerator; returns the user when valid, or `null` (and clears the
@@ -2231,6 +2260,18 @@ interface HttpClientOptions {
2231
2260
  getSessionToken?: () => string | null;
2232
2261
  /** Custom fetch — defaults to `globalThis.fetch`. */
2233
2262
  fetch?: typeof fetch;
2263
+ /**
2264
+ * Called when a token-gated request comes back `401` (stale/expired
2265
+ * session). Should attempt to silently re-establish a session and resolve
2266
+ * `true` if it succeeded (the request is then retried once with the fresh
2267
+ * token) or `false` if it couldn't (the `401` propagates as an
2268
+ * {@link HttpError}). Wired by {@link Client} to `auth.zk.recover()`.
2269
+ *
2270
+ * Only consulted by the JSON helpers (`get`/`post`/`del`); the raw
2271
+ * {@link fetch} passes 401s straight through so the auth wire calls that
2272
+ * power recovery itself can't recurse.
2273
+ */
2274
+ onUnauthorized?: () => Promise<boolean>;
2234
2275
  }
2235
2276
  /** Thrown for any non-2xx accelerator response. Carries the HTTP status. */
2236
2277
  declare class HttpError extends Error {
@@ -2242,6 +2283,7 @@ declare class HttpClient {
2242
2283
  readonly baseUrl: string;
2243
2284
  private readonly apiKey;
2244
2285
  private readonly getSessionToken;
2286
+ private readonly onUnauthorized;
2245
2287
  private readonly fetchFn;
2246
2288
  constructor(opts: HttpClientOptions);
2247
2289
  /**
@@ -2252,19 +2294,20 @@ declare class HttpClient {
2252
2294
  readonly fetch: typeof fetch;
2253
2295
  get<T = unknown>(path: string): Promise<T>;
2254
2296
  post<T = unknown>(path: string, body?: unknown): Promise<T>;
2297
+ patch<T = unknown>(path: string, body?: unknown): Promise<T>;
2255
2298
  del<T = unknown>(path: string, body?: unknown): Promise<T>;
2256
2299
  private request;
2257
2300
  }
2258
2301
 
2259
2302
  /**
2260
- * `client.storage` — per-user persistent storage backed by the accelerator's
2261
- * personal space.
2303
+ * `client.kv` — per-user key/value storage backed by the accelerator's personal
2304
+ * space.
2262
2305
  *
2263
- * await client.storage.set('todos', id, { title, completed })
2264
- * const todo = await client.storage.get('todos', id)
2265
- * const ids = await client.storage.list('todos')
2266
- * await client.storage.delete('todos', id)
2267
- * const off = client.storage.on('change', e => …) // realtime, cross-device
2306
+ * await client.kv.set('todos', id, { title, completed })
2307
+ * const todo = await client.kv.get('todos', id)
2308
+ * const ids = await client.kv.list('todos')
2309
+ * await client.kv.delete('todos', id)
2310
+ * const off = client.kv.on('change', e => …) // realtime, cross-device
2268
2311
  *
2269
2312
  * A *collection* is a key namespace within the user's space; the stored key is
2270
2313
  * `${collection}/${id}`. Operations are authorized by the user's session token
@@ -2272,13 +2315,15 @@ declare class HttpClient {
2272
2315
  *
2273
2316
  * Values are encrypted at rest by default ({@link StorageCipher}, an AES-GCM
2274
2317
  * key derived from the ZK identity); pass `{ encrypt: false }` to store
2275
- * plaintext. The realtime feed rides the personal space's own websocket, so no
2276
- * separate transport is involved.
2318
+ * plaintext. The realtime feed rides the personal space's websocket over a
2319
+ * {@link WSTransport} (heartbeat keep-alive + auto-reconnect, event-namespaced
2320
+ * so it doesn't cross wires with chat/space transports).
2277
2321
  *
2278
- * `query()` is intentionally deferred — see the SDK overhaul plan.
2322
+ * For **files** (blobs), use `client.storage` instead — `client.kv` is for
2323
+ * small structured values. `query()` is intentionally deferred.
2279
2324
  */
2280
2325
 
2281
- interface StorageNamespaceDeps {
2326
+ interface KvNamespaceDeps {
2282
2327
  http: HttpClient;
2283
2328
  session: SessionState;
2284
2329
  /** WebSocket base (ws/wss) for the change feed; derived from baseUrl. */
@@ -2295,11 +2340,11 @@ interface StorageChangeEvent<T = unknown> {
2295
2340
  /** The decrypted value for `set`; `null` for `delete`. */
2296
2341
  data: T | null;
2297
2342
  }
2298
- declare class StorageNamespace {
2343
+ declare class KvNamespace {
2299
2344
  private readonly deps;
2300
2345
  private cipher;
2301
2346
  private cipherIdentity;
2302
- constructor(deps: StorageNamespaceDeps);
2347
+ constructor(deps: KvNamespaceDeps);
2303
2348
  /** Store `value` under `collection`/`id` (encrypted at rest by default). */
2304
2349
  set<T = unknown>(collection: string, id: string, value: T, opts?: SetOptions): Promise<void>;
2305
2350
  /** Fetch the value at `collection`/`id`, or `null` if absent. */
@@ -2328,6 +2373,100 @@ declare class StorageNamespace {
2328
2373
  private getCipher;
2329
2374
  }
2330
2375
 
2376
+ /**
2377
+ * `client.storage` — file storage. Hides the `FileStorage` / `ShardClient` /
2378
+ * `SharedSpaceClient` primitives behind the unified client.
2379
+ *
2380
+ * const { stat, manifest } = await client.storage.writeFile({ spaceId, data, metadata })
2381
+ * const { data } = await client.storage.readFile(spaceId, fileId)
2382
+ * const files = await client.storage.listFiles() // my shared files
2383
+ * const files = await client.storage.listFiles({ spaceId }) // a space's files
2384
+ * await client.storage.deleteFile(spaceId, fileId)
2385
+ *
2386
+ * Model:
2387
+ * - **Files are written to a SharedSpace** (never the personal space). A file
2388
+ * is its manifest (per-chunk keys + global shard hashes); the manifest lives
2389
+ * in the space's gated, metered `/files` store, the shard ciphertext in the
2390
+ * global open `/api/shards` store.
2391
+ * - **The manifest is the capability** — anyone holding it reads the file from
2392
+ * the global shards, anywhere ({@link readByManifest}). Share a file by
2393
+ * sharing its manifest.
2394
+ * - **Personal discovery mirror** — on a successful write the SDK also records
2395
+ * the manifest in the user's `client.kv` under a reserved collection, so
2396
+ * {@link listFiles} (no `spaceId`) can show "files I've shared" without
2397
+ * enumerating spaces. The mirror is unmetered and is NOT a write path.
2398
+ *
2399
+ * For small structured values use `client.kv` instead.
2400
+ */
2401
+
2402
+ interface FileNamespaceDeps {
2403
+ http: HttpClient;
2404
+ baseUrl: string;
2405
+ /** Used for the unmetered personal discovery mirror. */
2406
+ kv: KvNamespace;
2407
+ /**
2408
+ * Optional pre-compiled Reed–Solomon WASM module. Defaults to the bundled
2409
+ * loader; pass one where the bundled loader path isn't available (vitest,
2410
+ * deploy-time-precompiled environments). Forwarded to {@link FileStorage}.
2411
+ */
2412
+ rsWasmModule?: WebAssembly.Module;
2413
+ }
2414
+ interface WriteFileOptions {
2415
+ /** SharedSpace the file is written to (obtain via `client.space`). */
2416
+ spaceId: string;
2417
+ data: Uint8Array | Blob | File;
2418
+ metadata: {
2419
+ name: string;
2420
+ type: string;
2421
+ path?: string;
2422
+ lastModified?: number;
2423
+ };
2424
+ }
2425
+ declare class StorageNamespace {
2426
+ private readonly deps;
2427
+ private fileStorage;
2428
+ constructor(deps: FileNamespaceDeps);
2429
+ /**
2430
+ * Write a file to a SharedSpace: shard ciphertext → the global store, the
2431
+ * manifest → the space's gated store, and a mirror copy → the caller's
2432
+ * personal index. Returns the `stat` and the `manifest` (share the manifest
2433
+ * to let others read the file).
2434
+ */
2435
+ writeFile(input: WriteFileOptions): Promise<{
2436
+ stat: FileStat;
2437
+ manifest: FileManifest;
2438
+ }>;
2439
+ /** Read a file from its space (resolves the manifest, then the shards). */
2440
+ readFile(spaceId: string, fileId: string): Promise<{
2441
+ data: Uint8Array;
2442
+ stat: FileStat;
2443
+ }>;
2444
+ /**
2445
+ * Read a file straight from a manifest — the capability path. No space
2446
+ * membership needed; this is how a shared file is opened.
2447
+ */
2448
+ readByManifest(manifest: FileManifest): Promise<{
2449
+ data: Uint8Array;
2450
+ stat: FileStat;
2451
+ }>;
2452
+ /**
2453
+ * List files. With no argument, returns the caller's own shared files from
2454
+ * the personal mirror; with `{ spaceId }`, lists that space's files.
2455
+ */
2456
+ listFiles(opts?: {
2457
+ spaceId?: string;
2458
+ }): Promise<FileStat[]>;
2459
+ /** Delete a file from its space and remove the personal mirror entry. */
2460
+ deleteFile(spaceId: string, fileId: string): Promise<boolean>;
2461
+ /** Read a manifest verbatim (e.g. to share a file). */
2462
+ getManifest(spaceId: string, fileId: string): Promise<FileManifest>;
2463
+ private fs;
2464
+ private _shards;
2465
+ private shards;
2466
+ private _space;
2467
+ private space;
2468
+ }
2469
+
2331
2470
  /**
2332
2471
  * Shared types for the Space fan-out group-encryption layer.
2333
2472
  *
@@ -2393,6 +2532,23 @@ interface RosterMember {
2393
2532
  /** Base64url-encoded JWK of the member's identity ECDSA public key. */
2394
2533
  identityEcdsaPub?: string;
2395
2534
  }
2535
+ /** A shareable invite link to a space. Redeeming it allowlists the holder so
2536
+ * the keeper admits them. */
2537
+ interface InviteLink {
2538
+ /** The capability token; embed in a URL to share. */
2539
+ token: string;
2540
+ /** Member id (username) that created the link. */
2541
+ createdBy?: string;
2542
+ createdAt?: number;
2543
+ /** Epoch ms when the link stops working; 0 = never. */
2544
+ expiresAt?: number;
2545
+ /** Max redemptions; 0 = unlimited. */
2546
+ maxUses?: number;
2547
+ /** Redemptions so far. */
2548
+ uses?: number;
2549
+ /** Access level granted to redeemers ("viewer" | "editor"). */
2550
+ role?: string;
2551
+ }
2396
2552
 
2397
2553
  /**
2398
2554
  * PacketCipher — the strategy `Network` uses to seal/open a packet's payload.
@@ -2631,8 +2787,35 @@ interface SpaceMessageEvent {
2631
2787
  channel: string;
2632
2788
  epoch: number;
2633
2789
  contentType?: string;
2790
+ /**
2791
+ * Server storage handle — the monotonic server timestamp this message is
2792
+ * persisted under (`msg:<handle>`). Stable across history replay; pass it to
2793
+ * {@link Space.editMessage} / {@link Space.deleteMessage} to mutate the
2794
+ * persisted entry. Carried on edit events too (same handle as the original).
2795
+ */
2796
+ handle: number;
2634
2797
  message: Message;
2635
2798
  }
2799
+ /**
2800
+ * An inbound ephemeral signal — an application broadcast the server relays to
2801
+ * the room but never persists (no history). Rides the generic `pub` relay:
2802
+ * `subject` is an app-defined routing key, `data` is opaque app JSON, and
2803
+ * `from` is the server-authenticated sender. Typing indicators, presence pings,
2804
+ * live cursors, etc. are built on top of this in the app layer — the SDK and
2805
+ * server stay domain-agnostic.
2806
+ */
2807
+ interface EphemeralEvent {
2808
+ /** Server-stamped authenticated sender. */
2809
+ from: string;
2810
+ /** Application-defined routing key (e.g. "typing"). */
2811
+ subject: string;
2812
+ /** Opaque application payload. */
2813
+ data: unknown;
2814
+ }
2815
+ /** A server-authoritative deletion of the persisted message at `handle`. */
2816
+ interface MessageDeletedEvent {
2817
+ handle: number;
2818
+ }
2636
2819
  interface SpaceDeps {
2637
2820
  /** Transport room id. For fan-out spaces this is the encoded space pubkey. */
2638
2821
  name: string;
@@ -2715,6 +2898,38 @@ declare class Space {
2715
2898
  }): Promise<void>;
2716
2899
  /** Subscribe to decrypted fan-out messages. Returns an unsubscribe fn. */
2717
2900
  onMessage(handler: (e: SpaceMessageEvent) => void): () => void;
2901
+ /**
2902
+ * Edit a persisted message IN PLACE, addressed by its server
2903
+ * {@link SpaceMessageEvent.handle}. Re-seals `payload` and asks the server
2904
+ * to replace the stored entry; the server authorizes against the original
2905
+ * author. Receivers get an {@link onMessageEdited} event carrying the new
2906
+ * content at the same handle. This is a generic persisted-item edit — the
2907
+ * server has no notion of what the payload means.
2908
+ */
2909
+ editMessage(handle: number, payload: unknown, opts?: {
2910
+ channel?: string;
2911
+ contentType?: string;
2912
+ }): Promise<void>;
2913
+ /**
2914
+ * Hard-delete a persisted message by its server {@link SpaceMessageEvent.handle}
2915
+ * — the ciphertext is removed from storage. Server authorizes against the
2916
+ * original author. Receivers get an {@link onMessageDeleted} event.
2917
+ */
2918
+ deleteMessage(handle: number): Promise<void>;
2919
+ /** Subscribe to in-place edits of persisted messages (new content at the same handle). */
2920
+ onMessageEdited(handler: (e: SpaceMessageEvent) => void): () => void;
2921
+ /** Subscribe to deletions of persisted messages. */
2922
+ onMessageDeleted(handler: (e: MessageDeletedEvent) => void): () => void;
2923
+ /**
2924
+ * Broadcast an ephemeral, never-persisted signal to the room over the
2925
+ * generic `pub` relay. `subject` is an app-defined routing key; `data` is
2926
+ * opaque. The transport stays domain-agnostic — app concepts like typing
2927
+ * indicators, presence, or live cursors are built on top of this. No-op if
2928
+ * the space isn't connected (ephemeral signals aren't worth queuing).
2929
+ */
2930
+ sendEphemeral(subject: string, data: unknown): void;
2931
+ /** Subscribe to inbound ephemeral signals. `from` is the authenticated sender. */
2932
+ onEphemeral(handler: (e: EphemeralEvent) => void): () => void;
2718
2933
  /** Subscribe to inbound join requests (for a key-holder to admit). */
2719
2934
  onJoinRequest(handler: (req: JoinRequest) => void): () => void;
2720
2935
  /**
@@ -2747,7 +2962,9 @@ declare class Space {
2747
2962
  memberId: string;
2748
2963
  identityEcdhPub: string;
2749
2964
  }>): Promise<number>;
2750
- putFile(file: File | Blob | Uint8Array, metadata: SpaceFileMetadata): Promise<{
2965
+ putFile(file: File | Blob | Uint8Array, metadata: SpaceFileMetadata, opts?: {
2966
+ onProgress?: (completed: number, total: number) => void;
2967
+ }): Promise<{
2751
2968
  manifest: FileManifest;
2752
2969
  stat: FileStat;
2753
2970
  }>;
@@ -2759,10 +2976,18 @@ declare class Space {
2759
2976
  /** Wire the RAW_FRAME handler that drives fan-out events. */
2760
2977
  private wireFanout;
2761
2978
  private onRawFrame;
2979
+ /**
2980
+ * Seal `payload` once with the current group key + sign it, returning the
2981
+ * serialized `Packet` string. Shared by send (a new message) and edit
2982
+ * (replacement content for an existing handle). Requires a keyring.
2983
+ */
2984
+ private sealMessage;
2762
2985
  /**
2763
2986
  * Decode one persisted/broadcast frame into a SpaceMessageEvent.
2764
2987
  * `isRawFrame` true means `input` is the already-extracted `spaceMessage`
2765
- * packet string; false means it's the full `{spaceMessage, name, ...}` frame.
2988
+ * packet string (and `serverTs` carries the message's storage handle from
2989
+ * the enclosing frame); false means `input` is the full
2990
+ * `{spaceMessage, name, timestamp}` frame and the handle is read from it.
2766
2991
  */
2767
2992
  private decodeFrame;
2768
2993
  /**
@@ -2945,6 +3170,40 @@ declare class SpaceNamespace {
2945
3170
  joinChannel(name: string, opts?: {
2946
3171
  timeoutMs?: number;
2947
3172
  }): Promise<Space>;
3173
+ /** Mint a shareable invite link for a space. `opts.expiresInSec` / `maxUses`
3174
+ * bound it (0/undefined = unlimited); `opts.role` ("viewer"|"editor") is the
3175
+ * access granted to redeemers (default viewer). Returns the link. */
3176
+ createInviteLink(spaceId: string, opts?: {
3177
+ expiresInSec?: number;
3178
+ maxUses?: number;
3179
+ role?: string;
3180
+ }): Promise<InviteLink>;
3181
+ /** Set a member's role on a space ("viewer" | "editor"). Owner-only. */
3182
+ setMemberRole(spaceId: string, username: string, role: string): Promise<void>;
3183
+ /** Roster with roles (owner/members only). */
3184
+ members(spaceId: string): Promise<Array<{
3185
+ memberId: string;
3186
+ role: string;
3187
+ }>>;
3188
+ /** List a space's active invite links (creator/members only). */
3189
+ listInviteLinks(spaceId: string): Promise<InviteLink[]>;
3190
+ /** Revoke an invite link by token. */
3191
+ revokeInviteLink(spaceId: string, token: string): Promise<void>;
3192
+ /**
3193
+ * Join a space using an invite link: redeem the token (the keeper admits
3194
+ * this member), then connect and resolve once the group key arrives.
3195
+ * Returns the open {@link Space}.
3196
+ */
3197
+ joinByInvite(spaceId: string, token: string, opts?: {
3198
+ timeoutMs?: number;
3199
+ }): Promise<Space>;
3200
+ /** The current member roster of a space (member id → identity public keys). */
3201
+ roster(spaceId: string): Promise<Array<{
3202
+ memberId: string;
3203
+ identityEcdhPub: string;
3204
+ identityEcdsaPub?: string;
3205
+ }>>;
3206
+ private keyringFor;
2948
3207
  private build;
2949
3208
  /** Ensure the member has an identity keypair; return its public ECDH + ECDSA keys. */
2950
3209
  private ensureIdentity;
@@ -2953,6 +3212,236 @@ declare class SpaceNamespace {
2953
3212
  private myId;
2954
3213
  }
2955
3214
 
3215
+ /**
3216
+ * `client.agents` — manage an app's **Programmable Agents** (server-side
3217
+ * "virtual users" backed by Workers AI).
3218
+ *
3219
+ * const { config } = await client.agents.create(appId, {
3220
+ * handle: '@assistant', displayName: 'Assistant',
3221
+ * systemPrompt: 'You are a helpful teammate in this Space.',
3222
+ * triggers: [{ type: 'mention' }],
3223
+ * });
3224
+ * await client.agents.enable(appId, config.agentId, spaceId); // per-Space opt-in
3225
+ *
3226
+ * These are MANAGEMENT calls — the agent itself runs on the accelerator. Only an
3227
+ * app's owner or an editor of its shared Space may create/update/enable an agent
3228
+ * (viewers can read). Authorization rides the user session token; non-owner
3229
+ * editors pass their management Space via `opts.space`.
3230
+ *
3231
+ * v1 is "prompts + persona": a system prompt, a model, named prompt `skills`,
3232
+ * and `triggers` that decide when the agent speaks. No tools, no code.
3233
+ */
3234
+
3235
+ type AgentTriggerType = "mention" | "keyword" | "regex" | "always";
3236
+ interface AgentTrigger {
3237
+ type: AgentTriggerType;
3238
+ /** Required for `keyword` (case-insensitive substring) and `regex`. */
3239
+ pattern?: string;
3240
+ /** Name of a {@link AgentSkill} to run on match. */
3241
+ skill?: string;
3242
+ }
3243
+ interface AgentSkill {
3244
+ name: string;
3245
+ prompt: string;
3246
+ }
3247
+ interface AgentConfig {
3248
+ agentId: string;
3249
+ /** @-mention target, e.g. "@assistant". */
3250
+ handle: string;
3251
+ displayName: string;
3252
+ model: string;
3253
+ systemPrompt: string;
3254
+ skills: AgentSkill[];
3255
+ triggers: AgentTrigger[];
3256
+ /** Space ids this agent is enabled on (per-Space opt-in). */
3257
+ enabledSpaces: string[];
3258
+ caps: {
3259
+ dailyTokenBudget: number;
3260
+ };
3261
+ createdAt: number;
3262
+ updatedAt: number;
3263
+ }
3264
+ /** The editor-settable fields when creating an agent. */
3265
+ interface AgentCreateInput {
3266
+ handle: string;
3267
+ displayName: string;
3268
+ systemPrompt: string;
3269
+ model?: string;
3270
+ skills?: AgentSkill[];
3271
+ triggers?: AgentTrigger[];
3272
+ caps?: {
3273
+ dailyTokenBudget: number;
3274
+ };
3275
+ }
3276
+ type AgentUpdateInput = Partial<AgentCreateInput>;
3277
+ /** Create/get response — the config plus the agent's published identity keys. */
3278
+ interface AgentProvisioned {
3279
+ config: AgentConfig;
3280
+ /** The agent's Space member id (`__agent__:<agentId>`). */
3281
+ memberId: string;
3282
+ /** base64url-JWK identity ECDH public key (null until provisioned). */
3283
+ ecdhPub: string | null;
3284
+ /** base64url-JWK identity ECDSA public key — verifies the agent's messages. */
3285
+ ecdsaPub: string | null;
3286
+ }
3287
+ interface AgentsNamespaceDeps {
3288
+ http: HttpClient;
3289
+ }
3290
+ /** Common option: a management Space for delegated (non-owner) access. */
3291
+ interface AgentScopeOpts {
3292
+ space?: string;
3293
+ }
3294
+ declare class AgentsNamespace {
3295
+ private readonly deps;
3296
+ constructor(deps: AgentsNamespaceDeps);
3297
+ /** List the app's agents. */
3298
+ list(appId: string, opts?: AgentScopeOpts): Promise<AgentConfig[]>;
3299
+ /** Read one agent (config + identity keys). */
3300
+ get(appId: string, agentId: string, opts?: AgentScopeOpts): Promise<AgentProvisioned>;
3301
+ /** Create an agent (owner/editor only). */
3302
+ create(appId: string, input: AgentCreateInput, opts?: AgentScopeOpts): Promise<AgentProvisioned>;
3303
+ /** Update an agent's editor-settable fields (owner/editor only). */
3304
+ update(appId: string, agentId: string, patch: AgentUpdateInput, opts?: AgentScopeOpts): Promise<{
3305
+ config: AgentConfig;
3306
+ }>;
3307
+ /** Delete an agent (owner/editor only). */
3308
+ delete(appId: string, agentId: string, opts?: AgentScopeOpts): Promise<{
3309
+ deleted: boolean;
3310
+ }>;
3311
+ /** Enable the agent on a Space (per-Space opt-in). */
3312
+ enable(appId: string, agentId: string, spaceId: string, opts?: AgentScopeOpts): Promise<{
3313
+ config: AgentConfig;
3314
+ }>;
3315
+ /** Disable the agent on a Space. */
3316
+ disable(appId: string, agentId: string, spaceId: string, opts?: AgentScopeOpts): Promise<{
3317
+ config: AgentConfig;
3318
+ }>;
3319
+ private path;
3320
+ }
3321
+
3322
+ /**
3323
+ * `client.functions` — manage an app's **serverless functions** (developer-
3324
+ * authored code that runs on the accelerator).
3325
+ *
3326
+ * const { config } = await client.functions.deploy(appId, {
3327
+ * name: 'hello', displayName: 'Hello',
3328
+ * code: `export default { fetch: () => new Response('hi') }`,
3329
+ * triggers: { http: { enabled: true } },
3330
+ * });
3331
+ * // HTTP-reachable at hello--<slug>.fns.muhkoo.dev
3332
+ * await client.functions.enable(appId, config.functionId, spaceId); // Space-bound
3333
+ *
3334
+ * These are MANAGEMENT calls — the function itself runs on the accelerator
3335
+ * (uploaded just-in-time, decrypted only at invocation). Only an app's owner or
3336
+ * an editor of its shared Space may deploy/update/enable (viewers read).
3337
+ * Authorization rides the user session token; non-owner editors pass their
3338
+ * management Space via `opts.space`.
3339
+ *
3340
+ * v1 is "compute-only": a function is an untrusted single-module ES worker
3341
+ * (`export default { fetch }`) with NO platform bindings — it reaches the
3342
+ * platform as an external client (the end-user session is passed through). Two
3343
+ * triggers: `http` (its own subdomain) and `space` (invoked on Space messages,
3344
+ * like a Programmable Agent).
3345
+ */
3346
+
3347
+ type FunctionTriggerType = "keyword" | "regex" | "always";
3348
+ interface FunctionTrigger {
3349
+ type: FunctionTriggerType;
3350
+ /** Required for `keyword` (case-insensitive substring) and `regex`. */
3351
+ pattern?: string;
3352
+ }
3353
+ interface FunctionTriggers {
3354
+ /** HTTP-invocable at `<name>--<slug>.fns.<zone>`. */
3355
+ http?: {
3356
+ enabled: boolean;
3357
+ methods?: string[];
3358
+ };
3359
+ /** Invoked on matching Space messages (per-Space opt-in via enable). */
3360
+ space?: {
3361
+ match: FunctionTrigger[];
3362
+ };
3363
+ }
3364
+ interface FunctionCaps {
3365
+ /** Per-invocation CPU ceiling (ms). */
3366
+ cpuMs: number;
3367
+ /** Per-invocation subrequest ceiling. */
3368
+ subRequests: number;
3369
+ /** Hard daily invocation cap (0 = unlimited). */
3370
+ dailyInvocationBudget: number;
3371
+ }
3372
+ interface FunctionConfig {
3373
+ functionId: string;
3374
+ /** DNS-safe slug — the function's subdomain label + WfP script name part. */
3375
+ name: string;
3376
+ displayName: string;
3377
+ triggers: FunctionTriggers;
3378
+ /** Space ids this function is enabled on (per-Space opt-in for `space`). */
3379
+ enabledSpaces: string[];
3380
+ caps: FunctionCaps;
3381
+ scriptName: string;
3382
+ /** sha-256 of the last-deployed source. */
3383
+ codeHash?: string;
3384
+ /** When the source was last (re)deployed. */
3385
+ deployedAt?: number;
3386
+ createdAt: number;
3387
+ updatedAt: number;
3388
+ }
3389
+ /** The editor-settable fields when deploying a function. */
3390
+ interface FunctionDeployInput {
3391
+ name: string;
3392
+ displayName: string;
3393
+ /** Raw single-module ES worker source (`export default { fetch }`). */
3394
+ code: string;
3395
+ triggers?: FunctionTriggers;
3396
+ caps?: Partial<FunctionCaps>;
3397
+ }
3398
+ /** Update input — any subset, including new `code` to redeploy. */
3399
+ type FunctionUpdateInput = Partial<FunctionDeployInput>;
3400
+ interface FunctionsNamespaceDeps {
3401
+ http: HttpClient;
3402
+ }
3403
+ /** Common option: a management Space for delegated (non-owner) access. */
3404
+ interface FunctionScopeOpts {
3405
+ space?: string;
3406
+ }
3407
+ declare class FunctionsNamespace {
3408
+ private readonly deps;
3409
+ constructor(deps: FunctionsNamespaceDeps);
3410
+ /** List the app's functions. */
3411
+ list(appId: string, opts?: FunctionScopeOpts): Promise<FunctionConfig[]>;
3412
+ /** Read one function's config. */
3413
+ get(appId: string, functionId: string, opts?: FunctionScopeOpts): Promise<{
3414
+ config: FunctionConfig;
3415
+ }>;
3416
+ /** Read a function's decrypted source (owner/editor; for the editor UI). */
3417
+ code(appId: string, functionId: string, opts?: FunctionScopeOpts): Promise<{
3418
+ functionId: string;
3419
+ code: string;
3420
+ codeHash: string | null;
3421
+ }>;
3422
+ /** Deploy (create) a function (owner/editor only). */
3423
+ deploy(appId: string, input: FunctionDeployInput, opts?: FunctionScopeOpts): Promise<{
3424
+ config: FunctionConfig;
3425
+ }>;
3426
+ /** Update a function's editor-settable fields and/or redeploy its `code`. */
3427
+ update(appId: string, functionId: string, patch: FunctionUpdateInput, opts?: FunctionScopeOpts): Promise<{
3428
+ config: FunctionConfig;
3429
+ }>;
3430
+ /** Delete a function (owner/editor only). */
3431
+ delete(appId: string, functionId: string, opts?: FunctionScopeOpts): Promise<{
3432
+ deleted: boolean;
3433
+ }>;
3434
+ /** Enable the function on a Space (per-Space opt-in for the `space` trigger). */
3435
+ enable(appId: string, functionId: string, spaceId: string, opts?: FunctionScopeOpts): Promise<{
3436
+ config: FunctionConfig;
3437
+ }>;
3438
+ /** Disable the function on a Space. */
3439
+ disable(appId: string, functionId: string, spaceId: string, opts?: FunctionScopeOpts): Promise<{
3440
+ config: FunctionConfig;
3441
+ }>;
3442
+ private path;
3443
+ }
3444
+
2956
3445
  /** The hosted Muhkoo Accelerator — the default {@link ClientOptions.baseUrl}. */
2957
3446
  declare const DEFAULT_BASE_URL = "https://api.muhkoo.dev";
2958
3447
  interface ClientOptions {
@@ -2990,19 +3479,48 @@ declare class Client {
2990
3479
  readonly baseUrl: string;
2991
3480
  /** Authentication — `client.auth.zk.login(...)`, etc. */
2992
3481
  readonly auth: AuthNamespace;
2993
- /** Per-user persistent storage — `client.storage.set(...)`, etc. */
3482
+ /** Per-user key/value storage — `client.kv.set(...)`, etc. */
3483
+ readonly kv: KvNamespace;
3484
+ /** File storage — `client.storage.writeFile(...)`, etc. */
2994
3485
  readonly storage: StorageNamespace;
2995
3486
  /** Realtime messaging — `client.message.subscribe(...)`, etc. */
2996
3487
  readonly message: MessageNamespace;
2997
3488
  /** Fan-out group spaces — `client.space.createSpace(...)`, etc. */
2998
3489
  readonly space: SpaceNamespace;
3490
+ /** Programmable Agents — `client.agents.create(appId, …)`, etc. */
3491
+ readonly agents: AgentsNamespace;
3492
+ /** Serverless functions — `client.functions.deploy(appId, …)`, etc. */
3493
+ readonly functions: FunctionsNamespace;
2999
3494
  private readonly session;
3000
3495
  private readonly http;
3496
+ /** Subscribers notified when a session expires and can't be recovered. */
3497
+ private readonly sessionExpiredHandlers;
3498
+ /** Shared in-flight recovery so a burst of 401s triggers one re-auth. */
3499
+ private recoverInFlight;
3001
3500
  constructor(options?: ClientOptions);
3002
3501
  /** The currently signed-in user, or `null`. */
3003
3502
  get user(): AuthUser | null;
3004
3503
  /** Whether a token-bearing session is active. */
3005
3504
  get isAuthenticated(): boolean;
3505
+ /**
3506
+ * Subscribe to session expiry that couldn't be silently recovered (the
3507
+ * user's identity wasn't in memory to re-prove — typically after a reload
3508
+ * where only the token was persisted, and that token has since gone stale).
3509
+ * This is the signal to send the user back to the login screen.
3510
+ *
3511
+ * Returns an unsubscribe function.
3512
+ *
3513
+ * const off = client.onSessionExpired(() => router.push('/login'));
3514
+ */
3515
+ onSessionExpired(handler: () => void): () => void;
3516
+ /**
3517
+ * Attempt a silent re-auth of the current user. Concurrent callers share a
3518
+ * single in-flight attempt (a stale token usually 401s several requests at
3519
+ * once — we re-auth once, not once per request). On failure the
3520
+ * session-expired event fires so the app can react. Exposed so apps can
3521
+ * also drive recovery proactively (e.g. on `visibilitychange`).
3522
+ */
3523
+ recoverSession(): Promise<boolean>;
3006
3524
  }
3007
3525
 
3008
3526
  /**
@@ -3040,8 +3558,30 @@ declare class KeyringClient implements KeyringTransport {
3040
3558
  epoch: number;
3041
3559
  }>;
3042
3560
  invite(username: string): Promise<void>;
3561
+ createInviteLink(opts?: {
3562
+ expiresInSec?: number;
3563
+ maxUses?: number;
3564
+ role?: string;
3565
+ }): Promise<InviteLink>;
3566
+ listInviteLinks(): Promise<InviteLink[]>;
3567
+ /** Set a member's role ("viewer" | "editor"). Owner-only on the server. */
3568
+ setMemberRole(username: string, role: string): Promise<void>;
3569
+ /** Roster with roles (owner/members only). */
3570
+ members(): Promise<Array<{
3571
+ memberId: string;
3572
+ role: string;
3573
+ }>>;
3574
+ revokeInviteLink(token: string): Promise<void>;
3575
+ /** Redeem a link: the server allowlists this member and asks the keeper to
3576
+ * wrap the group key for their identity. Caller must be authenticated. */
3577
+ redeemInvite(req: {
3578
+ token: string;
3579
+ identityEcdhPub: string;
3580
+ identityEcdsaPub?: string;
3581
+ desiredEpoch?: number;
3582
+ }): Promise<void>;
3043
3583
  fetchMetadata(): Promise<SpaceMetadata | null>;
3044
3584
  }
3045
3585
 
3046
- export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AuthClient, AuthNamespace, AuthPublicInput, Authenticator, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, Client, DEFAULT_BASE_URL, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, HashKnowledge, HttpClient, HttpError, KEEPER_MEMBER_ID, KeyStore, KeyringClient, LocalStorageSessionStore, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PersonalSpaceClient, Poseidon, PreimagePoK, ReedSolomonCodec, Space as Room, SerializeMessage, SessionState, ShardClient, SharedSpaceClient, Space, SpaceKeyring, SpaceNamespace, SpacePacketCipher, StorageNamespace, WSTransport, ZkAuth, buildCommitment, compilePrograms, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultSessionStore, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, encodeToHex, encryptAesGcm, exportPublicKeyBase64, exportPublicKeyHex, generateAuthProof, getSubtle, initBn128Wasm, initRsWasm, initializeCircuits, manifestToStat, poseidonHash, quickVerify, randomBytes, shardHash, signMessage, toField, unwrapWithPassphrase, verify, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase };
3047
- export type { ACL, ACLEntry, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, ChannelLike, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitUrls, ClientOptions, DehydratedKeypair, DehydratedKeys, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EpochKeyProvider, FileManifest, FileStat, FileStorageOptions, Groth16Proof, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, LoginOptions, MessageBody, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOptions, MessageSubscription, MuhkooMessageEvent, OAuthProvider, PacketCipher, PacketHeaders, PacketOptions, Participant, Permission, PermissionCheckRequest, PermissionCheckResult, PersonalSpaceClientOptions, PublicKeyJWK, RatchetState, ReceiveResult, RegisterParams, Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, SealedHeaders, Session, SessionOptions, SessionStore, SetOptions, ShardClientOptions, SharedSpaceClientOptions, SnarkProof, SpaceChannelLike, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, StorageChangeEvent, StorageNamespaceDeps, StoredSession, Subscription, UserIdentity, VerificationKey, WSMessage, WSMessageType, WSTransportOptions, WrappedPayload, WriteFileInput, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };
3586
+ export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AgentsNamespace, AuthClient, AuthNamespace, AuthPublicInput, Authenticator, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, Client, DEFAULT_BASE_URL, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, FunctionsNamespace, HashKnowledge, HttpClient, HttpError, KEEPER_MEMBER_ID, KeyStore, KeyringClient, KvNamespace, LocalStorageSessionStore, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PersonalSpaceClient, Poseidon, PreimagePoK, ReedSolomonCodec, Space as Room, SerializeMessage, SessionState, ShardClient, SharedSpaceClient, Space, SpaceKeyring, SpaceNamespace, SpacePacketCipher, StorageNamespace, WSTransport, ZkAuth, buildCommitment, compilePrograms, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultSessionStore, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, encodeToHex, encryptAesGcm, exportPublicKeyBase64, exportPublicKeyHex, generateAuthProof, getSubtle, initBn128Wasm, initRsWasm, initializeCircuits, manifestToStat, poseidonHash, quickVerify, randomBytes, shardHash, signMessage, toField, unwrapWithPassphrase, verify, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase };
3587
+ export type { ACL, ACLEntry, AgentConfig, AgentCreateInput, AgentProvisioned, AgentScopeOpts, AgentSkill, AgentTrigger, AgentTriggerType, AgentUpdateInput, AgentsNamespaceDeps, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, ChannelLike, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitUrls, ClientOptions, DehydratedKeypair, DehydratedKeys, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EphemeralEvent, EpochKeyProvider, FileManifest, FileNamespaceDeps, FileStat, FileStorageOptions, FunctionCaps, FunctionConfig, FunctionDeployInput, FunctionScopeOpts, FunctionTrigger, FunctionTriggerType, FunctionTriggers, FunctionUpdateInput, FunctionsNamespaceDeps, Groth16Proof, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, KvNamespaceDeps, LoginOptions, MessageBody, MessageDeletedEvent, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOptions, MessageSubscription, MuhkooMessageEvent, OAuthProvider, PacketCipher, PacketHeaders, PacketOptions, Participant, Permission, PermissionCheckRequest, PermissionCheckResult, PersonalSpaceClientOptions, PublicKeyJWK, RatchetState, ReceiveResult, RegisterParams, Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, SealedHeaders, Session, SessionOptions, SessionStore, SetOptions, ShardClientOptions, SharedSpaceClientOptions, SnarkProof, SpaceChannelLike, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, StorageChangeEvent, StoredSession, Subscription, UserIdentity, VerificationKey, WSMessage, WSMessageType, WSTransportOptions, WrappedPayload, WriteFileInput, WriteFileOptions, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };