@fleetless/sdk 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -18,9 +18,9 @@ import { z } from 'zod';
18
18
  * admits it lost track.
19
19
  */
20
20
  declare const jobState: z.ZodEnum<{
21
+ failed: "failed";
21
22
  running: "running";
22
23
  succeeded: "succeeded";
23
- failed: "failed";
24
24
  cancelled: "cancelled";
25
25
  lost: "lost";
26
26
  }>;
@@ -30,9 +30,9 @@ declare const job: z.ZodObject<{
30
30
  robot_id: z.ZodUUID;
31
31
  slug: z.ZodString;
32
32
  state: z.ZodEnum<{
33
+ failed: "failed";
33
34
  running: "running";
34
35
  succeeded: "succeeded";
35
- failed: "failed";
36
36
  cancelled: "cancelled";
37
37
  lost: "lost";
38
38
  }>;
@@ -65,9 +65,9 @@ declare const jobEvent: z.ZodObject<{
65
65
  robot_id: z.ZodUUID;
66
66
  slug: z.ZodString;
67
67
  state: z.ZodEnum<{
68
+ failed: "failed";
68
69
  running: "running";
69
70
  succeeded: "succeeded";
70
- failed: "failed";
71
71
  cancelled: "cancelled";
72
72
  lost: "lost";
73
73
  }>;
@@ -97,9 +97,9 @@ declare const busyDetails: z.ZodObject<{
97
97
  robot_id: z.ZodUUID;
98
98
  slug: z.ZodString;
99
99
  state: z.ZodEnum<{
100
+ failed: "failed";
100
101
  running: "running";
101
102
  succeeded: "succeeded";
102
- failed: "failed";
103
103
  cancelled: "cancelled";
104
104
  lost: "lost";
105
105
  }>;
@@ -256,6 +256,9 @@ type DatapointEvent = z.infer<typeof datapointEvent>;
256
256
  * Access plus refresh (spec §3.4). The access token is short-lived; the
257
257
  * refresh token rotates on every use, so a stolen one is detectable when the
258
258
  * original is presented again.
259
+ *
260
+ * One shape for both identity spaces: a session is a session, and the claims
261
+ * inside the token are what differ.
259
262
  */
260
263
  declare const sessionTokens: z.ZodObject<{
261
264
  access_token: z.ZodString;
@@ -265,105 +268,149 @@ declare const sessionTokens: z.ZodObject<{
265
268
  type SessionTokens = z.infer<typeof sessionTokens>;
266
269
 
267
270
  /**
268
- * **What logout can and cannot end, said in three separable facts (W9c,
269
- * DEF-098).**
271
+ * **Why a federated sign-in ended without a session, in a code the app can
272
+ * branch on** — carried back to the app's own `redirect_uri` as `error`, not
273
+ * rendered by Fleetless (D2). The only Fleetless-rendered page in this flow is
274
+ * the one for a state that can no longer be resolved to a redirect URI, because
275
+ * then there is nowhere to send the answer.
276
+ *
277
+ * The five rows of D4's table are the first five values plus `no_access`:
270
278
  *
271
- * Until now this route answered `204`: the Fleetless session was over and the
272
- * response had nothing to say about the *other* session. For a federated user
273
- * that is the larger half they clicked "log out", the IdP's cookie survived,
274
- * and the next login goes straight through without a password. The register
275
- * row calls that an expectation gap, and it is: the word on the button is
276
- * "log out", not "log out of this app".
279
+ * - `no_access` the identity is unknown and nothing admits it, or the account
280
+ * it names is not `active`. **One code for both**, because to the person the
281
+ * remedy is the sameask somebody to let you in and a code that split an
282
+ * outcome nobody acts on differently would tell a stranger which half applied.
283
+ * - `email_taken` the address already belongs to another app user, and the
284
+ * provider is not permitted to link (`link_verified_emails`, or the provider
285
+ * did not assert `email_verified`). Deliberately not `no_access`: the remedy
286
+ * is different — *sign in the way you signed up*.
287
+ * - `email_unverified` — the provider asserted an address without
288
+ * `email_verified`. **An unverified address never produces or links an
289
+ * account**, whatever the rest of the policy says.
290
+ * - `domain_not_allowed`, `registration_closed` — the self-registration policy
291
+ * refused. Honest, because neither is about whether a person exists.
292
+ * - `idp_unavailable`, `exchange_failed`, `claims_incomplete`,
293
+ * `provider_misconfigured`, `provider_disabled` — the provider's or the
294
+ * developer's to fix, and the app can say so.
295
+ * - `invalid_request` — the start parameters did not hold up.
296
+ * - `quota_exceeded` — the org has as many app users as its `max_end_users`
297
+ * quota allows, so no account can be created for this identity. Named rather
298
+ * than folded into `no_access`, for `domain_not_allowed`'s reason: it is not
299
+ * about the person, the app can say what happened, and the remedy belongs to
300
+ * the developer rather than to whoever is trying to sign in. It is raised
301
+ * **only where an account would be created** — an identity that already has
302
+ * one signs in at the quota exactly as it does under it, because refusing a
303
+ * sign-in would turn a protection limit into an outage.
304
+ */
305
+ declare const clientOidcErrorCode: z.ZodEnum<{
306
+ no_access: "no_access";
307
+ email_taken: "email_taken";
308
+ email_unverified: "email_unverified";
309
+ domain_not_allowed: "domain_not_allowed";
310
+ registration_closed: "registration_closed";
311
+ idp_unavailable: "idp_unavailable";
312
+ exchange_failed: "exchange_failed";
313
+ claims_incomplete: "claims_incomplete";
314
+ provider_misconfigured: "provider_misconfigured";
315
+ provider_disabled: "provider_disabled";
316
+ invalid_request: "invalid_request";
317
+ quota_exceeded: "quota_exceeded";
318
+ }>;
319
+ type ClientOidcErrorCode = z.infer<typeof clientOidcErrorCode>;
320
+ /**
321
+ * **A pending MCP authorization, as the app's own consent screen reads it**
322
+ * (D7). Fleetless renders no page here either: `authorize` redirects to the
323
+ * app's `mcp_login_url` with an interaction id, the app authenticates the user
324
+ * with its normal UI, shows this, and approves or denies through the API.
277
325
  *
278
- * **The Fleetless session is ended before this is computed, unconditionally.**
279
- * Nothing below can fail in a way that leaves the caller logged in here a
280
- * logout that depends on reaching a third party is not a logout.
326
+ * `client_name_verified` is `z.literal(false)`, and that is the whole point of
327
+ * the field. The name comes from an **unauthenticated** dynamic registration
328
+ * the client typed it about itself, nobody checked it so a consent screen
329
+ * that rendered it as though it were an identity would be teaching people to
330
+ * trust a string an attacker chooses. A literal rather than a boolean because
331
+ * there is no verified case to distinguish: an app that reads this field at all
332
+ * has to handle the untrusted one, and a `true` branch would be dead code
333
+ * pretending to be a safeguard.
334
+ */
335
+ declare const clientMcpInteraction: z.ZodObject<{
336
+ id: z.ZodString;
337
+ app_id: z.ZodUUID;
338
+ client_name: z.ZodNullable<z.ZodString>;
339
+ client_name_verified: z.ZodLiteral<false>;
340
+ scopes: z.ZodArray<z.ZodString>;
341
+ already_granted: z.ZodBoolean;
342
+ expires_at: z.ZodISODateTime;
343
+ }, z.core.$strip>;
344
+ type ClientMcpInteraction = z.infer<typeof clientMcpInteraction>;
345
+ /**
346
+ * **One standing MCP consent, as both withdrawal doors list it.**
281
347
  *
282
- * **Four outcomes**, and they are deliberately not collapsed into a nullable
283
- * URL. *No IdP was involved* and *an IdP was involved and publishes no
284
- * `end_session_endpoint`* are different things: the first needs no action and
285
- * the second means a session survives that this platform cannot end. A caller
286
- * that renders them identically is choosing to; a contract that cannot tell
287
- * them apart makes the choice for everyone.
348
+ * A grant is what lets a later authorization skip the app's consent screen:
349
+ * `clientMcpInteraction.already_granted` is a read of exactly this row. It is
350
+ * written when a person approves and it is removed by neither the client's
351
+ * registration lapsing nor its access token expiring so without a door it
352
+ * was a decision a person could make once and never unmake.
288
353
  *
289
- * **This sentence said "three" for a whole wave, directly above a four-branch
290
- * union in this same file** found by Momus-W9, along with the same number in
291
- * `sdk/src/auth.ts` and `sdk/README.md`. The type is derived
292
- * (`ClientLogoutResponse['idp_logout']`), so `tsc` had nothing to say, and the
293
- * sweep shows the mechanism plainly: `sdk d065447` is literally titled *"logout
294
- * says three separable things"* — correct when written, never carried forward
295
- * when `hint_unavailable` arrived in `b417d2a`.
354
+ * **Standing only.** A withdrawn grant is stamped rather than deleted, so the
355
+ * store still holds it; neither listing returns one. The question both doors
356
+ * ask is *what is connected right now*, and a row that answered "connected,
357
+ * but no" would be a state every caller has to filter for itself.
296
358
  *
297
- * The count is not the point. **`hint_unavailable` is precisely the case this
298
- * comment warns about** the IdP session survives — so a caller who handles
299
- * the three documented branches drops it into an `else` they believe means
300
- * *nothing to do*.
359
+ * `client_name_verified` is `z.literal(false)` for the reason
360
+ * `clientMcpInteraction` gives at length: the name comes from an
361
+ * unauthenticated dynamic registration, the client chose it about itself, and
362
+ * a list that rendered it as an identity would be teaching people to trust a
363
+ * string an attacker picked. Here it matters more than on the consent screen,
364
+ * not less — a "connected apps" list is read long after the moment of
365
+ * approval, when nobody remembers what they clicked.
301
366
  */
302
- declare const clientLogoutResponse: z.ZodObject<{
303
- idp_logout: z.ZodDiscriminatedUnion<[z.ZodObject<{
304
- status: z.ZodLiteral<"redirect">;
305
- url: z.ZodURL;
306
- }, z.core.$strip>, z.ZodObject<{
307
- status: z.ZodLiteral<"not_federated">;
308
- }, z.core.$strip>, z.ZodObject<{
309
- status: z.ZodLiteral<"unsupported_by_idp">;
310
- }, z.core.$strip>, z.ZodObject<{
311
- status: z.ZodLiteral<"hint_unavailable">;
312
- }, z.core.$strip>, z.ZodObject<{
313
- status: z.ZodLiteral<"session_unknown">;
314
- }, z.core.$strip>], "status">;
367
+ declare const mcpConsentGrant: z.ZodObject<{
368
+ client_id: z.ZodString;
369
+ client_name: z.ZodNullable<z.ZodString>;
370
+ client_name_verified: z.ZodLiteral<false>;
371
+ granted_at: z.ZodISODateTime;
315
372
  }, z.core.$strip>;
316
- type ClientLogoutResponse = z.infer<typeof clientLogoutResponse>;
373
+ type McpConsentGrant = z.infer<typeof mcpConsentGrant>;
317
374
  /**
318
375
  * Who the caller turned out to be. Returned by the "who am I" endpoint and by
319
376
  * the realtime `auth_ok` frame, so a client can render a session without
320
377
  * decoding a token itself — decoding a JWT in the client is how apps end up
321
378
  * trusting claims nobody verified.
322
379
  *
323
- * **Three kinds of caller reach the client API, not two.** Besides end users
324
- * and server keys, a **developer** does: spec §15.2 says the console's
325
- * playground runs over the real client API and appears in the audit as the
326
- * developer, and the console's own live views (robot list badges, the Live
327
- * tab) subscribe on `/realtime` as one. A developer is **org-scoped, not
380
+ * **Three kinds of caller reach the client API.** Besides app users and server
381
+ * keys, a **developer** does: the console's playground runs over the real
382
+ * client API and appears in the audit as the developer, and the console's own
383
+ * live views subscribe on `/realtime` as one. A developer is **org-scoped, not
328
384
  * app-scoped** — they own the configuration of every robot in their org — so
329
385
  * `app_id` and `role_id` are null for them, and roles do not filter what they
330
- * see. `kind` states this explicitly rather than leaving it to be inferred
331
- * from which id happens to be set.
386
+ * see. `kind` states this explicitly rather than leaving it to be inferred from
387
+ * which id happens to be set.
332
388
  *
333
- * **`act` is the real admin behind an impersonation** (spec
334
- * `2026-08-29-org-identity-redesign`, D4, the `act`-claim pattern of RFC
335
- * 8693). An Org Admins member entering an app through the interstitial
336
- * (`impersonationChoice`) gets a token whose *effective* identity is the role
337
- * or user they chose that is what the rest of this shape describes — while
338
- * `act` names **the admin who is actually driving**. So every action can
339
- * audit as "Admin A as User B / as role X", and a client can render the "you
340
- * are acting as …" banner without decoding the token.
389
+ * **`end_user_id` became `app_user_id`, and that is a rename with a meaning.**
390
+ * The old subject was a member of the org's one pool, reachable through an
391
+ * assignment; the new one is a row that belongs to exactly one app. Renaming
392
+ * rather than keeping the key is deliberate: a consumer reading `.end_user_id`
393
+ * would have typechecked and meant something subtly different, which is the
394
+ * quietest way for a cut like this to go wrong.
341
395
  *
342
- * **Optional, not a nullable actor, for `orgUser.tier`'s reason:** absence
343
- * means *this is an ordinary session, nobody is delegating*, which is not the
344
- * same fact as *the actor is unknown*. The overwhelming majority of sessions
345
- * are ordinary and carry no `act` at all; a session that has one is a
346
- * delegation and says who by. The schema cannot check that `act` is present
347
- * exactly when the effective identity was impersonated — that pairing is the
348
- * cloud's, minted at the authorize step. Only the admin's **id** rides here:
349
- * the label is resolved by whoever renders it, not carried as a second
350
- * unverified name on the wire.
396
+ * **`act` is gone.** It named the org admin behind an impersonation (the RFC
397
+ * 8693 pattern). Impersonation is deleted with no successor (D1), so a field
398
+ * that could still arrive would describe a delegation nothing can mint — and a
399
+ * client rendering "you are acting as …" from it would be showing a state the
400
+ * platform cannot enter.
351
401
  */
352
402
  declare const clientIdentity: z.ZodObject<{
353
403
  kind: z.ZodEnum<{
354
- server_key: "server_key";
355
404
  developer: "developer";
356
- end_user: "end_user";
405
+ server_key: "server_key";
406
+ app_user: "app_user";
357
407
  }>;
358
408
  developer_id: z.ZodNullable<z.ZodUUID>;
359
- end_user_id: z.ZodNullable<z.ZodUUID>;
409
+ app_user_id: z.ZodNullable<z.ZodUUID>;
360
410
  server_key_id: z.ZodNullable<z.ZodUUID>;
361
411
  app_id: z.ZodNullable<z.ZodUUID>;
362
412
  role_id: z.ZodNullable<z.ZodUUID>;
363
413
  email: z.ZodNullable<z.ZodEmail>;
364
- act: z.ZodOptional<z.ZodObject<{
365
- admin_user_id: z.ZodUUID;
366
- }, z.core.$strict>>;
367
414
  }, z.core.$strip>;
368
415
  type ClientIdentity = z.infer<typeof clientIdentity>;
369
416
 
@@ -423,9 +470,9 @@ declare const assetSyncStatus: z.ZodObject<{
423
470
  sync_id: z.ZodUUID;
424
471
  robot_id: z.ZodUUID;
425
472
  state: z.ZodEnum<{
473
+ failed: "failed";
426
474
  running: "running";
427
475
  succeeded: "succeeded";
428
- failed: "failed";
429
476
  }>;
430
477
  done: z.ZodNumber;
431
478
  total: z.ZodNumber;
@@ -467,9 +514,9 @@ declare const assetListResponse: z.ZodObject<{
467
514
  sync_id: z.ZodUUID;
468
515
  robot_id: z.ZodUUID;
469
516
  state: z.ZodEnum<{
517
+ failed: "failed";
470
518
  running: "running";
471
519
  succeeded: "succeeded";
472
- failed: "failed";
473
520
  }>;
474
521
  done: z.ZodNumber;
475
522
  total: z.ZodNumber;
@@ -543,79 +590,9 @@ type ParameterInvalidDetails = z.infer<typeof parameterInvalidDetails>;
543
590
  * list is the shared vocabulary, not a closed set, so a new refusal never
544
591
  * needs a contracts release before it can be reported honestly.
545
592
  */
546
- declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "invite_expired", "invite_used", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "job_lost", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "asset_too_large", "dynamic_registration_disabled", "client_limit_reached", "identity_conflict", "identity_not_provisioned", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "group_not_deletable", "group_in_use", "target_state_conflict", "mcp_access_denied", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml"];
593
+ declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "job_lost", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "asset_too_large", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired"];
547
594
  type ErrorCode = (typeof ERROR_CODES)[number];
548
595
 
549
- /**
550
- * **What an end user sees about their own consents, and why it is not
551
- * `consentGrant` (W9c, DEF-099).**
552
- *
553
- * `consentGrant` is the *record* — four ids and a scope string. A person
554
- * deciding whether to revoke something needs to recognise it, and an id is
555
- * not recognisable. So this carries the names that were on the screen when
556
- * they consented: the client's, the app's, and the role's.
557
- *
558
- * `client_id` stays, because it is what a revocation addresses — the names
559
- * are for reading, the id is for acting.
560
- */
561
- declare const consentGrantSummary: z.ZodObject<{
562
- client_id: z.ZodString;
563
- client_name: z.ZodString;
564
- app_id: z.ZodUUID;
565
- app_name: z.ZodString;
566
- role_id: z.ZodUUID;
567
- role_name: z.ZodString;
568
- scope: z.ZodString;
569
- granted_at: z.ZodISODateTime;
570
- }, z.core.$strip>;
571
- type ConsentGrantSummary = z.infer<typeof consentGrantSummary>;
572
- /**
573
- * **Revoking one grant must end the access it authorised, not merely forget
574
- * that it happened (W9c, DEF-099).**
575
- *
576
- * The register row is about a user who wants a specific client to stop, and
577
- * the failure mode to avoid is a revocation that deletes the consent row
578
- * while every already-issued token keeps working until it expires. So the
579
- * response says what was actually ended, and a caller can tell *nothing
580
- * matched* from *matched and ended*.
581
- *
582
- * `tokens_revoked` is the count of refresh **families** ended. It is not a
583
- * count of access tokens, and deliberately so: an access token is stateless
584
- * and short-lived, and a number that claimed to have revoked one would be the
585
- * kind of sentence this project keeps having to take back.
586
- *
587
- * **What actually happens to the access token is stronger than this comment
588
- * first claimed, and narrower than its correction (W9c, 2026-08-19).** The
589
- * first version said *"let the access token expire"*. It does not. The
590
- * correction then said *"the same access token dies"*, which is true and
591
- * under-specified — Data-W9c read `resolveAnyToken` instead of copying the
592
- * sentence and found what the check is actually bound to:
593
- *
594
- * `if (claims.client_id) { ...consent-grant lookup... }`
595
- *
596
- * So the immediate death is scoped to **`client_id`, not to a session and not
597
- * to the end user.** Every currently-valid token issued through *this
598
- * client's* OAuth flow for this end user dies at once — including a second
599
- * tab holding a different token from the same client. A plain `auth.login()`
600
- * session is untouched, because it carries no `client_id` for the check to
601
- * read, and a token bound to a *different* client is untouched too.
602
- *
603
- * That distinction is the whole point of revoking one grant rather than
604
- * logging somebody out: *"without touching any other client's access"* is
605
- * what this route promises, and the check is what makes it true.
606
- *
607
- * That is a better outcome than the contract promised, and it is written down
608
- * here for one reason: **a caller must not build on the weaker sentence.** If
609
- * this platform ever moves to stateless verification without the revocation
610
- * re-check, the access token would start living out its TTL again, and
611
- * anything that quietly relied on immediate death would break silently.
612
- */
613
- declare const consentRevokeResponse: z.ZodObject<{
614
- revoked: z.ZodBoolean;
615
- tokens_revoked: z.ZodNumber;
616
- }, z.core.$strip>;
617
- type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
618
-
619
596
  /**
620
597
  * Codes the SDK produces itself rather than relaying from the server. Kept
621
598
  * out of `@fleetless/contracts`' `ERROR_CODES` deliberately — that list is
@@ -656,7 +633,12 @@ type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
656
633
  * signature and is still passing an options object third; and a
657
634
  * `concurrency` on `assets.prepareUrdfScene` that is not a positive
658
635
  * integer, which would otherwise fetch nothing and return a scene that
659
- * renders blank with no error to explain why. All three are refused
636
+ * renders blank with no error to explain why. **A fourth since 3.0.0:**
637
+ * every `auth` method needing an app user's own session, called on a client
638
+ * built with a `serverKey` — `register`, `login`, `logout`, the password
639
+ * and invitation calls, both OIDC calls, the two MCP decisions and the two
640
+ * grant calls. Those threw a bare `Error` before, which a caller could only
641
+ * catch by message. All of them are refused
660
642
  * before any request is sent — as a rejection, since every one of those
661
643
  * methods is `async`. A client-side mistake to fix, not something a
662
644
  * server response could ever produce, which is why this code belongs here
@@ -675,17 +657,6 @@ type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
675
657
  * rather than left to surface as a confusing downstream failure from
676
658
  * `URDFLoader.parse(undefined)` or similar — the caller's fix is "sync a
677
659
  * URDF first", which this error can say directly.
678
- * - `no_hosted_login_attempt`: `auth.completeHostedLogin()` was called with
679
- * an empty `expectedState` — nothing was persisted for this attempt. A
680
- * callback landing in a different tab or window than the one that called
681
- * `beginHostedLogin`, a restored session, or storage cleared in between
682
- * all produce exactly this, and none of them is an attack. Told apart
683
- * from `state_mismatch` on purpose: the two diagnoses have different
684
- * remedies ("check how you persisted the value" versus "this response
685
- * belongs to a login you did not start"). It also closes a real gap —
686
- * comparing two *empty* strings with `!==` is `false`, so without this
687
- * check first, a caller with nothing persisted at all could reach
688
- * `state_mismatch`'s comparison having contributed no defence whatsoever.
689
660
  * - `aborted`: `assets.prepareUrdfScene()` was given an `AbortSignal` and it
690
661
  * fired — either already-aborted before the call started, or mid-flight
691
662
  * while a fetch was in progress. Normalized to this one code regardless of
@@ -698,18 +669,27 @@ type ConsentRevokeResponse = z.infer<typeof consentRevokeResponse>;
698
669
  * created (`blob:` URLs) is revoked before this throws — an aborted load
699
670
  * must not leak what it fetched before the signal fired, the same
700
671
  * guarantee a failed load already had.
701
- * - `state_mismatch`: `auth.completeHostedLogin()` was called with a `state`
702
- * that does not match the `expectedState` its own `beginHostedLogin()`
703
- * returned for this attempt (or with no `state` at all `beginHostedLogin`
672
+ * - `state_mismatch`: `auth.completeOidcLogin()` was called with a `state`
673
+ * that does not match the `expectedState` its own `beginOidcLogin()`
674
+ * returned for this attempt or with no `state` at all (`beginOidcLogin`
704
675
  * always sets one, so a callback carrying none does not look like a reply
705
- * to a flow this client started). Thrown before `/oauth/token` is ever
706
- * called: RFC 6749 section 10.12's whole point is that a caller must not
707
- * complete an authorization response it did not itself request, so this
708
- * check happens client-side, first, rather than being left to the server
709
- * to catch by which point a code exchange would already have been
710
- * attempted for a flow this client never started.
676
+ * to a flow this client started), or with an **empty `expectedState`**,
677
+ * meaning nothing was persisted for this attempt at all. That last case is
678
+ * folded in rather than given its own code: two empty strings compare
679
+ * equal, so it has to be checked explicitly or the comparison defends
680
+ * nothing for exactly the callers most likely to hit it but a caller
681
+ * branching on the code has the same next step either way, which is to
682
+ * start the sign-in again. Which of the two happened is in the message,
683
+ * because the *developer's* remedies do differ ("check how your app
684
+ * persisted the value" versus "this response belongs to a sign-in you did
685
+ * not start"). Thrown before `/api/client/oidc/exchange` is ever called:
686
+ * RFC 6749 section 10.12's whole point is that a caller must not complete
687
+ * an authorization response it did not itself request, so this check
688
+ * happens client-side, first, rather than being left to the server to
689
+ * catch — by which point a one-time code would already have been spent for
690
+ * a flow this client never started.
711
691
  */
712
- declare const SDK_ERROR_CODES: readonly ["no_session", "no_websocket", "unparseable_error", "command_timeout", "command_outcome_unknown", "unexpected_response", "invalid_option", "untrusted_absolute_url", "state_mismatch", "no_hosted_login_attempt", "no_urdf_synced", "aborted"];
692
+ declare const SDK_ERROR_CODES: readonly ["no_session", "no_websocket", "unparseable_error", "command_timeout", "command_outcome_unknown", "unexpected_response", "invalid_option", "untrusted_absolute_url", "state_mismatch", "no_urdf_synced", "aborted"];
713
693
  /**
714
694
  * The union of `SDK_ERROR_CODES` — the SDK's own client-side error
715
695
  * vocabulary. A `FleetlessError` whose `code` is one of these was raised by
@@ -1270,389 +1250,351 @@ declare class InMemoryTokenStore implements TokenStore {
1270
1250
  }
1271
1251
 
1272
1252
  /**
1273
- * `beginHostedLogin()`'s input: the OAuth client the developer registered
1274
- * for this app, where the browser should come back to, and optionally what
1275
- * the resulting token should be usable against.
1253
+ * `register()`'s input. The app identifier is **not** here: the client already
1254
+ * holds one (`createClient({ appIdentifier })`) and sends it itself, so there
1255
+ * is no way for a caller to register somebody into a different app than the
1256
+ * one this client speaks for.
1276
1257
  */
1277
- interface BeginHostedLoginOptions {
1258
+ interface RegisterOptions {
1259
+ /** The address the verification mail goes to. Nothing works until that link is spent. */
1260
+ email: string;
1261
+ /** At least 12 characters — `clientRegisterRequest` refuses less with a `validation_error`. */
1262
+ password: string;
1278
1263
  /**
1279
- * The opaque `client_id` issued when the developer registered this app's
1280
- * OAuth client (console, App Settings). **Never `appIdentifier`**they
1281
- * are deliberately different identifiers the contracts call this "the
1282
- * `client_id` on the wire: opaque, and not the app identifier".
1264
+ * What the app should call this person. Optional, and **omitted from the
1265
+ * request entirely** when you do not pass it `clientRegisterRequest` is a
1266
+ * strict schema, so a key carrying `undefined` would be a `422` rather than a
1267
+ * default.
1283
1268
  */
1284
- clientId: string;
1269
+ displayName?: string;
1270
+ }
1271
+ /** `acceptInvitation()`'s input — the token out of the mailed link, plus the password the account gets. */
1272
+ interface AcceptInvitationOptions {
1273
+ /** The `token` from the invitation link the developer's app was linked to. */
1274
+ token: string;
1275
+ /** At least 12 characters. The invitation fixes the role; this call fixes the credential. */
1276
+ password: string;
1277
+ /** Optional, and omitted from the request entirely when absent — same strict-schema reason as `RegisterOptions.displayName`. */
1278
+ displayName?: string;
1279
+ }
1280
+ /** One sign-in button on the app's own login screen, as `listProviders()` lists it. */
1281
+ interface ProviderButton {
1282
+ /** What `beginOidcLogin` addresses this provider by. */
1283
+ slug: string;
1284
+ /** The label the developer configured, to be rendered on the button. */
1285
+ name: string;
1286
+ }
1287
+ /** `beginOidcLogin()`'s input: which provider, and where Fleetless should send the browser back to. */
1288
+ interface BeginOidcLoginOptions {
1289
+ /** A `slug` from `listProviders()`. An unknown one is a `404` when the browser reaches the start route, not here. */
1290
+ slug: string;
1285
1291
  /**
1286
- * Must be registered, byte-for-byte, as one of that client's
1287
- * `redirect_uris` matching at the server is exact-string, never a
1288
- * prefix (the contracts' `redirectUri` doc comment says why).
1292
+ * Where the browser comes back to with `?code=…&state=…` (or `?error=…`).
1293
+ * Checked against the app's **allowed origins** server-side, by origin — so
1294
+ * the path is yours to choose and the origin is not.
1295
+ *
1296
+ * **Two shapes are refused outright, before the origin is compared**, and
1297
+ * neither refusal mentions them: a URL carrying a **fragment**
1298
+ * (`https://app.example.com/#/auth/callback`) and one carrying **userinfo**
1299
+ * (`https://someone@app.example.com/cb`). Both come back as a flat
1300
+ * `400 invalid_redirect_uri` reading "The redirect_uri is not an origin this
1301
+ * app answers for", which sends people to re-check an allow-list that was
1302
+ * never the problem.
1303
+ *
1304
+ * The fragment case is the one that costs time, because hash routing is the
1305
+ * default for a static-hosted SPA with no server rewrite. Give the callback
1306
+ * a real path (`/auth/callback`) and let your router pick the hash route up
1307
+ * from there; a fragment is a browser-side construct the redirect could not
1308
+ * carry a code in anyway.
1289
1309
  */
1290
1310
  redirectUri: string;
1311
+ }
1312
+ /** What `beginOidcLogin()` returns. Nothing here has touched the network. */
1313
+ interface OidcLoginRequest {
1291
1314
  /**
1292
- * The OAuth scopes to request, space-separated. Omit it to get the
1293
- * client's registered default, which is what an ordinary app login wants.
1294
- */
1295
- scope?: string;
1296
- /**
1297
- * RFC 8707 audience binding: the resource this
1298
- * session's token should be usable against. **Omit it for an ordinary app
1299
- * login.** A token with no `resource` carries no `aud` and works
1300
- * unrestricted against this app's own REST surface exactly as it always
1301
- * has; that path is unaffected by this field's existence. Only a caller
1302
- * that is itself going to present the token to an audience-checking
1303
- * resource needs to ask for one.
1304
- *
1305
- * **`/oauth/authorize` accepts exactly two shapes** — anything else is
1306
- * refused with `invalid_target` on the redirect back, before a code is
1307
- * ever issued (the cloud's own known-resource check):
1308
- *
1309
- * - `<base>/mcp-stub/resource` — the global OAuth resource stub, matched
1310
- * byte-for-byte.
1311
- * - `<base>/mcp-stub/resource/<app_identifier>` — the per-app stub, and
1312
- * only for **the calling client's own app**. This is an existence check
1313
- * plus an ownership check, not a shape check: the app must exist *and*
1314
- * be the one this `clientId` is registered to, so a client on app A can
1315
- * never be minted a token whose `aud` names app B.
1316
- *
1317
- * Both are served and validated by the platform's resource stub, whose
1318
- * validator refuses a token with **no** `aud` exactly as hard as one with
1319
- * the wrong `aud` —
1320
- * "unscoped" must never read as "for me" — comparing by equality, never
1321
- * by prefix.
1322
- *
1323
- * **`<base>/mcp/<app_identifier>` is not a resource any more.** There is
1324
- * no per-app MCP endpoint: the cloud registers one central, non-parametric
1325
- * `POST /mcp` (the contracts' `MCP_ENDPOINT_PATH`), and the
1326
- * `/oauth/authorize` branch that used to
1327
- * accept a `/mcp/<app>` resource was deleted along with the app-level
1328
- * `mcp_enabled` flag. Asking for one now yields `invalid_target` for
1329
- * every app, **including your own**. The central MCP endpoint has its own
1330
- * OAuth flow, which this SDK's hosted login does not drive —
1331
- * `beginHostedLogin` always targets
1332
- * `OAUTH_PATHS.authorize`.
1333
- *
1334
- * Whatever you name here is re-checked at the token exchange: it must
1335
- * match what the code was authorized for, and it must still name a
1336
- * resource this client may be issued a token for.
1315
+ * Send the end user's browser here. **This SDK does not navigate** — it has
1316
+ * no opinion about whether that is a full page load, a popup or a native web
1317
+ * view, the same boundary `cameras.live` draws by handing back a URL and a
1318
+ * token and stopping there.
1337
1319
  */
1338
- resource?: string;
1339
- }
1340
- /** What `beginHostedLogin()` returns — nothing here has touched the network yet. */
1341
- interface HostedLoginRequest {
1342
- /** Send the end user's browser here to start the hosted login page. */
1343
1320
  url: string;
1344
1321
  /**
1345
- * Persist this alongside `codeVerifier` before navigating away, and pass
1346
- * both back into `completeHostedLogin`. **This SDK does not persist them
1347
- * for you.** The redirect back to `redirectUri` is a fresh page load for a
1348
- * browser app — nothing kept in this SDK's own memory survives it (the
1349
- * same reasoning `TokenStore` states: the SDK itself never assumes a
1350
- * browser, or any storage, exists). An in-memory default
1351
- * here would not be merely suboptimal, it would be broken for the primary
1352
- * use case while looking like it worked for anything that never actually
1353
- * navigates away. `sessionStorage`, a signed cookie, or a plain variable
1354
- * (a popup flow that never truly navigates) are all valid — that choice is
1355
- * the caller's.
1322
+ * Persist this next to `codeVerifier` **before navigating away**, and pass
1323
+ * both back into `completeOidcLogin`. The SDK does not persist them for you:
1324
+ * the redirect back is a fresh page load for a browser app, and nothing kept
1325
+ * in this SDK's memory survives it. `sessionStorage`, a signed cookie or a
1326
+ * plain variable (a popup flow that never truly navigates) are all valid —
1327
+ * that choice is the caller's, and an in-memory default here would look like
1328
+ * it worked right up until the first real redirect.
1356
1329
  */
1357
1330
  state: string;
1358
1331
  /**
1359
- * The PKCE code verifier for this attempt. Persist it exactly as
1360
- * `state` above and pass it back to `completeHostedLogin` it is what
1361
- * proves the code exchange comes from the client that started the flow.
1332
+ * The PKCE code verifier for this attempt persist it exactly as `state`.
1333
+ * **The app runs its own PKCE against Fleetless**, a second exchange
1334
+ * independent of the one Fleetless runs against the identity provider, which
1335
+ * is what makes the one-time code in the redirect worth nothing to whoever
1336
+ * else reads that URL.
1362
1337
  */
1363
1338
  codeVerifier: string;
1364
1339
  }
1365
- /** `completeHostedLogin()`'s input — the redirect back, plus what `beginHostedLogin` returned for this same attempt. */
1366
- interface CompleteHostedLoginOptions {
1367
- /** The `code` query parameter from the redirect back to `redirectUri`. */
1340
+ /** `completeOidcLogin()`'s input — the redirect back, plus what `beginOidcLogin` returned for this same attempt. */
1341
+ interface CompleteOidcLoginOptions {
1342
+ /** The `code` query parameter from the redirect back to `redirectUri`. It lives 60 seconds. */
1368
1343
  code: string;
1369
1344
  /** The `state` query parameter from that same redirect. */
1370
1345
  state: string;
1371
- /**
1372
- * The `state` this attempt's `beginHostedLogin` returned. Checked against
1373
- * `state` above **before any network call** — the whole point of RFC
1374
- * 6749 section 10.12 is that a caller must not complete an authorization
1375
- * response it did not itself request.
1376
- */
1346
+ /** The `state` this attempt's `beginOidcLogin` returned. Compared **before any network call**. */
1377
1347
  expectedState: string;
1378
- /** The `codeVerifier` this attempt's `beginHostedLogin` returned. */
1348
+ /** The `codeVerifier` this attempt's `beginOidcLogin` returned. */
1379
1349
  codeVerifier: string;
1380
- /** Must be the exact same `client_id` passed to `beginHostedLogin`. */
1381
- clientId: string;
1382
- /** Must be the exact same string passed to `beginHostedLogin`. */
1383
- redirectUri: string;
1384
- /**
1385
- * Must be the exact same string passed to `beginHostedLogin`, if any.
1386
- * Resending it here is not what binds the audience — the server already
1387
- * bound `resource` to the authorization code at `/oauth/authorize` and
1388
- * mints `aud` from that stored value regardless of what this call sends —
1389
- * but RFC 8707 section 2 expects a client to name the resource at both steps,
1390
- * and the cloud rejects a *mismatched* resend outright (`invalid_target`).
1391
- * Omit it here exactly when it was omitted at `beginHostedLogin`.
1392
- */
1393
- resource?: string;
1394
1350
  }
1395
1351
  /**
1396
- * What `logout()` resolves with five separable facts, not one nullable
1397
- * URL, mirroring the wire's `clientLogoutResponse`.
1398
- *
1399
- * **`idp_logout` is `null` whenever the server never answered**, which
1400
- * happens for two different reasons: there was no local session to ask
1401
- * about, or the request itself failed. Either way there is nothing to
1402
- * report — not even "not federated", because this client never learned that
1403
- * either.
1404
- *
1405
- * `revoked` does not identify which of the two you got. It is `false` only
1406
- * for the failed request; **a logout with no local session at all resolves
1407
- * `{ revoked: true, idp_logout: null }`**, since nothing lingers
1408
- * server-side and that counts as revoked. So `revoked: false` does imply
1409
- * `idp_logout: null`, and the converse does not hold. Do not read the two
1410
- * fields as one bit.
1352
+ * What `approveMcpInteraction`/`denyMcpInteraction` resolve with: **where to
1353
+ * send the browser**, and nothing else.
1411
1354
  *
1412
- * When a real response did come back, `revoked` is `true` and `idp_logout`
1413
- * is one of:
1414
- *
1415
- * - `{ status: 'redirect', url }` — send the browser here to end the
1416
- * session at the IdP too. Nothing else in this SDK does that navigation
1417
- * for you (same "this SDK is thin" reasoning as `cameras.live`).
1418
- * - `{ status: 'not_federated' }` — this session never came from an IdP;
1419
- * there is nothing else to end.
1420
- * - `{ status: 'unsupported_by_idp' }` — it did, and the IdP publishes no
1421
- * `end_session_endpoint` (RP-initiated logout is optional in OIDC).
1422
- * - `{ status: 'hint_unavailable' }` — it did, the IdP *can* end the
1423
- * session, and Fleetless has nothing to ask it with: the stored
1424
- * `id_token_hint` could not be decrypted, or the session predates the fix
1425
- * that started keeping one.
1426
- * - `{ status: 'session_unknown' }` — the server did not find this session at
1427
- * all (the token was unknown, already superseded, revoked, or expired), so
1428
- * it can say **nothing** about an IdP.
1355
+ * A denial carries a redirect too, with `error=access_denied` on it a client
1356
+ * that is refused has to learn so from its own callback rather than from a page
1357
+ * nobody sent it, so both outcomes end the same way for the app: navigate here.
1358
+ */
1359
+ interface McpInteractionDecision {
1360
+ /** The absolute URL to navigate to. Wire field `redirect_to`. */
1361
+ redirectTo: string;
1362
+ }
1363
+ /**
1364
+ * Who the caller is, reachable as `client.auth` the whole client auth API of
1365
+ * spec §4, as JSON.
1429
1366
  *
1430
- * **`unsupported_by_idp` and `hint_unavailable` both mean the IdP session
1431
- * survives and this platform cannot end it** — do not render either one the
1432
- * same as `not_federated`; that reports a session as fully ended when it
1433
- * isn't.
1367
+ * **Fleetless serves an app user no page.** The developer's own UI owns every
1368
+ * screen: login, registration, verification, invitation acceptance, password
1369
+ * reset, the provider buttons and the MCP consent. These methods are what those
1370
+ * screens call. The hosted, app-branded login and consent pages this SDK used
1371
+ * to drive are gone, along with `beginHostedLogin`/`completeHostedLogin`.
1434
1372
  *
1435
- * **`session_unknown` is a different kind of nothing, and the reason it
1436
- * exists is a second logout.** A double click, a repeated POST, an
1437
- * app that logs out on unmount *and* on a route change: the last call wins,
1438
- * and before this outcome existed it answered `not_federated` a positive
1439
- * claim about an IdP the server had never looked up. An app that treats it as
1440
- * *"fully logged out"* skips a redirect the **first** call may well have
1441
- * returned, and the user stays signed in at the IdP after clicking log out.
1373
+ * **The enumeration discipline is the design's, and it shapes this surface.**
1374
+ * `register`, `resendVerification` and `requestPasswordReset` resolve for every
1375
+ * policy-allowed request whether or not the address exists, and `login` answers
1376
+ * the identical `invalid_credentials` for a wrong password, a blocked account
1377
+ * and an unverified one. So: *resolving does not mean an account exists*, and
1378
+ * the only honest refusals are the ones about policy rather than about a
1379
+ * person `registration_closed`, `domain_not_allowed`, `quota_exceeded`.
1442
1380
  *
1443
- * Note that `revoked` cannot help you here either: it reports whether the
1444
- * HTTP call succeeded, not whether a session was found so this case
1445
- * arrives as `{ revoked: true, idp_logout: { status: 'session_unknown' } }`,
1446
- * never as `null`.
1381
+ * **A client built with a `serverKey` refuses everything that needs an app
1382
+ * user's own session** with `invalid_option`, before any request. `me()`,
1383
+ * `listProviders()`, `mcpInteraction()` and `oidcErrorFromCallback()` still
1384
+ * work on one: the first is what a server key is *for*, the next two are public
1385
+ * reads the cloud answers without any credential at all, and the last touches
1386
+ * no network.
1447
1387
  */
1448
- interface LogoutResult {
1449
- /**
1450
- * Whether the server-side revoke actually happened. It reports the fate
1451
- * of the HTTP call, not whether a session was found — `false` means the
1452
- * refresh family may still be alive even though this client has
1453
- * forgotten it.
1454
- */
1455
- revoked: boolean;
1388
+ interface AuthApi {
1456
1389
  /**
1457
- * What the server could say about the identity provider behind this
1458
- * session, or `null` when there was no answer to report at all — either
1459
- * because there was no local session to ask about, or because the request
1460
- * failed. `null` is **not** the same as `not_federated`, which is a real
1461
- * finding about a real session.
1462
- *
1463
- * When it is not `null` it is one of five statuses: `redirect` (with a
1464
- * `url` to send the browser to, to end the session at the identity
1465
- * provider too), `not_federated` (this session never came from one),
1466
- * `unsupported_by_idp` (it did, and the provider offers no
1467
- * RP-initiated logout), `hint_unavailable` (it did, the provider can end
1468
- * the session, and Fleetless has nothing to ask it with), or
1469
- * `session_unknown` (the server did not find this session, so it can say
1470
- * nothing about a provider).
1471
- *
1472
- * **`unsupported_by_idp` and `hint_unavailable` both mean the identity
1473
- * provider's session survives and this platform cannot end it.** Render
1474
- * either one like `not_federated` and you report a session as fully ended
1475
- * when it is not.
1390
+ * Self-registration. Writes the account as `pending_verification` and mails
1391
+ * the app's verification link; **the account cannot log in until that link is
1392
+ * spent** (`verifyEmail`).
1393
+ *
1394
+ * Resolves on the route's `202` — which the cloud answers for every
1395
+ * policy-allowed request, whether the address was new or already known. It is
1396
+ * not a claim that an account was created, and an app that renders it as one
1397
+ * ("welcome, Ada!") is showing a stranger the enumeration oracle this whole
1398
+ * family is built to avoid. Render "check your mail" instead.
1399
+ *
1400
+ * Throws a `FleetlessError` carrying the cloud's own code for a refusal, and
1401
+ * that is the distinction this method exists to preserve: `registration_closed`
1402
+ * (the app has self-registration off), `domain_not_allowed` (the address is
1403
+ * outside the app's allowed domains), `quota_exceeded` (the org has as many
1404
+ * app users as its quota allows) and `not_found` (no app carries this
1405
+ * client's `appIdentifier`) are all things the app can say out loud, because
1406
+ * none of them is about whether a person exists.
1476
1407
  */
1477
- idp_logout: ClientLogoutResponse['idp_logout'] | null;
1478
- }
1479
- /**
1480
- * Who the caller is, reachable as `client.auth`. A client built with a
1481
- * `tokenStore` uses the full surface. One built with a `serverKey` already
1482
- * has an identity and no user session, so **`me()` is the only method it
1483
- * can call** — `login`, `beginHostedLogin`, `completeHostedLogin`,
1484
- * `logout`, `changePassword` and `passwordResetUrl` all throw on one.
1485
- */
1486
- interface AuthApi {
1487
- /** Exchanges email + password, and the client's configured app identifier, for a session. */
1488
- login(email: string, password: string): Promise<void>;
1408
+ register(input: RegisterOptions): Promise<void>;
1489
1409
  /**
1490
- * Starts the hosted login flow: a Fleetless-served
1491
- * login page an app's end user is redirected to, with optional
1492
- * per-app IdP federation. Builds the `/oauth/authorize` URL (Authorization
1493
- * Code + PKCE, S256 only — OAuth 2.1 removes `plain`) and generates the
1494
- * `state`/`codeVerifier` PKCE and CSRF protection need. **Makes no network
1495
- * call** everything here is local, so nothing about the app, the
1496
- * client, or the redirect URI is validated until the browser actually
1497
- * reaches `/oauth/authorize`.
1498
- *
1499
- * Async only because computing the S256 `code_challenge` needs
1500
- * `crypto.subtle.digest`, which the Web Crypto API only ever offers as a
1501
- * promise — there is no synchronous digest to call instead.
1410
+ * Spends a verification token and **stores the session it answers with**, so
1411
+ * the person is not asked to log in immediately after proving they can read
1412
+ * the mail.
1413
+ *
1414
+ * `token_spent` covers unknown, expired and already-used alike one code,
1415
+ * because the remedy is one thing: ask for a fresh link with
1416
+ * `resendVerification`. An app rendering this refusal should offer that.
1502
1417
  */
1503
- beginHostedLogin(options: BeginHostedLoginOptions): Promise<HostedLoginRequest>;
1418
+ verifyEmail(token: string): Promise<void>;
1419
+ /** Asks for the verification mail again. Resolves on `202` for every policy-allowed request, existing address or not — same reason as `register`. */
1420
+ resendVerification(email: string): Promise<void>;
1504
1421
  /**
1505
- * Completes the hosted login flow: checks `state` against `expectedState`
1506
- * (before any network call — see `CompleteHostedLoginOptions.expectedState`),
1507
- * exchanges `code` for tokens at `/oauth/token`, and stores them via the
1508
- * same `tokenStore` `login()` uses.
1509
- *
1510
- * **Two distinct refusals before that check, not one.** An empty
1511
- * `expectedState` throws `no_hosted_login_attempt` — nothing was
1512
- * persisted for this attempt (a different tab, a restored session,
1513
- * cleared storage), not necessarily an attack. Only once `expectedState`
1514
- * is actually present does a mismatch (or a missing `state` on the
1515
- * callback itself) throw `state_mismatch`. The two are told apart on
1516
- * purpose: they call for different remedies, and collapsing them would
1517
- * tell a developer debugging an ordinary storage gap that their app is
1518
- * under attack.
1519
- *
1520
- * Once past that check and the exchange completes, `me()`, `logout()`,
1521
- * `changePassword()` and silent refresh all behave identically afterwards,
1522
- * regardless of which flow the session started from. Both routes into a
1523
- * session end at the same Fleetless token: not merely that the bytes
1524
- * match, but that every existing code path treats the result the same
1525
- * way.
1526
- *
1527
- * The wire response is the envelope of RFC 6749 section 5.1 (`token_type`, optional
1528
- * `scope`), not `sessionTokens` — this method normalizes one into the
1529
- * other before storing. **Refresh needs no separate handling**: the cloud
1530
- * mints these tokens through the same session mechanism `/api/client/login`
1531
- * uses (same `refresh_tokens` row, same rotation), so the existing silent
1532
- * refresh (`/api/client/refresh`) already works for a hosted-login
1533
- * session — nothing about the origin of a session is tracked or needs to
1534
- * be.
1535
- *
1536
- * Throws with the OAuth error code as `.code` (e.g. `invalid_grant` for an
1537
- * expired or already-used `code`) if the exchange itself fails — a
1538
- * different vocabulary from every other method on this interface, because
1539
- * `/oauth/token` answers in the shape of RFC 6749 section 5.2, not `apiError`.
1540
- *
1541
- * **Makes exactly one request to `/oauth/token` — never retried, no
1542
- * timeout-and-resend, no internal concurrency of its own.** Stated
1543
- * because the constraint that matters here is not this method's, it is
1544
- * the caller's: **never call this a second time for the same `code`
1545
- * while a first call is still in flight** (a plain "the first attempt
1546
- * looked like it timed out, so retry" is exactly the shape this warns
1547
- * against — it is not a defect in this method, since this method itself
1548
- * has nothing that could ever cause that). The platform treats a second
1549
- * presentation of an authorization code as theft and revokes the whole
1550
- * token family it belongs to, deliberately, even though a plain
1551
- * double-submission looks identical on the wire —
1552
- * because the blast radius is bounded (only a caller already holding the
1553
- * correct `code_verifier` and `client_id` can trigger it, so a merely
1554
- * *sniffed* code cannot lock anyone out) and the alternative is a
1555
- * narrower defence against a real theft.
1556
- *
1557
- * **What actually happens if two requests race, measured:**
1558
- * one of the two receives `200` with a refresh token that the server has
1559
- * already revoked. The access token in that same response keeps working
1560
- * normally for the rest of its short TTL — nothing about the race is
1561
- * visible yet. The failure surfaces at this session's **first silent
1562
- * refresh**, as `token_revoked`, potentially many minutes after the race
1563
- * that actually caused it and with nothing in that later error pointing
1564
- * back to a retry that "worked". If your own framework, an HTTP client
1565
- * wrapper, or a user's impatient double-click can cause this method to
1566
- * be invoked twice concurrently for the same redirect, guard against
1567
- * that at the call site — a simple in-flight flag or disabling the
1568
- * triggering control is enough, since there is only ever one legitimate
1569
- * exchange per authorization code.
1422
+ * Exchanges email + password, and the client's configured app identifier, for
1423
+ * a session.
1424
+ *
1425
+ * `invalid_credentials` is answered identically for a wrong password, a
1426
+ * blocked account and one still waiting to verify. Do not try to tell them
1427
+ * apart there is nothing in the answer that does, deliberately.
1570
1428
  */
1571
- completeHostedLogin(options: CompleteHostedLoginOptions): Promise<void>;
1429
+ login(email: string, password: string): Promise<void>;
1572
1430
  /**
1573
- * Ends the session: revokes the whole refresh-token family server-side
1574
- * (a stolen refresh token stops working immediately) and closes this
1575
- * client's live realtime connection, if it has one. Then clears the
1576
- * local store. Never rejects and always clears the store, even if the
1577
- * server call fails: a user who presses "log out" must end up logged out
1578
- * locally regardless of the network. `revoked` reports whether the
1579
- * server-side revoke actually happened — `false` means the refresh
1580
- * family may still be alive server-side even though this client has
1581
- * forgotten it; an app that cares (a kiosk, a shared workstation) can
1582
- * warn the user or retry, one that doesn't can ignore it.
1431
+ * Ends the session: revokes the whole refresh-token family server-side (a
1432
+ * stolen refresh token stops working immediately), closes this client's live
1433
+ * realtime connection if it has one, and clears the local store.
1434
+ *
1435
+ * **Never rejects, and always clears the store**, even if the server call
1436
+ * fails: a user who presses "log out" must end up logged out locally
1437
+ * regardless of the network.
1583
1438
  *
1584
1439
  * **What this does not do:** invalidate the access token already issued.
1585
- * Access-token checks are stateless (a signed JWT, verified without a
1586
- * server-side lookup) — logout has nothing to flip on that token, only
1587
- * on the refresh family behind it. A token stolen before logout keeps
1588
- * working on REST, and can still open a *new* realtime connection, until
1589
- * it expires on its own — at most 15 minutes. This is a deliberate
1590
- * boundary of the stateless-JWT design (the same one that lets a role
1591
- * change, a block, or a membership removal take effect on the very next
1592
- * request without a fresh token), not a bug but a kiosk or shared
1593
- * workstation needs to know that number.
1594
- *
1595
- * **Nor does it end a federated session at the identity provider.**
1596
- * Ending the Fleetless session and ending the IdP session are
1597
- * two different things — see `LogoutResult.idp_logout`. This method does
1598
- * not act on that information itself (no redirect, no fetch to the IdP);
1599
- * it only reports what the server found, the same "this SDK stays thin"
1600
- * boundary as everywhere else (`cameras.live` hands back a URL and a
1601
- * token and stops there too).
1602
- *
1603
- * **`idp_logout` is `null` when there was no server answer to report** —
1604
- * and that is *not* the same as `revoked === false`.
1605
- *
1606
- * Calling `logout()` with **no local session** returns
1607
- * `{ revoked: true, idp_logout: null }`, so `null` is not the same as
1608
- * `revoked === false` and the two fields are not one bit. TypeScript
1609
- * catches a caller who forgets (the type is `| null`); a JavaScript caller
1610
- * does not.
1611
- *
1612
- * So: **`null` means this client had nothing to send or the request never
1613
- * answered.** It is not `not_federated`, which is a real finding about a
1614
- * real session — conflating the two is the ambiguity this shape exists to
1615
- * remove, and it is the reason to read both fields rather than one.
1440
+ * Access-token checks are stateless (a signed JWT, verified without a lookup),
1441
+ * so logout has nothing to flip on that token only on the refresh family
1442
+ * behind it. A token stolen before logout keeps working on REST, and can
1443
+ * still open a *new* realtime connection, until it expires on its own, at
1444
+ * most 15 minutes. That is a deliberate boundary of the stateless-JWT design,
1445
+ * not a bug, but a kiosk or a shared workstation needs to know the number.
1446
+ *
1447
+ * **Nor does it end a session at the identity provider.** It used to report
1448
+ * what was left of one; that apparatus belonged to the hosted login, where
1449
+ * Fleetless owned the browser. The app owns it now, and an app that wants to
1450
+ * end a provider session redirects there itself — knowing its own provider,
1451
+ * which Fleetless never did better than it.
1616
1452
  */
1617
- logout(): Promise<LogoutResult>;
1618
- /** Who the caller turned out to be, without decoding a token client-side. */
1453
+ logout(): Promise<void>;
1454
+ /** Who the caller turned out to be, without decoding a token client-side — which is how apps end up trusting claims nobody verified. */
1619
1455
  me(): Promise<ClientIdentity>;
1620
1456
  /**
1621
- * Changes the current end user's password.
1622
- *
1623
- * `currentPassword` is required by the server even though the session
1624
- * already proves identity — it is what stops a stolen *session* from
1625
- * becoming a stolen *account* (see `passwordChangeRequest` in
1626
- * `@fleetless/contracts`).
1627
- *
1628
- * **Every other session of this identity is revoked on success, and this
1629
- * call's own session is re-issued, not left alone.** `passwordChangeRequest`
1630
- * carries nothing that identifies the caller's own refresh family, so the
1631
- * server cannot spare one token out of the family it just revoked it
1632
- * revokes all of them and hands back a fresh pair, which this method
1633
- * stores exactly like `login` does. Skipping that store would leave the
1634
- * caller holding tokens the server has already revoked, working only
1635
- * until the access token expires and then silently logged out —
1636
- * indistinguishable from the change having failed, which is the one
1637
- * outcome this route exists to prevent. A user with other tabs or
1638
- * devices logged in will see *those* signed out the moment this
1639
- * resolves; if your app does not already make that consequence visible
1640
- * before they confirm, they will find out from a support ticket instead
1641
- * of from you.
1457
+ * Changes the current app user's password.
1458
+ *
1459
+ * `currentPassword` is required even though the session already proves
1460
+ * identity — it is what stops a stolen *session* from becoming a stolen
1461
+ * *account*.
1462
+ *
1463
+ * **Every other session of this identity is revoked, and this call's own
1464
+ * session is re-issued rather than spared.** The request carries nothing
1465
+ * identifying the caller's own refresh family, so the server revokes all of
1466
+ * them and hands back a fresh pair, which this method stores exactly like
1467
+ * `login`. A user with other tabs or devices signed in will see those signed
1468
+ * out the moment this resolves; if your app does not make that consequence
1469
+ * visible before they confirm, they will find out from a support ticket.
1642
1470
  */
1643
1471
  changePassword(currentPassword: string, newPassword: string): Promise<void>;
1472
+ /** Asks for a reset link. Resolves on `202` for a known and an unknown address alike — the answer says nothing about which it was. */
1473
+ requestPasswordReset(email: string): Promise<void>;
1474
+ /**
1475
+ * Spends a reset token, sets the new password and **stores the session it
1476
+ * answers with**. Every refresh family of that user is revoked first — a
1477
+ * forgotten password is one of the two states where somebody else may be
1478
+ * holding a session.
1479
+ */
1480
+ confirmPasswordReset(token: string, newPassword: string): Promise<void>;
1481
+ /**
1482
+ * Accepts an app invitation: creates the account (or activates one invited
1483
+ * before it existed) with the role the invitation fixed, and **stores the
1484
+ * session**.
1485
+ *
1486
+ * An invitation always bypasses the app's domain whitelist — a developer
1487
+ * inviting somebody by hand has already made the decision the whitelist
1488
+ * automates.
1489
+ */
1490
+ acceptInvitation(input: AcceptInvitationOptions): Promise<void>;
1491
+ /**
1492
+ * The app's **enabled** sign-in providers, for drawing the buttons on your
1493
+ * own login screen. A disabled provider is not a button that refuses; it is a
1494
+ * button that is not there.
1495
+ *
1496
+ * Public and unauthenticated, and it carries nothing but `slug` and `name` on
1497
+ * purpose: the issuer, the client id, the scopes and the linking policy are
1498
+ * management-side facts that would tell a stranger how the app's federation
1499
+ * is configured.
1500
+ */
1501
+ listProviders(): Promise<ProviderButton[]>;
1502
+ /**
1503
+ * Builds the URL that starts a federated sign-in, with a fresh `state` and a
1504
+ * fresh PKCE verifier. **Makes no network call and does not navigate** —
1505
+ * persist `state` and `codeVerifier`, then send the browser to `url`.
1506
+ *
1507
+ * Nothing about the app, the provider or the redirect URI is validated here;
1508
+ * it is all checked when the browser actually reaches the route, in that
1509
+ * order, with the redirect target checked before the provider so that a
1510
+ * caller who got the target wrong learns nothing about which providers the
1511
+ * app has.
1512
+ *
1513
+ * Async only because the S256 `code_challenge` needs `crypto.subtle.digest`,
1514
+ * which the Web Crypto API only ever offers as a promise.
1515
+ */
1516
+ beginOidcLogin(input: BeginOidcLoginOptions): Promise<OidcLoginRequest>;
1517
+ /**
1518
+ * Completes a federated sign-in: checks `state` against `expectedState`,
1519
+ * trades the one-time `code` for a session, and stores it.
1520
+ *
1521
+ * **The state check runs before any request is sent.** RFC 6749 §10.12's
1522
+ * whole point is that a client must not complete an authorization response it
1523
+ * did not itself request — a check made after the exchange would already have
1524
+ * spent a code for a flow this client never started. Both an outright
1525
+ * mismatch and an *empty* `expectedState` throw `state_mismatch`; the message
1526
+ * says which, because the remedies differ ("check how your app persisted the
1527
+ * value" versus "this response belongs to a sign-in you did not start") even
1528
+ * though the next step is the same either way — start the sign-in again.
1529
+ *
1530
+ * The code lives 60 seconds and is single-use. Unknown, expired, replayed and
1531
+ * "the account was blocked in between" all arrive as one `token_spent`,
1532
+ * because the app has nothing different to do about any of them.
1533
+ */
1534
+ completeOidcLogin(input: CompleteOidcLoginOptions): Promise<void>;
1535
+ /**
1536
+ * Reads a **failed** federated sign-in off the redirect back, as a
1537
+ * `FleetlessError` you can branch on, or `null` when the callback carries no
1538
+ * `error` at all.
1539
+ *
1540
+ * Fleetless renders no page for these: the reason is carried to your own
1541
+ * `redirectUri` as `?error=<code>`, and this turns that string into the same
1542
+ * error type every other method throws. A code the contracts define (see
1543
+ * `ClientOidcErrorCode`) becomes that code verbatim; anything else becomes
1544
+ * `unexpected_response` with the raw value in the message, rather than being
1545
+ * passed through as a code neither side defines.
1546
+ *
1547
+ * Purely local — it parses a query string and asks nothing.
1548
+ */
1549
+ oidcErrorFromCallback(params: URLSearchParams): FleetlessError | null;
1550
+ /**
1551
+ * Reads a pending MCP authorization by the interaction id the browser
1552
+ * arrived with, so the app can render its own consent screen.
1553
+ *
1554
+ * **`client_name` is a string the client typed about itself** during an
1555
+ * unauthenticated dynamic registration — nobody checked it, which is why
1556
+ * `client_name_verified` is the literal `false` rather than a boolean with a
1557
+ * `true` branch that could never happen. Do not render it as an identity.
1558
+ *
1559
+ * `interaction_expired` means exactly that: ten minutes ran out, or the id
1560
+ * was never real. Both answer the same way, so the screen to show is "that
1561
+ * took too long, start again" rather than an error.
1562
+ *
1563
+ * **Call this with the app user already signed in.** The route needs no
1564
+ * credential, but it reads one if present, and `already_granted` is `false`
1565
+ * for an anonymous read whatever the truth is — so a consent screen rendered
1566
+ * from an unauthenticated call asks a person to agree to something they
1567
+ * agreed to already. An **expired** token counts as anonymous to this route,
1568
+ * which answers `200` rather than refusing, so this method probes the
1569
+ * session's liveness first and refreshes if it can; a session that cannot be
1570
+ * refreshed is not an error here, it is genuinely anonymous.
1571
+ */
1572
+ mcpInteraction(id: string): Promise<ClientMcpInteraction>;
1573
+ /** Approves a pending MCP authorization on behalf of the signed-in app user, and returns where to send the browser. */
1574
+ approveMcpInteraction(id: string): Promise<McpInteractionDecision>;
1575
+ /** Denies one. Also returns a redirect — with `error=access_denied` on it, so the client learns from its own callback. */
1576
+ denyMcpInteraction(id: string): Promise<McpInteractionDecision>;
1577
+ /**
1578
+ * Every MCP client this app user has standing consent for — the "connected
1579
+ * apps" list, and the door out of a decision a person could otherwise make
1580
+ * once and never unmake. A withdrawn grant is never listed.
1581
+ *
1582
+ * `client_name_verified` is `false` here for the reason it is on
1583
+ * `mcpInteraction`, and it matters more rather than less: a list like this is
1584
+ * read long after the moment of approval, when nobody remembers what they
1585
+ * clicked.
1586
+ */
1587
+ listMcpGrants(): Promise<McpConsentGrant[]>;
1644
1588
  /**
1645
- * The hosted password-reset page, served by the platform on the API
1646
- * origin (`/reset-password`). An app links a user there; nothing is
1647
- * called. The two former methods that posted to `/api/client/password/…`
1648
- * are gone with the routes they named (2.0.0).
1649
- *
1650
- * The page owns the whole flow it takes the address, mails a
1651
- * single-use link, and takes the new password on the way back — so there
1652
- * is nothing for an app to sequence and nothing here that could reveal
1653
- * whether an address belongs to an account.
1589
+ * Withdraws one standing consent by the client's id.
1590
+ *
1591
+ * Resolves whether or not there was anything to withdraw — a client id this
1592
+ * account never approved and one it withdrew a minute ago both land on the
1593
+ * end state the caller asked for. A refusal there would tell a caller which
1594
+ * clients an account has connected, and would turn a double-clicked button
1595
+ * into a failure.
1654
1596
  */
1655
- passwordResetUrl(): string;
1597
+ revokeMcpGrant(clientId: string): Promise<void>;
1656
1598
  }
1657
1599
 
1658
1600
  /**
@@ -1937,58 +1879,6 @@ interface DatapointsApi {
1937
1879
  }): Promise<HistorySamplesResponse>;
1938
1880
  }
1939
1881
 
1940
- /**
1941
- * An end user's own consent grants, reachable as `client.grants` — every
1942
- * client this identity has ever authorized, and the means to take one back
1943
- * without touching any of the others.
1944
- *
1945
- * **End-user only.** A server key acts with the app's own full rights and
1946
- * never went through a consent screen itself — there is no "self" here for
1947
- * it to list or revoke, same reasoning as `auth.login` on a `serverKey`
1948
- * client. An end user reaches a group by invitation and signs in through
1949
- * `auth.login` or the hosted login; a server key never does either.
1950
- */
1951
- interface GrantsApi {
1952
- /**
1953
- * Every client this end user has consented to, most-recently-granted
1954
- * first. Each entry carries names, not only ids (`client_name`, `app_name`,
1955
- * `role_name`) — the id is what `revoke()` addresses, but a person
1956
- * deciding whether to revoke something needs to *recognise* it first, and
1957
- * an id is not recognisable. **`client_name` is not trusted** — a
1958
- * self-registered client chooses its own display name, and one has
1959
- * already been measured calling itself "Fleetless Official Helper" — do
1960
- * not render it as if Fleetless vouched for it.
1961
- */
1962
- list(): Promise<ConsentGrantSummary[]>;
1963
- /**
1964
- * Revokes one grant by the client's id — the `client_id` from a
1965
- * `list()` entry — without touching any other client's access.
1966
- *
1967
- * `revoked` tells "nothing matched" (`false`) from "matched and ended"
1968
- * (`true`) — revoking a grant that is already gone (a stale id, a double
1969
- * click) is not an error, just a no-op the caller can still tell apart
1970
- * from a real revocation. `tokens_revoked` counts refresh **families**
1971
- * ended, not access tokens — that count is deliberately never a claim
1972
- * about access tokens, which are stateless and short-lived by design.
1973
- *
1974
- * **What actually happens to an already-issued access token is stronger
1975
- * than that count implies.** It does not simply expire on its own: the
1976
- * cloud re-checks
1977
- * every request for a revoked grant, so **any** currently-valid access
1978
- * token minted through this same client's OAuth flow for this end user
1979
- * — not only the one used to call `revoke()` — answers `401
1980
- * token_revoked` on its very next request, no wait for its TTL. This is
1981
- * scoped to that one client: a session that never went through this
1982
- * client's consent (a plain `auth.login()` session, or one bound to a
1983
- * different client) carries no trace of this client's id and is
1984
- * unaffected. Do not build on the weaker "it will expire eventually" —
1985
- * if this platform ever moves to stateless verification without that
1986
- * re-check, this immediacy would go away silently, which is exactly why
1987
- * it is written down here instead of left implied.
1988
- */
1989
- revoke(clientId: string): Promise<ConsentRevokeResponse>;
1990
- }
1991
-
1992
1882
  /**
1993
1883
  * Robot-wide job reads, reachable as `client.jobs`. Everything here is
1994
1884
  * addressed by robot rather than by slug, which is what `actions` and
@@ -2142,10 +2032,12 @@ interface FleetlessClientConfig {
2142
2032
  interface FleetlessClient {
2143
2033
  /** The settled configuration, including the defaults `createClient` filled in. */
2144
2034
  readonly config: FleetlessClientConfig;
2145
- /** Logging in, logging out, and reading who the caller currently is. */
2035
+ /**
2036
+ * The whole client auth API: registration, verification, login, logout,
2037
+ * password reset, invitations, the app's federated sign-in providers, the
2038
+ * MCP consent screen, and the app user's own standing MCP grants.
2039
+ */
2146
2040
  readonly auth: AuthApi;
2147
- /** An end user's own consent grants — not available on a `serverKey` client, same reasoning as `auth`'s session-only methods. */
2148
- readonly grants: GrantsApi;
2149
2041
  /** A topic's latest value, a live subscription to it, and its recorded history. */
2150
2042
  readonly datapoints: DatapointsApi;
2151
2043
  /** Long-running work on the robot: invoke, cancel, and watch a job as it runs. */
@@ -2176,8 +2068,8 @@ interface FleetlessClient {
2176
2068
  }
2177
2069
  /**
2178
2070
  * Builds a client for one app. Pass `tokenStore` (or nothing — the default
2179
- * keeps the session in memory) for an end-user client that logs in with
2180
- * `auth.login` or the hosted login; pass `serverKey` for a server-side
2071
+ * keeps the session in memory) for an app-user client that signs in with
2072
+ * `auth.login` or a federated provider; pass `serverKey` for a server-side
2181
2073
  * caller that never holds a user session. Passing both throws, because the
2182
2074
  * two are different identities and a client acts as exactly one.
2183
2075
  *
@@ -2186,4 +2078,4 @@ interface FleetlessClient {
2186
2078
  */
2187
2079
  declare function createClient(options: FleetlessClientOptions): FleetlessClient;
2188
2080
 
2189
- export { type ActionsApi, type Asset, type AssetBytes, type AssetListResponse, type AssetsApi, type AuthApi, type BeginHostedLoginOptions, type BusyDetails, type CameraDescriptor, type CameraLiveSession, type CameraSnapshot, type CameraSnapshotMeta, type CamerasApi, type ClientIdentity, type CompleteHostedLoginOptions, type ConsentGrantSummary, type ConsentRevokeResponse, type CreateMeshLoaderOptions, type DatapointEvent, type DatapointSubscription, type DatapointSubscriptionHandlers, type DatapointValue, type DatapointsApi, type FleetlessClient, type FleetlessClientConfig, type FleetlessClientOptions, FleetlessError, type FleetlessErrorCode, type FleetlessErrorOptions, type GrantsApi, type HistoryAggregation, type HistoryBucketsResponse, type HistoryOptions, type HistorySamplesResponse, type HostedLoginRequest, InMemoryTokenStore, type InvokeOptions, type Job, type JobEvent, type JobState, type JobSubscription, type JobSubscriptionHandlers, type JobsApi, type LogoutResult, type MeshLoaderDelegate, type ParameterInvalidDetails, type ParameterViolation, type PrepareUrdfSceneOptions, type PublishersApi, type RateLimitDetails, SDK_ERROR_CODES, type SdkErrorCode, type SendCommandOptions, type ServicesApi, type StoredSession, type TokenStore, type UrdfCompleteness, type UrdfSceneManager, type UrdfSceneResources, createClient, parameterInvalidDetails };
2081
+ export { type AcceptInvitationOptions, type ActionsApi, type Asset, type AssetBytes, type AssetListResponse, type AssetsApi, type AuthApi, type BeginOidcLoginOptions, type BusyDetails, type CameraDescriptor, type CameraLiveSession, type CameraSnapshot, type CameraSnapshotMeta, type CamerasApi, type ClientIdentity, type ClientMcpInteraction, type ClientOidcErrorCode, type CompleteOidcLoginOptions, type CreateMeshLoaderOptions, type DatapointEvent, type DatapointSubscription, type DatapointSubscriptionHandlers, type DatapointValue, type DatapointsApi, type FleetlessClient, type FleetlessClientConfig, type FleetlessClientOptions, FleetlessError, type FleetlessErrorCode, type FleetlessErrorOptions, type HistoryAggregation, type HistoryBucketsResponse, type HistoryOptions, type HistorySamplesResponse, InMemoryTokenStore, type InvokeOptions, type Job, type JobEvent, type JobState, type JobSubscription, type JobSubscriptionHandlers, type JobsApi, type McpConsentGrant, type McpInteractionDecision, type MeshLoaderDelegate, type OidcLoginRequest, type ParameterInvalidDetails, type ParameterViolation, type PrepareUrdfSceneOptions, type ProviderButton, type PublishersApi, type RateLimitDetails, type RegisterOptions, SDK_ERROR_CODES, type SdkErrorCode, type SendCommandOptions, type ServicesApi, type StoredSession, type TokenStore, type UrdfCompleteness, type UrdfSceneManager, type UrdfSceneResources, createClient, parameterInvalidDetails };