@neta-art/cohub 8.13.0 → 8.15.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.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { $ as BoardConnectionSchema, A as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, B as BoardItemPatchSchema, C as SessionPatchReducer, D as DESKTOP_COMMAND_MAX_TIMEOUT_MS, E as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, F as parseDesktopCommand, G as parseBoardEffectInput, H as BoardSemanticMutationSchema, I as parseDesktopCommandId, J as BoardCompositionInputSchema, K as BOARD_ANIMATION_CHANNELS, L as resolveOpenSurface, M as DESKTOP_COMMAND_VERSION, N as isDesktopCallMethod, O as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, P as isTerminalDesktopCommandStatus, Q as parseBoardCompositionInput, R as NAVIGATION_ERROR_MESSAGE_MAX_LENGTH, S as parseAssistantMessageCommit, T as ensureRealtimeConnected, U as BoardEffectInputSchema, V as BoardSemanticCommandSchema, W as BoardEffectSchema, X as BoardProceduralClipSchema, Y as BoardCompositionSchema, Z as BoardTrackSchema, _ as PublicInviteApi, a as DesktopCommandsApi, at as SessionAccessApi, b as SessionGenerationStreamClient, c as ReferralsApi, ct as PublicAssetsApi, d as TasksApi, dt as ModelsApi, et as BOARD_ARROW_STROKE_SIZE, f as BoardClient, ft as GenerationsApi, g as SpacesApi, h as SpaceWebhooksApi, i as WorkCommerceApi, it as DEFAULT_BOARD_RENDER_LIMITS, j as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, k as DESKTOP_COMMAND_PENDING_TTL_SECONDS, l as UsersApi, lt as SkillsApi, m as SpacePublicFilesApi, mt as ChannelsApi, n as createHttpClient, nt as BOARD_BUILTIN_CLIP_KINDS, o as UiCommandsApi, ot as ReferencesApi, p as SpaceClient, pt as CronJobsApi, q as BOARD_ANIMATION_CHANNEL_CAPABILITIES, r as AppCommerceApi, rt as BOARD_BUILTIN_EFFECT_KINDS, s as AppsApi, st as SearchApi, t as CohubHttpClient, tt as BOARD_BUILTIN_CAPABILITIES, u as UserApi, ut as PromptsApi, v as buildSpaceInvitePath, w as createSessionPatchReducer, x as createSessionGenerationStreamClient, y as buildSpacePath, z as BoardAuthoringItemSchema } from "./chunks/http.js";
2
2
  import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as REALTIME_ROOM_EVENT_NAME_PATTERN, c as REQUEST_SOURCE_VIA_MAX_LENGTH, d as isRequestSourceEmpty, f as isRequestSourceUuid, g as readRequestSourceFromEnv, h as parseRequestSourceFromHeaders, i as matchesUnauthorizedErrorToken, l as hasRequestSourceIdentity, m as normalizeRequestSource, n as HttpTransport, o as COHUB_SOURCE_HEADER, p as mergeRequestSourceIntoMeta, r as joinApiUrl, s as COHUB_SOURCE_HEADER_NAMES, t as HttpError, u as isRequestSourceClientId, v as resolveRequestSourceChannel, y as isUuid } from "./chunks/transport.js";
3
- import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebsocketUrl, f as PERMISSIONS, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, n as getCohubContext, o as resolveApiBaseUrl, p as scopeListHasPermission, r as normalizeBaseUrl, s as resolveCohubEnvironment, t as COHUB_ENVIRONMENTS, u as resolveVoiceInputWebsocketUrl } from "./chunks/environment.js";
3
+ import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebsocketUrl, f as PERMISSIONS, h as scopeListHasPermission, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, m as isUserLevelPermission, n as getCohubContext, o as resolveApiBaseUrl, p as USER_LEVEL_PERMISSIONS, r as normalizeBaseUrl, s as resolveCohubEnvironment, t as COHUB_ENVIRONMENTS, u as resolveVoiceInputWebsocketUrl } from "./chunks/environment.js";
4
4
  import { a as extractBillingPayload, c as isFeatureNotEntitledError, i as FEATURE_NOT_ENTITLED_ERROR_CODE, l as isHttpErrorCode, n as createWebsocketClient, o as isBillingAccessBlockedCode, r as BILLING_ACCESS_BLOCKED_ERROR_CODE, s as isBillingAccessBlockedError, t as WebsocketClient } from "./chunks/websocket.js";
5
5
  import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
6
+ import { filterSpacePickerItems, normalizeSpacePickerQuery, orderSpacePickerItems, selectSpacePickerItems } from "./space-picker.js";
6
7
  import { z } from "zod";
7
8
  //#region ../protocol/dist/board.js
8
9
  const BOARD_DOCUMENT_KIND = "cohub.board";
@@ -1390,6 +1391,14 @@ const buildAppRuntimeConfigureRequest = (input) => ({
1390
1391
  type: "configure.request",
1391
1392
  ...input
1392
1393
  });
1394
+ const buildAppRuntimePointer = (input) => ({
1395
+ protocol: APP_RUNTIME_PROTOCOL,
1396
+ version: 1,
1397
+ type: "pointer",
1398
+ x: input.x,
1399
+ y: input.y,
1400
+ down: input.down
1401
+ });
1393
1402
  //#endregion
1394
1403
  //#region ../protocol/dist/app-navigation.js
1395
1404
  const APP_NAVIGATION_PROTOCOL = "cohub.app.navigation";
