@neta-art/cohub 8.0.1 → 8.2.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,4 +1,4 @@
1
- import { $ as BOARD_BUILTIN_EFFECT_KINDS, A as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, B as BoardEffectSchema, C as createSessionPatchReducer, D as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, E as DESKTOP_COMMAND_MAX_TIMEOUT_MS, F as parseDesktopCommandId, G as BoardCompositionSchema, H as BOARD_ANIMATION_CHANNELS, I as BoardAuthoringItemSchema, J as parseBoardCompositionInput, K as BoardProceduralClipSchema, L as BoardItemPatchSchema, M as isDesktopCallMethod, N as isTerminalDesktopCommandStatus, O as DESKTOP_COMMAND_PENDING_TTL_SECONDS, P as parseDesktopCommand, Q as BOARD_BUILTIN_CLIP_KINDS, R as BoardSemanticMutationSchema, S as SessionPatchReducer, T as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, U as BOARD_ANIMATION_CHANNEL_CAPABILITIES, V as parseBoardEffectInput, W as BoardCompositionInputSchema, X as BOARD_ARROW_STROKE_SIZE, Y as BoardConnectionSchema, Z as BOARD_BUILTIN_CAPABILITIES, _ as buildSpaceInvitePath, a as DesktopCommandsApi, at as SkillsApi, b as createSessionGenerationStreamClient, c as ReferralsApi, ct as GenerationsApi, d as TasksApi, et as DEFAULT_BOARD_RENDER_LIMITS, f as BoardClient, g as PublicInviteApi, h as SpacesApi, i as WorkCommerceApi, it as PublicAssetsApi, j as DESKTOP_COMMAND_VERSION, k as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, l as UsersApi, lt as CronJobsApi, m as SpacePublicFilesApi, n as createHttpClient, nt as ReferencesApi, o as UiCommandsApi, ot as PromptsApi, p as SpaceClient, q as BoardTrackSchema, r as AppCommerceApi, rt as SearchApi, s as AppsApi, st as ModelsApi, t as CohubHttpClient, tt as SessionAccessApi, u as UserApi, ut as ChannelsApi, v as buildSpacePath, w as ensureRealtimeConnected, x as parseAssistantMessageCommit, y as SessionGenerationStreamClient, z as BoardEffectInputSchema } from "./chunks/http.js";
1
+ import { $ as BOARD_BUILTIN_CAPABILITIES, A as DESKTOP_COMMAND_PENDING_TTL_SECONDS, B as BoardSemanticMutationSchema, C as parseAssistantMessageCommit, D as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, E as ensureRealtimeConnected, F as isTerminalDesktopCommandStatus, G as BOARD_ANIMATION_CHANNEL_CAPABILITIES, H as BoardEffectSchema, I as parseDesktopCommand, J as BoardProceduralClipSchema, K as BoardCompositionInputSchema, L as parseDesktopCommandId, M as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, N as DESKTOP_COMMAND_VERSION, O as DESKTOP_COMMAND_MAX_TIMEOUT_MS, P as isDesktopCallMethod, Q as BOARD_ARROW_STROKE_SIZE, R as BoardAuthoringItemSchema, S as createSessionGenerationStreamClient, T as createSessionPatchReducer, U as parseBoardEffectInput, V as BoardEffectInputSchema, W as BOARD_ANIMATION_CHANNELS, X as parseBoardCompositionInput, Y as BoardTrackSchema, Z as BoardConnectionSchema, _ as SpacesApi, a as AppCommerceApi, at as SearchApi, b as buildSpacePath, c as UiCommandsApi, ct as PromptsApi, d as UsersApi, dt as CronJobsApi, et as BOARD_BUILTIN_CLIP_KINDS, f as UserApi, ft as ChannelsApi, g as SpacePublicFilesApi, h as SpaceClient, i as scopeListHasPermission, it as ReferencesApi, j as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, k as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, l as AppsApi, lt as ModelsApi, m as BoardClient, n as createHttpClient, nt as DEFAULT_BOARD_RENDER_LIMITS, o as WorkCommerceApi, ot as PublicAssetsApi, p as TasksApi, q as BoardCompositionSchema, r as PERMISSIONS, rt as SessionAccessApi, s as DesktopCommandsApi, st as SkillsApi, t as CohubHttpClient, tt as BOARD_BUILTIN_EFFECT_KINDS, u as ReferralsApi, ut as GenerationsApi, v as PublicInviteApi, w as SessionPatchReducer, x as SessionGenerationStreamClient, y as buildSpaceInvitePath, z as BoardItemPatchSchema } 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
3
  import { a as resolveApiBaseUrl, c as resolveWebsocketUrl, i as normalizeWebsocketUrl, n as normalizeBaseUrl, o as resolveCohubEnvironment, r as normalizeVoiceInputWebsocketUrl, s as resolveVoiceInputWebsocketUrl, t as COHUB_ENVIRONMENTS } 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";
@@ -1150,7 +1150,8 @@ const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
1150
1150
  "user",
1151
1151
  "users",
1152
1152
  "work-auth",
1153
- "app-auth"
1153
+ "app-auth",
1154
+ "zh"
1154
1155
  ]);
1155
1156
  new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
1156
1157
  [...RESERVED_PLATFORM_PATH_SEGMENTS];
@@ -2144,35 +2145,54 @@ var PopupBrokerTransport = class {
2144
2145
  }
2145
2146
  };
2146
2147
  const TOKEN_STORAGE_PREFIX = "cohub:app-token";
2147
- const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:app-auth-scopes";
2148
+ const AUTHORIZED_GRANTS_STORAGE_PREFIX = "cohub:app-auth-grants";
2149
+ /** The space a server response actually granted, when it says so. */
2150
+ const responseSpaceId = (value) => typeof value === "string" && value ? value : void 0;
2151
+ /**
2152
+ * Records one consent, keyed by its space. Newer entries replace older ones
2153
+ * for the same space, so implicit home-space requests (no `spaceId`) and
2154
+ * explicit ones converge onto a single entry once the server echoes its
2155
+ * canonical space id back.
2156
+ */
2157
+ function recordConsent(grants, spaceId, scopes) {
2158
+ return [...(grants ?? []).filter((grant) => grant.spaceId !== spaceId), spaceId ? {
2159
+ spaceId,
2160
+ scopes
2161
+ } : { scopes }];
2162
+ }
2163
+ const isAuthorizedGrant = (value) => {
2164
+ if (!value || typeof value !== "object") return false;
2165
+ const record = value;
2166
+ return (record.spaceId === void 0 || typeof record.spaceId === "string") && Array.isArray(record.scopes) && record.scopes.every((scope) => typeof scope === "string");
2167
+ };
2148
2168
  var AppRuntimeApi = class {
2149
2169
  token = null;
2150
2170
  transport;
2151
2171
  tokenStorageKey;
2152
- scopesStorageKey;
2172
+ grantsStorageKey;
2153
2173
  appIdResolver;
2154
2174
  /** Ensures storage keys are resolved (via slug lookup) at most once. */
2155
2175
  storageKeysReady;
2156
- /** Scopes previously granted via requestAuthorization, retained so token
2157
- * refreshes can re-authorize (preserving viewerScopes) instead of falling
2158
- * back to a base session token that only carries appScopes. */
2159
- authorizedScopes = null;
2176
+ /** Consents previously granted via requestAuthorization, retained so token
2177
+ * refreshes can re-authorize (preserving viewer grants) instead of falling
2178
+ * back to a base session token that only carries app-side scopes. */
2179
+ authorizedGrants = null;
2160
2180
  constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
2161
2181
  this.transport = transport;
2162
2182
  this.appIdResolver = appIdResolver;
2163
2183
  if (appId) {
2164
2184
  this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
2165
- this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${appId}`;
2185
+ this.grantsStorageKey = `${AUTHORIZED_GRANTS_STORAGE_PREFIX}:${appId}`;
2166
2186
  this.storageKeysReady = Promise.resolve();
2167
2187
  this.token = this.readStoredToken();
2168
- this.authorizedScopes = this.readStoredScopes();
2188
+ this.authorizedGrants = this.readStoredGrants();
2169
2189
  } else if (appIdResolver) {
2170
2190
  this.tokenStorageKey = null;
2171
- this.scopesStorageKey = null;
2191
+ this.grantsStorageKey = null;
2172
2192
  this.storageKeysReady = null;
2173
2193
  } else {
2174
2194
  this.tokenStorageKey = null;
2175
- this.scopesStorageKey = null;
2195
+ this.grantsStorageKey = null;
2176
2196
  this.storageKeysReady = Promise.resolve();
2177
2197
  }
2178
2198
  }
@@ -2187,11 +2207,11 @@ var AppRuntimeApi = class {
2187
2207
  const appId = this.appIdResolver ? await this.appIdResolver() : null;
2188
2208
  if (appId) {
2189
2209
  this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
2190
- this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${appId}`;
2210
+ this.grantsStorageKey = `${AUTHORIZED_GRANTS_STORAGE_PREFIX}:${appId}`;
2191
2211
  const stored = this.readStoredToken();
2192
2212
  if (stored && !this.token) this.token = stored;
2193
- const storedScopes = this.readStoredScopes();
2194
- if (storedScopes && !this.authorizedScopes) this.authorizedScopes = storedScopes;
2213
+ const storedGrants = this.readStoredGrants();
2214
+ if (storedGrants && !this.authorizedGrants) this.authorizedGrants = storedGrants;
2195
2215
  }
2196
2216
  })();
2197
2217
  return this.storageKeysReady;
@@ -2211,22 +2231,23 @@ var AppRuntimeApi = class {
2211
2231
  else localStorage.removeItem(this.tokenStorageKey);
2212
2232
  } catch {}
2213
2233
  }