@@ -2199,8 +2208,55 @@ const generateRequestId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now
2199
2208
  var ParentBridgeTransport = class {
2200
2209
  supportsNavigation = true;
2201
2210
  trustedParentOrigin = null;
2211
+ readyAnnounced = false;
2202
2212
  contextListeners = /* @__PURE__ */ new Set();
2203
2213
  contextListener = null;
2214
+ diagnosticListeners = /* @__PURE__ */ new Set();
2215
+ diagnosticListener = null;
2216
+ /**
2217
+ * Tells the host the runtime is ready once, before any subscription. Hosts
2218
+ * gate unsolicited messages (context pushes, diagnostics) on this handshake,
2219
+ * so the diagnostic subscription must trigger it too — an app that never
2220
+ * subscribes to context still needs diagnostics to arrive.
2221
+ */
2222
+ announceReady() {
2223
+ if (this.readyAnnounced || !hasParent()) return;
2224
+ const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
2225
+ if (!parentOrigin) return;
2226
+ try {
2227
+ window.parent.postMessage(buildAppRuntimeReady(), parentOrigin);
2228
+ this.readyAnnounced = true;
2229
+ } catch {}
2230
+ }
2231
+ subscribeDiagnostics(listener) {
2232
+ if (!hasParent() || typeof window.addEventListener !== "function") return () => {};
2233
+ this.diagnosticListeners.add(listener);
2234
+ this.announceReady();
2235
+ if (!this.diagnosticListener) {
2236
+ this.diagnosticListener = (event) => {
2237
+ if (event.source !== window.parent) return;
2238
+ const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
2239
+ if (parentOrigin && event.origin !== parentOrigin) return;
2240
+ const data = event.data;
2241
+ if (data?.type !== "cohub.app.diagnostic") return;
2242
+ if (typeof data.code !== "string" || typeof data.message !== "string") return;
2243
+ const diagnostic = {
2244
+ code: data.code,
2245
+ message: data.message,
2246
+ ...data.detail && typeof data.detail === "object" ? { detail: data.detail } : {}
2247
+ };
2248
+ for (const current of this.diagnosticListeners) current(diagnostic);
2249
+ };
2250
+ window.addEventListener("message", this.diagnosticListener);
2251
+ }
2252
+ return () => {
2253
+ this.diagnosticListeners.delete(listener);
2254
+ if (this.diagnosticListeners.size === 0 && this.diagnosticListener) {
2255
+ window.removeEventListener("message", this.diagnosticListener);
2256
+ this.diagnosticListener = null;
2257
+ }
2258
+ };
2259
+ }
2204
2260
  subscribeContextChanged(listener) {
2205
2261
  if (!hasParent()) return () => {};
2206
2262
  this.contextListeners.add(listener);
@@ -2214,10 +2270,7 @@ var ParentBridgeTransport = class {
2214
2270
  for (const current of this.contextListeners) current(data.context);
2215
2271
  };
2216
2272
  window.addEventListener("message", this.contextListener);
2217
- const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
2218
- if (parentOrigin) try {
2219
- window.parent.postMessage(buildAppRuntimeReady(), parentOrigin);
2220
- } catch {}
2273
+ this.announceReady();
2221
2274
  }
2222
2275
  return () => {
2223
2276
  this.contextListeners.delete(listener);
@@ -2433,9 +2486,88 @@ var AppRuntimeApi = class {
2433
2486
  * refreshes can re-authorize (preserving viewer grants) instead of falling
2434
2487
  * back to a base session token that only carries app-side scopes. */
2435
2488
  authorizedGrants = null;
2489
+ /**
2490
+ * Rect input regions are hover-gated by the host, which needs to know when
2491
+ * the pointer leaves them. A cross-origin frame swallows those moves once
2492
+ * the host makes it interactive, so while a rect region is active we report
2493
+ * the pointer ourselves. `all`/`none` need no reporting: their state is
2494
+ * known without it.
2495
+ */
2496
+ pointerListening = false;
2497
+ pointerForwarding = false;
2498
+ pointerDown = false;
2499
+ pointerFrame = null;
2500
+ pointerPending = null;
2501
+ /** Cleanup for the constructor's diagnostic subscription; cleared on dispose. */
2502
+ diagnosticUnsubscribe;
2503
+ onPointerMove = (event) => {
2504
+ if (!this.pointerForwarding) return;
2505
+ this.pointerDown = event.buttons !== 0;
2506
+ this.queuePointer(event.clientX, event.clientY);
2507
+ };
2508
+ onPointerDown = (event) => {
2509
+ if (!this.pointerForwarding) return;
2510
+ this.pointerDown = true;
2511
+ this.flushPointer(event.clientX, event.clientY);
2512
+ };
2513
+ onPointerUp = (event) => {
2514
+ if (!this.pointerForwarding && !this.pointerDown) return;
2515
+ this.pointerDown = false;
2516
+ this.flushPointer(event.clientX, event.clientY);
2517
+ };
2518
+ onPointerCancel = (event) => {
2519
+ if (!this.pointerForwarding && !this.pointerDown) return;
2520
+ this.pointerDown = false;
2521
+ this.flushPointer(event.clientX, event.clientY);
2522
+ };
2523
+ /** Coalesce moves to one message per frame; presses flush immediately. */
2524
+ queuePointer(x, y) {
2525
+ this.pointerPending = {
2526
+ x,
2527
+ y
2528
+ };
2529
+ if (this.pointerFrame !== null) return;
2530
+ this.pointerFrame = requestAnimationFrame(() => {
2531
+ this.pointerFrame = null;
2532
+ const pending = this.pointerPending;
2533
+ this.pointerPending = null;
2534
+ if (pending && this.pointerForwarding) this.reportPointer(pending.x, pending.y, this.pointerDown);
2535
+ });
2536
+ }
2537
+ flushPointer(x, y) {
2538
+ if (this.pointerFrame !== null) {
2539
+ cancelAnimationFrame(this.pointerFrame);
2540
+ this.pointerFrame = null;
2541
+ }
2542
+ this.pointerPending = null;
2543
+ this.reportPointer(x, y, this.pointerDown);
2544
+ }
2545
+ reportPointer(x, y, down) {
2546
+ this.transport.notify?.(buildAppRuntimePointer({
2547
+ x,
2548
+ y,
2549
+ down
2550
+ }));
2551
+ }
2552
+ setPointerForwarding(on) {
2553
+ if (typeof window === "undefined" || !this.transport.notify) return;
2554
+ this.pointerForwarding = on;
2555
+ if (!on || this.pointerListening) return;
2556
+ this.pointerListening = true;
2557
+ window.addEventListener("pointermove", this.onPointerMove, {
2558
+ passive: true,
2559
+ capture: true
2560
+ });
2561
+ window.addEventListener("pointerdown", this.onPointerDown, true);
2562
+ window.addEventListener("pointerup", this.onPointerUp, true);
2563
+ window.addEventListener("pointercancel", this.onPointerCancel, true);
2564
+ }
2436
2565
  constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
2437
2566
  this.transport = transport;
2438
2567
  this.appIdResolver = appIdResolver;
2568
+ this.diagnosticUnsubscribe = transport.subscribeDiagnostics?.((diagnostic) => {
2569
+ console.warn(`[cohub] ${diagnostic.message} (${diagnostic.code})`, diagnostic.detail ?? {});
2570
+ }) ?? null;
2439
2571
  if (appId) {
2440
2572
  this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
2441
2573
  this.grantsStorageKey = `${AUTHORIZED_GRANTS_STORAGE_PREFIX}:${appId}`;
@@ -2515,6 +2647,32 @@ var AppRuntimeApi = class {
2515
2647
  onContextChanged(listener) {
2516
2648
  return this.transport.subscribeContextChanged?.(listener) ?? (() => {});
2517
2649
  }
2650
+ /** Subscribes to developer-facing host diagnostics. */
2651
+ onDiagnostic(listener) {
2652
+ return this.transport.subscribeDiagnostics?.(listener) ?? (() => {});
2653
+ }
2654
+ /**
2655
+ * Releases listeners and any pending pointer frame this runtime registered.
2656
+ * Safe to call more than once.
2657
+ */
2658
+ dispose() {
2659
+ const unsubscribe = this.diagnosticUnsubscribe;
2660
+ this.diagnosticUnsubscribe = null;
2661
+ unsubscribe?.();
2662
+ this.pointerForwarding = false;
2663
+ this.pointerPending = null;
2664
+ if (this.pointerFrame !== null) {
2665
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(this.pointerFrame);
2666
+ this.pointerFrame = null;
2667
+ }
2668
+ if (this.pointerListening && typeof window !== "undefined") {
2669
+ window.removeEventListener("pointermove", this.onPointerMove, { capture: true });
2670
+ window.removeEventListener("pointerdown", this.onPointerDown, true);
2671
+ window.removeEventListener("pointerup", this.onPointerUp, true);
2672
+ window.removeEventListener("pointercancel", this.onPointerCancel, true);
2673
+ this.pointerListening = false;
2674
+ }
2675
+ }
2518
2676
  async navigationOpen(target, call) {
2519
2677
  if (this.transport.supportsNavigation === false) return {
2520
2678
  protocol: "cohub.app.navigation",
@@ -2552,9 +2710,13 @@ var AppRuntimeApi = class {
2552
2710
  * Requests the host to update the overlay's geometry or pointer hit regions.
2553
2711
  * Only meaningful when the App was opened as an `overlay` surface; the host
2554
2712
  * is free to clamp or ignore values that violate its layout policy.
2713
+ *
2714
+ * A non-empty rect `inputRegion` also starts reporting the pointer to the
2715
+ * host, which is how the host learns when it leaves the region.
2555
2716
  */
2556
2717
  requestConfigure(input) {
2557
2718
  this.transport.notify?.(buildAppRuntimeConfigureRequest(input));
2719
+ if (input.inputRegion !== void 0) this.setPointerForwarding(Array.isArray(input.inputRegion) && input.inputRegion.length > 0);
2558
2720
  }
2559
2721
  async getAccessToken(options) {
2560
2722
  await this.ensureStorageKeys();
@@ -2596,10 +2758,15 @@ var AppRuntimeApi = class {
2596
2758
  return this.token;
2597
2759
  }
2598
2760
  /**
2599
- * Requests viewer consent. Without a `spaceId` the grant targets the app's
2600
- * home space; with one it targets that space — the app may only grant what
2601
- * the viewer can already do there themselves. Reuses a previous grant
2602
- * silently unless `alwaysAsk` forces the consent dialog.
2761
+ * Requests viewer consent. With an accessible `spaceId` the grant targets
2762
+ * that Space; otherwise the host resolves a viewer-controlled Space (the
2763
+ * invocation or embedding Space, then the viewer's last picked Space, then
2764
+ * their first accessible one) — never the app author's home Space. The app
2765
+ * may only grant what the viewer can already do there themselves. Reuses a
2766
+ * previous grant silently unless `alwaysAsk` forces the consent dialog.
2767
+ *
2768
+ * Returns whether a token was granted; use
2769
+ * {@link requestSpaceAuthorization} when the app needs to know which Space.
2603
2770
  */
2604
2771
  async requestAuthorization(input) {
2605
2772
  await this.ensureStorageKeys();
@@ -2855,8 +3022,17 @@ var CohubClient = class {
2855
3022
  context() {
2856
3023
  return this.appRuntime.context();
2857
3024
  }
3025
+ /** Releases listeners this client registered. Safe to call more than once. */
3026
+ dispose() {
3027
+ this.appRuntime.dispose();
3028
+ }
2858
3029
  auth = {
2859
- /** Ensure the app holds these scopes. Silent when a grant already covers them; `alwaysAsk` forces the dialog. */
3030
+ /**
3031
+ * Ensure the app holds these scopes. Without an accessible `spaceId` the
3032
+ * host targets a viewer-controlled Space (never the app author's home
3033
+ * Space) — use `requestSpace` when the app needs to know which Space.
3034
+ * Silent when a grant already covers them; `alwaysAsk` forces the dialog.
3035
+ */
2860
3036
  request: (input) => this.appRuntime.requestAuthorization(input),
2861
3037
  /** One consent: the viewer picks a Space and grants the scopes on it. `alwaysAsk` re-opens the picker. */
2862
3038
  requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input),
@@ -3122,6 +3298,19 @@ function hasGrantedAppScopes(userUuid, appId, scopes, spaceId) {
3122
3298
  return scopes.every((scope) => scopeListHasPermission(entry.scopes, scope));
3123
3299
  }
3124
3300
  /**
3301
+ * The Space ids of cached grants that cover every requested scope, scanning all
3302
+ * Spaces. Used for account-level scopes, which are not bound to one Space but
3303
+ * must be renewed against a Space whose grant actually exists. The coverage is
3304
+ * already validated here, so callers must not re-check with a per-Space key
3305
+ * (a legacy, Space-less entry would otherwise never match).
3306
+ */
3307
+ function listGrantedAppSpacesForScopes(userUuid, appId, scopes, homeSpaceId) {
3308
+ if (!userUuid || !appId || scopes.length === 0) return [];
3309
+ const spaces = /* @__PURE__ */ new Set();
3310
+ for (const grant of listGrantedAppScopes(userUuid, appId, homeSpaceId)) if (scopes.every((scope) => scopeListHasPermission(grant.scopes, scope))) spaces.add(grant.spaceId);
3311
+ return [...spaces];
3312
+ }
3313
+ /**
3125
3314
  * Lists every cached grant for an app — one entry per space — so hosts can
3126
3315
  * report what the viewer previously consented to without a server round trip.
3127
3316
  * Entries past their re-consent window are dropped on read.
@@ -3226,11 +3415,24 @@ function sanitizeRequestedScopes(value) {
3226
3415
  }
3227
3416
  /** Consent dialogs render the reason; keep hostile input bounded. */
3228
3417
  const MAX_REASON_LENGTH = 280;
3418
+ /**
3419
+ * Bound the viewer's Space lookup so a stalled network can't leave the consent
3420
+ * dialog reserved but invisible until the SDK request times out.
3421
+ */
3422
+ const SPACE_LIST_TIMEOUT_MS = 1e4;
3423
+ /** Bound a single authorize call so a stalled network can't wedge the flow. */
3424
+ const AUTHORIZE_TIMEOUT_MS = 3e4;
3425
+ /** Cap serial silent-renewal attempts per request (account grants may span Spaces). */
3426
+ const MAX_SILENT_SPACE_ATTEMPTS = 4;
3427
+ const timeoutSignal = (ms) => typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(ms) : void 0;
3428
+ const spaceListSignal = () => timeoutSignal(SPACE_LIST_TIMEOUT_MS);
3229
3429
  var AppAuthorizationError = class extends Error {
3230
3430
  status;
3231
- constructor(message, status) {
3431
+ code;
3432
+ constructor(message, status, code) {
3232
3433
  super(message);
3233
3434
  this.status = status;
3435
+ this.code = code;
3234
3436
  this.name = "AppAuthorizationError";
3235
3437
  }
3236
3438
  };
@@ -3239,6 +3441,17 @@ const isDefinitiveAuthorizationFailure = (error) => error instanceof AppAuthoriz
3239
3441
  403,
3240
3442
  404
3241
3443
  ].includes(error.status);
3444
+ /** A grant the server rejected (revoked / not found) — safe to drop from cache. */
3445
+ const isGrantRejection = (error) => error instanceof AppAuthorizationError && [403, 404].includes(error.status);
3446
+ /** A stale access token — never a revoked grant, so never clear the cache. */
3447
+ const isAuthFailure = (error) => error instanceof AppAuthorizationError && error.status === 401;
3448
+ /** Viewer-facing copy for grant failures the viewer cannot act on technically. */
3449
+ const VIEWER_OPAQUE_AUTH_MESSAGES = {
3450
+ space_inaccessible: "Couldn't connect this app to your Space. Try another Space.",
3451
+ app_not_accessible: "This app isn't available in this Space right now.",
3452
+ scope_not_held: "This app needs permissions you don't have yet."
3453
+ };
3454
+ const viewerOpaqueAuthMessage = (error) => error instanceof AppAuthorizationError && error.code ? VIEWER_OPAQUE_AUTH_MESSAGES[error.code] ?? null : null;
3242
3455
  /**
3243
3456
  * Whether a new authorize request asks for exactly the consent a pending
3244
3457
  * dialog already shows: same scopes, same target and same mode. Such a request
@@ -3454,11 +3667,30 @@ function createAppBridgeCore(config) {
3454
3667
  authOpen: false,
3455
3668
  pendingAuth: null,
3456
3669
  authError: null,
3457
- authSaving: false
3670
+ authSaving: false,
3671
+ selectedSpaceId: null,
3672
+ canChangeSpace: false
3458
3673
  };
3459
3674
  function notify() {
3460
3675
  onStateChange?.({ ...state });
3461
3676
  }
3677
+ /** notify() that never lets a broken host callback escape. */
3678
+ function safeNotify() {
3679
+ try {
3680
+ notify();
3681
+ } catch {}
3682
+ }
3683
+ /** Sends a developer-facing diagnostic to the app; never surfaced to viewers. */
3684
+ function emitDiagnostic(diagnostic) {
3685
+ try {
3686
+ config.notify?.({
3687
+ type: "cohub.app.diagnostic",
3688
+ code: diagnostic.code,
3689
+ message: diagnostic.message,
3690
+ ...diagnostic.detail ? { detail: diagnostic.detail } : {}
3691
+ });
3692
+ } catch {}
3693
+ }
3462
3694
  const pendingPurchaseStorageKey = `cohub-app-purchase:${app.id}`;
3463
3695
  const purchaseInFlight = /* @__PURE__ */ new Map();
3464
3696
  let activePurchase = null;
@@ -3501,13 +3733,14 @@ function createAppBridgeCore(config) {
3501
3733
  * fresh dialog.
3502
3734
  */
3503
3735
  async function authorize(scopes, spaceId, options) {
3504
- const userToken = await getAccessToken();
3736
+ const userToken = await getAccessToken(options?.forceRefreshToken ? { forceRefresh: true } : void 0);
3505
3737
  if (!userToken) {
3506
3738
  await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
3507
3739
  throw new Error("Sign in is required to authorize this app.");
3508
3740
  }
3509
3741
  const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
3510
3742
  method: "POST",
3743
+ signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
3511
3744
  headers: {
3512
3745
  Authorization: `Bearer ${userToken}`,
3513
3746
  "Content-Type": "application/json"
@@ -3519,7 +3752,7 @@ function createAppBridgeCore(config) {
3519
3752
  })
3520
3753
  });
3521
3754
  const payload = await response.json().catch(() => null);
3522
- if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status);
3755
+ if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status, typeof payload?.code === "string" ? payload.code : void 0);
3523
3756
  const token = readTokenResponse(payload);
3524
3757
  if (!token) throw new Error("Invalid app authorization response.");
3525
3758
  const canonicalSpaceId = typeof payload?.grant?.spaceId === "string" && payload.grant.spaceId ? payload.grant.spaceId : spaceId ?? app.spaceId;
@@ -3538,6 +3771,64 @@ function createAppBridgeCore(config) {
3538
3771
  };
3539
3772
  }
3540
3773
  /**
3774
+ * Silent renewal with a single stale-token retry: a 401 means the access
3775
+ * token expired, not that the grant was revoked, so refresh once before
3776
+ * letting the caller classify the failure.
3777
+ */
3778
+ async function authorizeSilent(scopes, spaceId) {
3779
+ try {
3780
+ return await authorize(scopes, spaceId, { silent: true });
3781
+ } catch (error) {
3782
+ if (!isAuthFailure(error)) throw error;
3783
+ return authorize(scopes, spaceId, {
3784
+ silent: true,
3785
+ forceRefreshToken: true
3786
+ });
3787
+ }
3788
+ }
3789
+ /**
3790
+ * Attempts silent renewal against each target in order. Returns true when the
3791
+ * request was answered (success or terminal error); false when every target
3792
+ * was a rejected grant, so the caller should fall back to consent.
3793
+ */
3794
+ async function attemptSilentRenewal(reserved, viewerUuid, scopes, targets) {
3795
+ if (!viewerUuid || targets.length === 0) return false;
3796
+ for (const target of targets) try {
3797
+ const result = await authorizeSilent(scopes, target);
3798
+ if (state.pendingAuth !== reserved) return true;
3799
+ replyPendingAuth(reserved, authorizeResult(result.token, result.spaceId, null));
3800
+ resetDialogState();
3801
+ safeNotify();
3802
+ return true;
3803
+ } catch (error) {
3804
+ if (state.pendingAuth !== reserved) return true;
3805
+ if (isGrantRejection(error)) {
3806
+ clearGrantedAppScopes(viewerUuid, app.id, target);
3807
+ if (target === app.spaceId) clearGrantedAppScopes(viewerUuid, app.id);
3808
+ continue;
3809
+ }
3810
+ replyPendingAuth(reserved, {
3811
+ type: "cohub.app.error",
3812
+ message: error instanceof Error ? error.message : "Authorization failed."
3813
+ });
3814
+ resetDialogState();
3815
+ safeNotify();
3816
+ return true;
3817
+ }
3818
+ return false;
3819
+ }
3820
+ /** Normalizes a raw Space payload into a picker candidate. */
3821
+ function toSpaceOption(value) {
3822
+ if (typeof value.id !== "string" || !value.id) return null;
3823
+ const owner = value.ownerUserUuid ?? value.userUuid;
3824
+ return {
3825
+ id: value.id,
3826
+ name: typeof value.name === "string" && value.name ? value.name : null,
3827
+ ...typeof owner === "string" ? { ownerUserUuid: owner } : {},
3828
+ ...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {}
3829
+ };
3830
+ }
3831
+ /**
3541
3832
  * Lists the viewer's spaces for the consent dialog picker. Fetched by the
3542
3833
  * host with the viewer's own token — the app only ever learns the space
3543
3834
  * the viewer picks, never the list.
@@ -3546,7 +3837,10 @@ function createAppBridgeCore(config) {
3546
3837
  const request = async (forceRefresh = false) => {
3547
3838
  const userToken = await getAccessToken({ forceRefresh });
3548
3839
  if (!userToken) return null;
3549
- return fetch(`${apiOrigin}/api/spaces`, { headers: { Authorization: `Bearer ${userToken}` } });
3840
+ return fetch(`${apiOrigin}/api/spaces`, {
3841
+ headers: { Authorization: `Bearer ${userToken}` },
3842
+ signal: spaceListSignal()
3843
+ });
3550
3844
  };
3551
3845
  try {
3552
3846
  let response = await request();
@@ -3555,14 +3849,33 @@ function createAppBridgeCore(config) {
3555
3849
  const spaces = await response.json();
3556
3850
  if (!Array.isArray(spaces)) return null;
3557
3851
  return spaces.flatMap((space) => {
3558
- if (typeof space.id !== "string" || !space.id) return [];
3559
- return [{
3560
- id: space.id,
3561
- name: typeof space.name === "string" && space.name ? space.name : null,
3562
- ...typeof (space.ownerUserUuid ?? space.userUuid) === "string" ? { ownerUserUuid: space.ownerUserUuid ?? space.userUuid } : {},
3563
- ...typeof space.isPinned === "boolean" ? { isPinned: space.isPinned } : {}
3564
- }];
3852
+ const option = toSpaceOption(space);
3853
+ return option ? [option] : [];
3854
+ });
3855
+ } catch {
3856
+ return null;
3857
+ }
3858
+ }
3859
+ /**
3860
+ * Ensures the viewer has a Space to target when their accessible list is
3861
+ * empty. `GET /api/spaces/default` returns their home Space, creating it on
3862
+ * first use — the same path taken when they first enter Cohub.
3863
+ */
3864
+ async function ensureDefaultSpace() {
3865
+ const request = async (forceRefresh = false) => {
3866
+ const userToken = await getAccessToken({ forceRefresh });
3867
+ if (!userToken) return null;
3868
+ return fetch(`${apiOrigin}/api/spaces/default`, {
3869
+ headers: { Authorization: `Bearer ${userToken}` },
3870
+ signal: spaceListSignal()
3565
3871
  });
3872
+ };
3873
+ try {
3874
+ let response = await request();
3875
+ if (response?.status === 401) response = await request(true);
3876
+ if (!response?.ok) return null;
3877
+ const payload = await response.json();
3878
+ return payload?.space ? toSpaceOption(payload.space) : null;
3566
3879
  } catch {
3567
3880
  return null;
3568
3881
  }
@@ -3581,6 +3894,27 @@ function createAppBridgeCore(config) {
3581
3894
  } catch {}
3582
3895
  }
3583
3896
  /**
3897
+ * Resolves the default target for a Space-bound request to a Space the
3898
+ * viewer controls — preferring wherever the app was invoked, then the last
3899
+ * Space the viewer picked. Returns undefined when nothing is accessible.
3900
+ */
3901
+ function resolveDefaultSpaceId(candidates) {
3902
+ if (!candidates?.length) return void 0;
3903
+ const accessible = (id) => id && candidates.some((space) => space.id === id) ? id : void 0;
3904
+ const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
3905
+ const shell = config.getShell?.() ?? config.shell;
3906
+ return accessible(invocation?.spaceId) ?? accessible(shell?.space?.id) ?? accessible(readLastPickedSpace()) ?? candidates[0]?.id;
3907
+ }
3908
+ /**
3909
+ * The Space the app is currently in, from trusted host context only (no
3910
+ * network). Used to attempt silent renewal before loading the Space list.
3911
+ */
3912
+ function resolveContextSpaceId() {
3913
+ const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
3914
+ const shell = config.getShell?.() ?? config.shell;
3915
+ return invocation?.spaceId ?? shell?.space?.id ?? void 0;
3916
+ }
3917
+ /**
3584
3918
  * Creates the Space with the viewer's own token — the same `POST /api/spaces`
3585
3919
  * path as the web New Space flow and the CLI. The app never holds this token.
3586
3920
  */
@@ -3619,23 +3953,6 @@ function createAppBridgeCore(config) {
3619
3953
  };
3620
3954
  throw new Error(message);
3621
3955
  }
3622
- /**
3623
- * Resolves the target space's name for the consent dialog. The host — not
3624
- * the app — resolves it, so the dialog cannot be tricked into labeling a
3625
- * grant with the wrong space.
3626
- */
3627
- async function resolveSpaceName(spaceId) {
3628
- const userToken = await getAccessToken();
3629
- if (!userToken) return null;
3630
- try {
3631
- const response = await fetch(`${apiOrigin}/api/spaces/${encodeURIComponent(spaceId)}`, { headers: { Authorization: `Bearer ${userToken}` } });
3632
- if (!response.ok) return null;
3633
- const space = await response.json();
3634
- return typeof space.name === "string" && space.name ? space.name : null;
3635
- } catch {
3636
- return null;
3637
- }
3638
- }
3639
3956
  function writePendingPurchase(input) {
3640
3957
  if (typeof sessionStorage === "undefined") return;
3641
3958
  try {
@@ -3693,6 +4010,7 @@ function createAppBridgeCore(config) {
3693
4010
  if (!data?.requestId) return;
3694
4011
  const isLegacyWork = data.type?.startsWith("cohub.work.") === true;
3695
4012
  if (isLegacyWork) legacyRequestIds.add(data.requestId);
4013
+ let reservedAuth = null;
3696
4014
  try {
3697
4015
  if (data.type === "cohub.app.context" || data.type === "cohub.work.context") {
3698
4016
  const context = await getContext();
@@ -3820,20 +4138,24 @@ function createAppBridgeCore(config) {
3820
4138
  }
3821
4139
  if (createSpace) {
3822
4140
  mintedSpace = null;
3823
- state.pendingAuth = {
4141
+ const pendingCreate = {
3824
4142
  requestId: data.requestId,
3825
4143
  scopes,
3826
4144
  reason: sanitizeReason(data.reason),
3827
4145
  homeSpaceName: app.spaceName ?? null,
3828
4146
  createSpace
3829
4147
  };
4148
+ state.pendingAuth = pendingCreate;
4149
+ reservedAuth = pendingCreate;
4150
+ state.selectedSpaceId = null;
4151
+ state.canChangeSpace = false;
3830
4152
  state.authError = null;
3831
4153
  state.authOpen = true;
3832
4154
  notify();
3833
4155
  return;
3834
4156
  }
3835
4157
  if (!selectSpace && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
3836
- const result = await authorize(scopes, spaceId, { silent: true });
4158
+ const result = await authorizeSilent(scopes, spaceId);
3837
4159
  replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, result.spaceId === app.spaceId ? app.spaceName ?? null : null), true);
3838
4160
  return;
3839
4161
  } catch (error) {
@@ -3845,45 +4167,82 @@ function createAppBridgeCore(config) {
3845
4167
  return;
3846
4168
  }
3847
4169
  }
3848
- const viewerUuid = await getViewerUuid();
3849
- const silentSpaceId = selectSpace ? readLastPickedSpace() : spaceId;
3850
- if (!alwaysAsk && viewerUuid && (selectSpace ? Boolean(silentSpaceId) : true) && silentSpaceId !== null && hasGrantedAppScopes(viewerUuid, app.id, scopes, silentSpaceId ?? void 0)) try {
3851
- const result = await authorize(scopes, silentSpaceId, { silent: true });
3852
- replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, null), true);
3853
- return;
3854
- } catch (error) {
3855
- if (!isDefinitiveAuthorizationFailure(error)) {
3856
- replyForRequest(data.requestId, {
3857
- type: "cohub.app.error",
3858
- message: error instanceof Error ? error.message : "Authorization failed."
3859
- }, true);
4170
+ if (state.pendingAuth) {
4171
+ if (isSameConsent(state.pendingAuth, {
4172
+ scopes,
4173
+ spaceId,
4174
+ selectSpace,
4175
+ createSpace
4176
+ })) {
4177
+ state.pendingAuth.joinedRequestIds = [...state.pendingAuth.joinedRequestIds ?? [], data.requestId];
3860
4178
  return;
3861
4179
  }
3862
- clearGrantedAppScopes(viewerUuid, app.id, silentSpaceId);
4180
+ dismissPendingAuth();
3863
4181
  }
3864
- const [spaceName, spaces] = await Promise.all([spaceId ? resolveSpaceName(spaceId) : Promise.resolve(null), selectSpace ? listViewerSpaces() : Promise.resolve(void 0)]);
3865
- state.pendingAuth = {
4182
+ const reserved = {
3866
4183
  requestId: data.requestId,
3867
4184
  scopes,
3868
4185
  reason: sanitizeReason(data.reason),
3869
4186
  homeSpaceName: app.spaceName ?? null,
3870
- ...spaceId ? {
3871
- spaceId,
3872
- spaceName
3873
- } : {},
3874
- ...selectSpace ? {
3875
- selectSpace: true,
3876
- spaces: spaces ?? null
3877
- } : {}
4187
+ ...spaceId ? { spaceId } : {},
4188
+ ...selectSpace ? { selectSpace: true } : {}
3878
4189
  };
4190
+ state.pendingAuth = reserved;
4191
+ reservedAuth = reserved;
4192
+ state.selectedSpaceId = null;
4193
+ state.canChangeSpace = false;
3879
4194
  state.authError = null;
4195
+ const viewerUuid = await getViewerUuid();
4196
+ if (state.pendingAuth !== reserved) return;
4197
+ const spaceLevel = Boolean(spaceId) || selectSpace || scopes.some((scope) => !isUserLevelPermission(scope));
4198
+ const lastPicked = readLastPickedSpace();
4199
+ const contextSpaceId = resolveContextSpaceId();
4200
+ const preListTargets = !spaceLevel ? listGrantedAppSpacesForScopes(viewerUuid, app.id, scopes, app.spaceId) : spaceId === void 0 && !selectSpace && contextSpaceId && hasGrantedAppScopes(viewerUuid, app.id, scopes, contextSpaceId) ? [contextSpaceId] : [];
4201
+ if (!alwaysAsk && await attemptSilentRenewal(reserved, viewerUuid, scopes, preListTargets)) return;
4202
+ if (state.pendingAuth !== reserved) return;
4203
+ let spaces = spaceLevel ? await listViewerSpaces() : void 0;
4204
+ if (state.pendingAuth !== reserved) return;
4205
+ if (spaces && spaces.length === 0) {
4206
+ const fallback = await ensureDefaultSpace();
4207
+ if (state.pendingAuth !== reserved) return;
4208
+ if (fallback) spaces = [fallback];
4209
+ }
4210
+ const candidates = spaces ?? null;
4211
+ const explicitAccessible = Boolean(spaceId) && candidates?.some((space) => space.id === spaceId) === true;
4212
+ const defaultSpaceId = explicitAccessible ? spaceId : resolveDefaultSpaceId(candidates);
4213
+ const silentTargets = (!spaceLevel ? [] : [selectSpace ? lastPicked && candidates?.some((space) => space.id === lastPicked) ? lastPicked : null : explicitAccessible ? spaceId : spaceId === void 0 ? defaultSpaceId : null].filter((target) => Boolean(target) && hasGrantedAppScopes(viewerUuid, app.id, scopes, target ?? void 0))).slice(0, MAX_SILENT_SPACE_ATTEMPTS);
4214
+ if (!alwaysAsk && await attemptSilentRenewal(reserved, viewerUuid, scopes, silentTargets)) return;
4215
+ if (state.pendingAuth !== reserved) return;
4216
+ if (spaceId && candidates && !explicitAccessible) emitDiagnostic({
4217
+ code: "space_inaccessible",
4218
+ message: defaultSpaceId ? "The app requested a Space this viewer cannot access; using a Space the viewer controls instead." : "The app requested a Space this viewer cannot access, and no viewer-controlled Space was available.",
4219
+ detail: { requestedSpaceId: spaceId }
4220
+ });
4221
+ const spaceName = defaultSpaceId ? candidates?.find((space) => space.id === defaultSpaceId)?.name ?? null : null;
4222
+ Object.assign(reserved, {
4223
+ ...explicitAccessible && spaceName ? { spaceName } : {},
4224
+ ...spaces !== void 0 ? { spaces: spaces ?? null } : {},
4225
+ ...defaultSpaceId ? { defaultSpaceId } : {}
4226
+ });
4227
+ state.selectedSpaceId = defaultSpaceId ?? null;
4228
+ state.canChangeSpace = !explicitAccessible && (candidates?.length ?? 0) > 1;
3880
4229
  state.authOpen = true;
3881
4230
  notify();
3882
4231
  }
3883
4232
  } catch (error) {
4233
+ const message = error instanceof Error ? error.message : "Request failed.";
4234
+ if (reservedAuth && state.pendingAuth === reservedAuth) {
4235
+ replyPendingAuth(reservedAuth, {
4236
+ type: "cohub.app.error",
4237
+ message
4238
+ });
4239
+ resetDialogState();
4240
+ safeNotify();
4241
+ return;
4242
+ }
3884
4243
  replyForRequest(data.requestId, {
3885
4244
  type: "cohub.app.error",
3886
- message: error instanceof Error ? error.message : "Request failed."
4245
+ message
3887
4246
  }, true);
3888
4247
  }
3889
4248
  }
@@ -3891,15 +4250,21 @@ function createAppBridgeCore(config) {
3891
4250
  function replyPendingAuth(pending, payload) {
3892
4251
  for (const requestId of [pending.requestId, ...pending.joinedRequestIds ?? []]) replyForRequest(requestId, payload, true);
3893
4252
  }
4253
+ /** Closes the consent dialog and drops its transient selection state. */
4254
+ function resetDialogState() {
4255
+ state.pendingAuth = null;
4256
+ state.authOpen = false;
4257
+ state.authError = null;
4258
+ state.selectedSpaceId = null;
4259
+ state.canChangeSpace = false;
4260
+ }
3894
4261
  function dismissPendingAuth() {
3895
4262
  if (!state.pendingAuth) return;
3896
4263
  replyPendingAuth(state.pendingAuth, {
3897
4264
  type: "cohub.app.authorize.result",
3898
4265
  token: null
3899
4266
  });
3900
- state.pendingAuth = null;
3901
- state.authOpen = false;
3902
- state.authError = null;
4267
+ resetDialogState();
3903
4268
  mintedSpace = null;
3904
4269
  notify();
3905
4270
  }
@@ -3910,6 +4275,17 @@ function createAppBridgeCore(config) {
3910
4275
  state.authSaving = false;
3911
4276
  notify();
3912
4277
  }
4278
+ /**
4279
+ * Only a Space the host actually loaded may become the target, so a UI bug
4280
+ * cannot mint a grant against an arbitrary id.
4281
+ */
4282
+ function setSelectedSpace(spaceId) {
4283
+ if (!state.pendingAuth || state.authSaving || !state.canChangeSpace) return;
4284
+ if (!state.pendingAuth.spaces?.some((space) => space.id === spaceId)) return;
4285
+ if (state.selectedSpaceId === spaceId) return;
4286
+ state.selectedSpaceId = spaceId;
4287
+ notify();
4288
+ }
3913
4289
  async function executePurchase(purchase) {
3914
4290
  const checkout = await createPurchase(purchase.productKey, purchase.purchaseAttemptId);
3915
4291
  if (checkout && typeof checkout === "object") {
@@ -3946,38 +4322,50 @@ function createAppBridgeCore(config) {
3946
4322
  async function confirmAuth(pickedSpaceId) {
3947
4323
  if (!state.pendingAuth || state.authSaving) return;
3948
4324
  const pending = state.pendingAuth;
3949
- if (pending.selectSpace && !pickedSpaceId) {
3950
- state.authError = "Pick a Space to continue.";
3951
- notify();
3952
- return;
4325
+ let requestedSpaceId = (state.canChangeSpace ? pickedSpaceId : void 0) ?? state.selectedSpaceId ?? void 0;
4326
+ if (pending.spaces !== void 0) {
4327
+ const candidates = pending.spaces;
4328
+ if (!(requestedSpaceId !== void 0 && candidates?.some((space) => space.id === requestedSpaceId) === true)) {
4329
+ state.authError = !candidates || candidates.length === 0 ? "Couldn't load your Spaces. Please try again." : "Choose a Space to continue.";
4330
+ notify();
4331
+ return;
4332
+ }
3953
4333
  }
3954
4334
  state.authError = null;
3955
4335
  state.authSaving = true;
3956
4336
  notify();
3957
4337
  try {
3958
- let requestedSpaceId = pending.selectSpace ? pickedSpaceId : pending.spaceId;
3959
- let spaceName = pending.selectSpace ? pending.spaces?.find((space) => space.id === requestedSpaceId)?.name ?? null : pending.spaceName ?? app.spaceName ?? null;
3960
4338
  if (pending.createSpace) {
3961
4339
  mintedSpace ??= await createViewerSpace(pending.createSpace);
3962
4340
  if (!mintedSpace.provisioned) {
3963
4341
  replyPendingAuth(pending, authorizeResult(null, mintedSpace.id, mintedSpace.name ?? pending.createSpace.name));
3964
- state.authOpen = false;
3965
- state.pendingAuth = null;
4342
+ resetDialogState();
3966
4343
  mintedSpace = null;
3967
4344
  return;
3968
4345
  }
3969
4346
  requestedSpaceId = mintedSpace.id;
3970
- spaceName = mintedSpace.name ?? pending.createSpace.name;
3971
4347
  }
3972
4348
  const result = await authorize(pending.scopes, requestedSpaceId);
3973
4349
  setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
3974
- if (pending.selectSpace || pending.createSpace) writeLastPickedSpace(result.spaceId);
4350
+ if (pending.selectSpace || pending.createSpace || pickedSpaceId && pickedSpaceId !== pending.spaceId) writeLastPickedSpace(result.spaceId);
4351
+ let spaceName = null;
4352
+ if (pending.createSpace) spaceName = mintedSpace?.name ?? pending.createSpace.name;
4353
+ else {
4354
+ const listed = pending.spaces?.find((space) => space.id === result.spaceId)?.name;
4355
+ if (listed) spaceName = listed;
4356
+ else if (result.spaceId === pending.spaceId) spaceName = pending.spaceName ?? null;
4357
+ else if (result.spaceId === app.spaceId) spaceName = app.spaceName ?? null;
4358
+ }
3975
4359
  replyPendingAuth(pending, authorizeResult(result.token, result.spaceId, spaceName));
3976
- state.authOpen = false;
3977
- state.pendingAuth = null;
4360
+ resetDialogState();
3978
4361
  mintedSpace = null;
3979
4362
  } catch (error) {
3980
- state.authError = error instanceof Error ? error.message : "Authorization failed.";
4363
+ if (error instanceof AppAuthorizationError) emitDiagnostic({
4364
+ code: error.code ?? "authorize_failed",
4365
+ message: error.message,
4366
+ detail: { scopes: pending.scopes }
4367
+ });
4368
+ state.authError = viewerOpaqueAuthMessage(error) ?? (error instanceof Error ? error.message : "Authorization failed.");
3981
4369
  } finally {
3982
4370
  state.authSaving = false;
3983
4371
  notify();
@@ -3987,6 +4375,7 @@ function createAppBridgeCore(config) {
3987
4375
  getState: () => ({ ...state }),
3988
4376
  handleMessage,
3989
4377
  notifyContextChanged,
4378
+ setSelectedSpace,
3990
4379
  confirmAuth,
3991
4380
  cancelAuth
3992
4381
  };
@@ -4237,4 +4626,4 @@ function createBoardExtensionRegistry() {
4237
4626
  }
4238
4627
  const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
4239
4628
  //#endregion
4240
- export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, setGrantedAppScopes, track };
4629
+ export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectSpacePickerItems, setGrantedAppScopes, track };