2214
- readStoredScopes() {
2215
- if (!this.scopesStorageKey || typeof localStorage === "undefined") return null;
2234
+ readStoredGrants() {
2235
+ if (!this.grantsStorageKey || typeof localStorage === "undefined") return null;
2216
2236
  try {
2217
- const raw = localStorage.getItem(this.scopesStorageKey);
2237
+ const raw = localStorage.getItem(this.grantsStorageKey);
2218
2238
  if (!raw) return null;
2219
2239
  const parsed = JSON.parse(raw);
2220
- return Array.isArray(parsed) && parsed.every((s) => typeof s === "string") ? parsed : null;
2240
+ if (!Array.isArray(parsed) || !parsed.every(isAuthorizedGrant)) return null;
2241
+ return parsed;
2221
2242
  } catch {
2222
2243
  return null;
2223
2244
  }
2224
2245
  }
2225
- writeStoredScopes(scopes) {
2226
- if (!this.scopesStorageKey || typeof localStorage === "undefined") return;
2246
+ writeStoredGrants(grants) {
2247
+ if (!this.grantsStorageKey || typeof localStorage === "undefined") return;
2227
2248
  try {
2228
- if (scopes && scopes.length > 0) localStorage.setItem(this.scopesStorageKey, JSON.stringify(scopes));
2229
- else localStorage.removeItem(this.scopesStorageKey);
2249
+ if (grants && grants.length > 0) localStorage.setItem(this.grantsStorageKey, JSON.stringify(grants));
2250
+ else localStorage.removeItem(this.grantsStorageKey);
2230
2251
  } catch {}
2231
2252
  }
2232
2253
  async context() {
@@ -2245,14 +2266,29 @@ var AppRuntimeApi = class {
2245
2266
  this.token = null;
2246
2267
  this.writeStoredToken(null);
2247
2268
  }
2248
- if (options?.forceRefresh && this.authorizedScopes && this.authorizedScopes.length > 0) {
2249
- const response = await this.transport.request({
2250
- type: "cohub.app.authorize",
2251
- scopes: this.authorizedScopes
2252
- }, { timeoutMs: 12e4 });
2253
- this.token = response?.token ?? null;
2254
- this.writeStoredToken(this.token);
2255
- return this.token;
2269
+ if (options?.forceRefresh && this.authorizedGrants && this.authorizedGrants.length > 0) {
2270
+ let refreshed = false;
2271
+ let next = [];
2272
+ for (const grant of this.authorizedGrants) try {
2273
+ const response = await this.transport.request({
2274
+ type: "cohub.app.authorize",
2275
+ scopes: grant.scopes,
2276
+ spaceId: grant.spaceId
2277
+ }, { timeoutMs: 12e4 });
2278
+ const token = response?.token ?? null;
2279
+ if (!token) continue;
2280
+ this.token = token;
2281
+ next = recordConsent(next, responseSpaceId(response?.space?.id) ?? grant.spaceId, grant.scopes);
2282
+ refreshed = true;
2283
+ } catch {
2284
+ next = recordConsent(next, grant.spaceId, grant.scopes);
2285
+ }
2286
+ this.authorizedGrants = next;
2287
+ this.writeStoredGrants(next);
2288
+ if (refreshed) {
2289
+ this.writeStoredToken(this.token);
2290
+ return this.token;
2291
+ }
2256
2292
  }
2257
2293
  const response = await this.transport.request({
2258
2294
  type: "cohub.app.token",
@@ -2262,20 +2298,63 @@ var AppRuntimeApi = class {
2262
2298
  this.writeStoredToken(this.token);
2263
2299
  return this.token;
2264
2300
  }
2301
+ /**
2302
+ * Requests viewer consent. Without a `spaceId` the grant targets the app's
2303
+ * home space; with one it targets that space — the app may only grant what
2304
+ * the viewer can already do there themselves. Reuses a previous grant
2305
+ * silently unless `alwaysAsk` forces the consent dialog.
2306
+ */
2265
2307
  async requestAuthorization(input) {
2266
2308
  await this.ensureStorageKeys();
2267
2309
  const response = await this.transport.request({
2268
2310
  type: "cohub.app.authorize",
2269
2311
  scopes: input.scopes,
2270
- reason: input.reason
2312
+ reason: input.reason,
2313
+ spaceId: input.spaceId,
2314
+ alwaysAsk: input.alwaysAsk
2271
2315
  }, { timeoutMs: 12e4 });
2272
- this.token = response?.token ?? null;
2273
- this.writeStoredToken(this.token);
2274
- if (this.token) {
2275
- this.authorizedScopes = input.scopes;
2276
- this.writeStoredScopes(input.scopes);
2316
+ const token = response?.token ?? null;
2317
+ if (token) {
2318
+ this.token = token;
2319
+ this.writeStoredToken(token);
2320
+ const spaceId = responseSpaceId(response?.space?.id) ?? input.spaceId;
2321
+ this.authorizedGrants = recordConsent(this.authorizedGrants, spaceId, input.scopes);
2322
+ this.writeStoredGrants(this.authorizedGrants);
2277
2323
  }
2278
- return Boolean(this.token);
2324
+ return Boolean(token);
2325
+ }
2326
+ /**
2327
+ * Asks the viewer to pick a Space and grant the scopes on it — one consent
2328
+ * dialog covers both. Resolves with the picked space so the app knows where
2329
+ * it may act; `space` is null when the viewer denied.
2330
+ */
2331
+ async requestSpaceAuthorization(input) {
2332
+ await this.ensureStorageKeys();
2333
+ const response = await this.transport.request({
2334
+ type: "cohub.app.authorize",
2335
+ scopes: input.scopes,
2336
+ reason: input.reason,
2337
+ selectSpace: true,
2338
+ alwaysAsk: input.alwaysAsk
2339
+ }, { timeoutMs: 12e4 });
2340
+ const token = response?.token ?? null;
2341
+ const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
2342
+ const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
2343
+ if (token) {
2344
+ this.token = token;
2345
+ this.writeStoredToken(token);
2346
+ }
2347
+ if (token && spaceId) {
2348
+ this.authorizedGrants = recordConsent(this.authorizedGrants, spaceId, input.scopes);
2349
+ this.writeStoredGrants(this.authorizedGrants);
2350
+ }
2351
+ return {
2352
+ granted: Boolean(token),
2353
+ space: spaceId ? {
2354
+ id: spaceId,
2355
+ name: spaceName
2356
+ } : null
2357
+ };
2279
2358
  }
2280
2359
  async purchase(input) {
2281
2360
  const purchaseAttemptId = input.purchaseAttemptId?.trim() || generateRequestId();
@@ -2438,7 +2517,12 @@ var CohubClient = class {
2438
2517
  context() {
2439
2518
  return this.appRuntime.context();
2440
2519
  }
2441
- auth = { request: (input) => this.appRuntime.requestAuthorization(input) };
2520
+ auth = {
2521
+ /** Ensure the app holds these scopes. Silent when a grant already covers them; `alwaysAsk` forces the dialog. */
2522
+ request: (input) => this.appRuntime.requestAuthorization(input),
2523
+ /** One consent: the viewer picks a Space and grants the scopes on it. `alwaysAsk` re-opens the picker. */
2524
+ requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input)
2525
+ };
2442
2526
  app = {
2443
2527
  realtime: null,
2444
2528
  /** Expose callable methods from inside a published app. */
@@ -2634,8 +2718,9 @@ const MAX_AGE_MS = 336 * 60 * 60 * 1e3;
2634
2718
  function isBrowser() {
2635
2719
  return typeof localStorage !== "undefined";
2636
2720
  }
2637
- function storageKey(userUuid, appId) {
2638
- return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}:v${CACHE_VERSION}`;
2721
+ function storageKey(userUuid, appId, spaceId) {
2722
+ const base = `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}`;
2723
+ return spaceId ? `${base}:${encodeURIComponent(spaceId)}:v${CACHE_VERSION}` : `${base}:v${CACHE_VERSION}`;
2639
2724
  }
2640
2725
  function userPrefix(userUuid) {
2641
2726
  return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:`;
@@ -2643,19 +2728,19 @@ function userPrefix(userUuid) {
2643
2728
  function isPermissionArray(value) {
2644
2729
  return Array.isArray(value) && value.every((v) => typeof v === "string");
2645
2730
  }
2646
- function isCachedWorkGrant(value) {
2731
+ function isCachedAppGrant(value) {
2647
2732
  if (!value || typeof value !== "object") return false;
2648
2733
  const record = value;
2649
2734
  return record.version === CACHE_VERSION && typeof record.userUuid === "string" && typeof record.appId === "string" && isPermissionArray(record.scopes) && typeof record.updatedAt === "number";
2650
2735
  }
2651
- function readEntry(userUuid, appId) {
2736
+ function readEntry(userUuid, appId, spaceId) {
2652
2737
  if (!isBrowser()) return null;
2653
- const key = storageKey(userUuid, appId);
2738
+ const key = storageKey(userUuid, appId, spaceId);
2654
2739
  try {
2655
2740
  const raw = localStorage.getItem(key);
2656
2741
  if (!raw) return null;
2657
2742
  const parsed = JSON.parse(raw);
2658
- if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId) {
2743
+ if (!isCachedAppGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId) {
2659
2744
  localStorage.removeItem(key);
2660
2745
  return null;
2661
2746
  }
@@ -2673,44 +2758,86 @@ function readEntry(userUuid, appId) {
2673
2758
  }
2674
2759
  /**
2675
2760
  * Returns true when the viewer has previously granted every requested scope
2676
- * for this work, allowing a silent re-authorization.
2761
+ * for this app on the target space, allowing a silent re-authorization.
2677
2762
  */
2678
- function hasGrantedAppScopes(userUuid, appId, scopes) {
2763
+ function hasGrantedAppScopes(userUuid, appId, scopes, spaceId) {
2679
2764
  if (!userUuid || !appId || scopes.length === 0) return false;
2680
- const entry = readEntry(userUuid, appId);
2765
+ const entry = readEntry(userUuid, appId, spaceId);
2681
2766
  if (!entry) return false;
2682
- const granted = new Set(entry.scopes);
2683
- return scopes.every((scope) => granted.has(scope));
2767
+ return scopes.every((scope) => scopeListHasPermission(entry.scopes, scope));
2684
2768
  }
2685
2769
  /**
2686
- * Records the granted scopes for a work, merged with any previously granted
2687
- * scopes so a growing permission set stays covered.
2770
+ * Lists every cached grant for an app one entry per space — so hosts can
2771
+ * report what the viewer previously consented to without a server round trip.
2772
+ * Entries past their re-consent window are dropped on read.
2688
2773
  */
2689
- function setGrantedAppScopes(userUuid, appId, scopes) {
2774
+ function listGrantedAppScopes(userUuid, appId, homeSpaceId) {
2775
+ if (!userUuid || !appId || !isBrowser()) return [];
2776
+ const prefix = `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}:`;
2777
+ const grants = [];
2778
+ try {
2779
+ for (let i = 0; i < localStorage.length; i += 1) {
2780
+ const key = localStorage.key(i);
2781
+ if (!key?.startsWith(prefix)) continue;
2782
+ const spaceId = key.slice(prefix.length).match(/^(?:(.*):)?v\d+$/)?.[1] || homeSpaceId;
2783
+ if (!spaceId) continue;
2784
+ const raw = localStorage.getItem(key);
2785
+ if (!raw) continue;
2786
+ const parsed = JSON.parse(raw);
2787
+ if (!isCachedAppGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId || Date.now() - parsed.updatedAt > MAX_AGE_MS) continue;
2788
+ grants.push({
2789
+ spaceId: decodeURIComponent(spaceId),
2790
+ scopes: parsed.scopes
2791
+ });
2792
+ }
2793
+ } catch {}
2794
+ return grants;
2795
+ }
2796
+ /**
2797
+ * Records the granted scopes for an app on a space. Replaces any previously
2798
+ * cached scopes so the cache mirrors the server row exactly — an explicit
2799
+ * consent with fewer scopes must narrow the cache too, or silent reuse could
2800
+ * hand back permissions the viewer just removed.
2801
+ */
2802
+ function setGrantedAppScopes(userUuid, appId, scopes, spaceId) {
2690
2803
  if (!userUuid || !appId || scopes.length === 0) return;
2691
- const existing = readEntry(userUuid, appId);
2692
- const merged = Array.from(/* @__PURE__ */ new Set([...existing?.scopes ?? [], ...scopes]));
2693
2804
  const entry = {
2694
2805
  version: CACHE_VERSION,
2695
2806
  userUuid,
2696
2807
  appId,
2697
- scopes: merged,
2808
+ scopes: Array.from(new Set(scopes)),
2698
2809
  updatedAt: Date.now()
2699
2810
  };
2700
2811
  if (!isBrowser()) return;
2701
2812
  try {
2702
- localStorage.setItem(storageKey(userUuid, appId), JSON.stringify(entry));
2813
+ localStorage.setItem(storageKey(userUuid, appId, spaceId), JSON.stringify(entry));
2814
+ } catch {}
2815
+ }
2816
+ /**
2817
+ * Synchronizes a cached grant with the server while preserving the last
2818
+ * explicit-consent timestamp. Also migrates legacy keys to the canonical Space.
2819
+ */
2820
+ function syncGrantedAppScopes(userUuid, appId, fromSpaceId, toSpaceId, scopes) {
2821
+ if (!userUuid || !appId || !toSpaceId || scopes.length === 0 || !isBrowser()) return;
2822
+ const entry = readEntry(userUuid, appId, toSpaceId) ?? readEntry(userUuid, appId, fromSpaceId);
2823
+ if (!entry) return;
2824
+ try {
2825
+ localStorage.setItem(storageKey(userUuid, appId, toSpaceId), JSON.stringify({
2826
+ ...entry,
2827
+ scopes: Array.from(new Set(scopes))
2828
+ }));
2829
+ if (fromSpaceId !== toSpaceId) localStorage.removeItem(storageKey(userUuid, appId, fromSpaceId));
2703
2830
  } catch {}
2704
2831
  }
2705
2832
  /**
2706
- * Clears cached grants. Pass a appId to clear a single work, or omit it to
2707
- * clear every cached grant for the user (used on sign-out).
2833
+ * Clears cached grants. Pass an appId to clear one app (optionally one
2834
+ * space), or omit it to clear every cached grant for the user (sign-out).
2708
2835
  */
2709
- function clearGrantedAppScopes(userUuid, appId) {
2836
+ function clearGrantedAppScopes(userUuid, appId, spaceId) {
2710
2837
  if (!isBrowser() || !userUuid) return;
2711
2838
  try {
2712
2839
  if (appId) {
2713
- localStorage.removeItem(storageKey(userUuid, appId));
2840
+ localStorage.removeItem(storageKey(userUuid, appId, spaceId));
2714
2841
  return;
2715
2842
  }
2716
2843
  const prefix = userPrefix(userUuid);
@@ -2731,6 +2858,42 @@ function clonePermissionScopes(scopes) {
2731
2858
  return Array.from(scopes ?? []).filter((scope) => typeof scope === "string");
2732
2859
  }
2733
2860
  /**
2861
+ * Scopes arriving over postMessage are untrusted: keep only known permission
2862
+ * names (in first-seen order, deduplicated) so a malicious app cannot push
2863
+ * arbitrary strings, duplicates, or oversized arrays into the consent dialog.
2864
+ */
2865
+ function sanitizeRequestedScopes(value) {
2866
+ if (!Array.isArray(value)) return [];
2867
+ const known = new Set(PERMISSIONS);
2868
+ const seen = /* @__PURE__ */ new Set();
2869
+ for (const scope of value) if (typeof scope === "string" && known.has(scope)) seen.add(scope);
2870
+ return Array.from(seen);
2871
+ }
2872
+ /** Consent dialogs render the reason; keep hostile input bounded. */
2873
+ const MAX_REASON_LENGTH = 280;
2874
+ var AppAuthorizationError = class extends Error {
2875
+ status;
2876
+ constructor(message, status) {
2877
+ super(message);
2878
+ this.status = status;
2879
+ this.name = "AppAuthorizationError";
2880
+ }
2881
+ };
2882
+ const isDefinitiveAuthorizationFailure = (error) => error instanceof AppAuthorizationError && [
2883
+ 401,
2884
+ 403,
2885
+ 404
2886
+ ].includes(error.status);
2887
+ function sanitizeReason(value) {
2888
+ if (typeof value !== "string") return void 0;
2889
+ const trimmed = value.trim();
2890
+ if (!trimmed) return void 0;
2891
+ return trimmed.length > MAX_REASON_LENGTH ? trimmed.slice(0, MAX_REASON_LENGTH) : trimmed;
2892
+ }
2893
+ function normalizePermissionScopes(scopes) {
2894
+ return Array.from(new Set(clonePermissionScopes(scopes)));
2895
+ }
2896
+ /**
2734
2897
  * Framework-agnostic app bridge host core — message handling, app session
2735
2898
  * token minting, authorization (with silent re-grant cache), and
2736
2899
  * purchase/checkout flow — without any rendering or reactive primitives.
@@ -2745,6 +2908,8 @@ function createAppBridgeCore(config) {
2745
2908
  const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
2746
2909
  const onStateChange = config.onStateChange;
2747
2910
  let appToken = null;
2911
+ /** Consents made through this host, keyed by target space. */
2912
+ const sessionViewerGrants = /* @__PURE__ */ new Map();
2748
2913
  let activeInvocation;
2749
2914
  let contextChangeVersion = 0;
2750
2915
  const legacyRequestIds = /* @__PURE__ */ new Set();
@@ -2752,6 +2917,7 @@ function createAppBridgeCore(config) {
2752
2917
  const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
2753
2918
  const appScopes = clonePermissionScopes(app.appScopes);
2754
2919
  const viewerUuid = await getViewerUuid();
2920
+ const viewerGrants = mergeViewerGrants(viewerUuid, Array.from(sessionViewerGrants.values()));
2755
2921
  return {
2756
2922
  app: {
2757
2923
  id: app.id,
@@ -2762,9 +2928,24 @@ function createAppBridgeCore(config) {
2762
2928
  viewer: viewerUuid ? { userUuid: viewerUuid } : null,
2763
2929
  ...invocation ? { invocation: { ...invocation } } : {},
2764
2930
  permissions: {
2765
- scopes: appScopes,
2931
+ scopes: normalizePermissionScopes([...appScopes, ...viewerGrants.flatMap((grant) => grant.scopes)]),
2766
2932
  appScopes,
2767
- viewerScopes: []
2933
+ viewerScopes: normalizePermissionScopes(viewerGrants.flatMap((grant) => grant.scopes)),
2934
+ viewerGrants
2935
+ }
2936
+ };
2937
+ }
2938
+ /** Builds an authorize reply; the target space rides along for the app. */
2939
+ function authorizeResult(token, spaceId, spaceName) {
2940
+ return {
2941
+ type: "cohub.app.authorize.result",
2942
+ token,
2943
+ space: spaceId ? {
2944
+ id: spaceId,
2945
+ name: spaceName
2946
+ } : {
2947
+ id: app.spaceId,
2948
+ name: app.spaceName ?? null
2768
2949
  }
2769
2950
  };
2770
2951
  }
@@ -2821,6 +3002,12 @@ function createAppBridgeCore(config) {
2821
3002
  const viewerUuid = await getViewerUuid();
2822
3003
  return Boolean(viewerUuid && viewerUuid === app.userUuid);
2823
3004
  }
3005
+ function mergeViewerGrants(viewerUuid, sessionGrants) {
3006
+ const bySpace = /* @__PURE__ */ new Map();
3007
+ for (const grant of listGrantedAppScopes(viewerUuid, app.id, app.spaceId)) bySpace.set(grant.spaceId, grant);
3008
+ for (const grant of sessionGrants) bySpace.set(grant.spaceId, grant);
3009
+ return Array.from(bySpace.values());
3010
+ }
2824
3011
  function allowsOwnerAutoAuthorization() {
2825
3012
  return authorizationContext.surface === "background" || authorizationContext.surface === "app";
2826
3013
  }
@@ -2841,11 +3028,17 @@ function createAppBridgeCore(config) {
2841
3028
  appToken = token;
2842
3029
  return appToken;
2843
3030
  }
2844
- async function authorize(scopes) {
3031
+ /**
3032
+ * Calls the authorize endpoint. `silent` marks a background refresh of a
3033
+ * previous consent: the server then only renews a live grant and never
3034
+ * creates or revives one, so a revoked grant cannot come back without a
3035
+ * fresh dialog.
3036
+ */
3037
+ async function authorize(scopes, spaceId, options) {
2845
3038
  const userToken = await getAccessToken();
2846
3039
  if (!userToken) {
2847
3040
  await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
2848
- return null;
3041
+ throw new Error("Sign in is required to authorize this app.");
2849
3042
  }
2850
3043
  const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
2851
3044
  method: "POST",
@@ -2853,13 +3046,84 @@ function createAppBridgeCore(config) {
2853
3046
  Authorization: `Bearer ${userToken}`,
2854
3047
  "Content-Type": "application/json"
2855
3048
  },
2856
- body: JSON.stringify({ scopes })
3049
+ body: JSON.stringify({
3050
+ scopes,
3051
+ ...spaceId ? { spaceId } : {},
3052
+ ...options?.silent ? { silent: true } : {}
3053
+ })
2857
3054
  });
2858
- if (!response.ok) throw new Error((await response.json().catch(() => null))?.message ?? "Authorization failed.");
2859
- const token = readTokenResponse(await response.json());
3055
+ const payload = await response.json().catch(() => null);
3056
+ if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status);
3057
+ const token = readTokenResponse(payload);
2860
3058
  if (!token) throw new Error("Invalid app authorization response.");
3059
+ const canonicalSpaceId = typeof payload?.grant?.spaceId === "string" && payload.grant.spaceId ? payload.grant.spaceId : spaceId ?? app.spaceId;
3060
+ const grantedScopes = sanitizeRequestedScopes(payload?.grant?.scopes);
3061
+ const canonicalScopes = grantedScopes.length > 0 ? grantedScopes : clonePermissionScopes(scopes);
2861
3062
  appToken = token;
2862
- return appToken;
3063
+ sessionViewerGrants.set(canonicalSpaceId, {
3064
+ spaceId: canonicalSpaceId,
3065
+ scopes: canonicalScopes
3066
+ });
3067
+ syncGrantedAppScopes(await getViewerUuid(), app.id, spaceId, canonicalSpaceId, canonicalScopes);
3068
+ return {
3069
+ token,
3070
+ spaceId: canonicalSpaceId,
3071
+ scopes: canonicalScopes
3072
+ };
3073
+ }
3074
+ /**
3075
+ * Lists the viewer's spaces for the consent dialog picker. Fetched by the
3076
+ * host with the viewer's own token — the app only ever learns the space
3077
+ * the viewer picks, never the list.
3078
+ */
3079
+ async function listViewerSpaces() {
3080
+ const userToken = await getAccessToken();
3081
+ if (!userToken) return null;
3082
+ try {
3083
+ const response = await fetch(`${apiOrigin}/api/spaces`, { headers: { Authorization: `Bearer ${userToken}` } });
3084
+ if (!response.ok) return null;
3085
+ const spaces = await response.json();
3086
+ if (!Array.isArray(spaces)) return null;
3087
+ return spaces.flatMap((space) => {
3088
+ if (typeof space.id !== "string" || !space.id) return [];
3089
+ return [{
3090
+ id: space.id,
3091
+ name: typeof space.name === "string" && space.name ? space.name : null
3092
+ }];
3093
+ });
3094
+ } catch {
3095
+ return null;
3096
+ }
3097
+ }
3098
+ const pickedSpaceStorageKey = `cohub:app-picked-space:${app.id}`;
3099
+ function readLastPickedSpace() {
3100
+ try {
3101
+ return localStorage.getItem(pickedSpaceStorageKey);
3102
+ } catch {
3103
+ return null;
3104
+ }
3105
+ }
3106
+ function writeLastPickedSpace(spaceId) {
3107
+ try {
3108
+ localStorage.setItem(pickedSpaceStorageKey, spaceId);
3109
+ } catch {}
3110
+ }
3111
+ /**
3112
+ * Resolves the target space's name for the consent dialog. The host — not
3113
+ * the app — resolves it, so the dialog cannot be tricked into labeling a
3114
+ * grant with the wrong space.
3115
+ */
3116
+ async function resolveSpaceName(spaceId) {
3117
+ const userToken = await getAccessToken();
3118
+ if (!userToken) return null;
3119
+ try {
3120
+ const response = await fetch(`${apiOrigin}/api/spaces/${encodeURIComponent(spaceId)}`, { headers: { Authorization: `Bearer ${userToken}` } });
3121
+ if (!response.ok) return null;
3122
+ const space = await response.json();
3123
+ return typeof space.name === "string" && space.name ? space.name : null;
3124
+ } catch {
3125
+ return null;
3126
+ }
2863
3127
  }
2864
3128
  function writePendingPurchase(input) {
2865
3129
  if (typeof sessionStorage === "undefined") return;
@@ -2972,38 +3236,60 @@ function createAppBridgeCore(config) {
2972
3236
  config.onPurchaseRequested?.({ ...state.pendingPurchase });
2973
3237
  }
2974
3238
  if (data.type === "cohub.app.authorize" || data.type === "cohub.work.authorize") {
2975
- const allowedViewerScopes = clonePermissionScopes(app.allowedViewerScopes);
2976
- const scopes = clonePermissionScopes(data.scopes).filter((scope) => allowedViewerScopes.includes(scope));
3239
+ const scopes = sanitizeRequestedScopes(data.scopes);
3240
+ const spaceId = typeof data.spaceId === "string" && data.spaceId ? data.spaceId : void 0;
3241
+ const selectSpace = data.selectSpace === true && !spaceId;
3242
+ const alwaysAsk = data.alwaysAsk === true;
2977
3243
  if (scopes.length === 0) {
2978
3244
  replyForRequest(data.requestId, {
2979
3245
  type: "cohub.app.error",
2980
- message: "No allowed scopes requested."
3246
+ message: "No scopes requested."
2981
3247
  }, true);
2982
3248
  return;
2983
3249
  }
2984
- if (allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) {
2985
- const token = await authorize(scopes);
2986
- replyForRequest(data.requestId, {
2987
- type: "cohub.app.authorize.result",
2988
- token
2989
- }, true);
3250
+ if (!selectSpace && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
3251
+ const result = await authorize(scopes, spaceId, { silent: true });
3252
+ replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, result.spaceId === app.spaceId ? app.spaceName ?? null : null), true);
2990
3253
  return;
3254
+ } catch (error) {
3255
+ if (!isDefinitiveAuthorizationFailure(error)) {
3256
+ replyForRequest(data.requestId, {
3257
+ type: "cohub.app.error",
3258
+ message: error instanceof Error ? error.message : "Authorization failed."
3259
+ }, true);
3260
+ return;
3261
+ }
2991
3262
  }
2992
3263
  const viewerUuid = await getViewerUuid();
2993
- if (viewerUuid && hasGrantedAppScopes(viewerUuid, app.id, scopes)) try {
2994
- const token = await authorize(scopes);
2995
- replyForRequest(data.requestId, {
2996
- type: "cohub.app.authorize.result",
2997
- token
2998
- }, true);
3264
+ const silentSpaceId = selectSpace ? readLastPickedSpace() : spaceId;
3265
+ if (!alwaysAsk && viewerUuid && (selectSpace ? Boolean(silentSpaceId) : true) && silentSpaceId !== null && hasGrantedAppScopes(viewerUuid, app.id, scopes, silentSpaceId ?? void 0)) try {
3266
+ const result = await authorize(scopes, silentSpaceId, { silent: true });
3267
+ replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, null), true);
2999
3268
  return;
3000
- } catch {
3001
- clearGrantedAppScopes(viewerUuid, app.id);
3269
+ } catch (error) {
3270
+ if (!isDefinitiveAuthorizationFailure(error)) {
3271
+ replyForRequest(data.requestId, {
3272
+ type: "cohub.app.error",
3273
+ message: error instanceof Error ? error.message : "Authorization failed."
3274
+ }, true);
3275
+ return;
3276
+ }
3277
+ clearGrantedAppScopes(viewerUuid, app.id, silentSpaceId);
3002
3278
  }
3279
+ const [spaceName, spaces] = await Promise.all([spaceId ? resolveSpaceName(spaceId) : Promise.resolve(null), selectSpace ? listViewerSpaces() : Promise.resolve(void 0)]);
3003
3280
  state.pendingAuth = {
3004
3281
  requestId: data.requestId,
3005
3282
  scopes,
3006
- reason: data.reason
3283
+ reason: sanitizeReason(data.reason),
3284
+ homeSpaceName: app.spaceName ?? null,
3285
+ ...spaceId ? {
3286
+ spaceId,
3287
+ spaceName
3288
+ } : {},
3289
+ ...selectSpace ? {
3290
+ selectSpace: true,
3291
+ spaces: spaces ?? null
3292
+ } : {}
3007
3293
  };
3008
3294
  state.authError = null;
3009
3295
  state.authOpen = true;
@@ -3078,18 +3364,24 @@ function createAppBridgeCore(config) {
3078
3364
  notify();
3079
3365
  }
3080
3366
  }
3081
- async function confirmAuth() {
3367
+ async function confirmAuth(pickedSpaceId) {
3082
3368
  if (!state.pendingAuth || state.authSaving) return;
3369
+ const pending = state.pendingAuth;
3370
+ if (pending.selectSpace && !pickedSpaceId) {
3371
+ state.authError = "Pick a Space to continue.";
3372
+ notify();
3373
+ return;
3374
+ }
3083
3375
  state.authError = null;
3084
3376
  state.authSaving = true;
3085
3377
  notify();
3086
3378
  try {
3087
- const token = await authorize(state.pendingAuth.scopes);
3088
- setGrantedAppScopes(await getViewerUuid(), app.id, state.pendingAuth.scopes);
3089
- replyForRequest(state.pendingAuth.requestId, {
3090
- type: "cohub.app.authorize.result",
3091
- token
3092
- }, true);
3379
+ const requestedSpaceId = pending.selectSpace ? pickedSpaceId : pending.spaceId;
3380
+ const result = await authorize(pending.scopes, requestedSpaceId);
3381
+ setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
3382
+ if (pending.selectSpace) writeLastPickedSpace(result.spaceId);
3383
+ const spaceName = pending.selectSpace ? pending.spaces?.find((space) => space.id === result.spaceId)?.name ?? null : pending.spaceName ?? app.spaceName ?? null;
3384
+ replyForRequest(pending.requestId, authorizeResult(result.token, result.spaceId, spaceName), true);
3093
3385
  state.authOpen = false;
3094
3386
  state.pendingAuth = null;
3095
3387
  } catch (error) {
@@ -3343,4 +3635,4 @@ function createBoardExtensionRegistry() {
3343
3635
  }
3344
3636
  const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
3345
3637
  //#endregion
3346
- 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, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, 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, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, 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, 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, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, setGrantedAppScopes, track };
3638
+ 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, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, 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, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, 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, 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, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, setGrantedAppScopes, track };