@neta-art/cohub 8.22.0 → 8.24.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 BoardEffectInputSchema, A as SkillsApi, B as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, C as SessionPatchReducer, D as ReferencesApi, E as SessionAccessApi, F as ChannelsApi, G as parseDesktopCommand, H as DESKTOP_COMMAND_VERSION, I as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, J as NAVIGATION_ERROR_MESSAGE_MAX_LENGTH, K as parseDesktopCommandId, L as DESKTOP_COMMAND_MAX_TIMEOUT_MS, M as ModelsApi, N as GenerationsApi, O as SearchApi, P as CronJobsApi, Q as BoardSemanticMutationSchema, R as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, S as parseAssistantMessageCommit, T as ensureRealtimeConnected, U as isDesktopCallMethod, V as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, W as isTerminalDesktopCommandStatus, X as BoardItemPatchSchema, Y as BoardAuthoringItemSchema, Z as BoardSemanticCommandSchema, _ as PublicInviteApi, a as DesktopCommandsApi, at as BoardCompositionSchema, b as SessionGenerationStreamClient, c as ReferralsApi, ct as parseBoardCompositionInput, d as TasksApi, dt as BOARD_BUILTIN_CAPABILITIES, et as BoardEffectSchema, f as BoardClient, ft as BOARD_BUILTIN_CLIP_KINDS, g as SpacesApi, h as SpaceWebhooksApi, i as WorkCommerceApi, it as BoardCompositionInputSchema, j as PromptsApi, k as PublicAssetsApi, l as UsersApi, lt as BoardConnectionSchema, m as SpacePublicFilesApi, mt as DEFAULT_BOARD_RENDER_LIMITS, n as createHttpClient, nt as BOARD_ANIMATION_CHANNELS, o as UiCommandsApi, ot as BoardProceduralClipSchema, p as SpaceClient, pt as BOARD_BUILTIN_EFFECT_KINDS, q as resolveOpenSurface, r as AppCommerceApi, rt as BOARD_ANIMATION_CHANNEL_CAPABILITIES, s as AppsApi, st as BoardTrackSchema, t as CohubHttpClient, tt as parseBoardEffectInput, u as UserApi, ut as BOARD_ARROW_STROKE_SIZE, v as buildSpaceInvitePath, w as createSessionPatchReducer, x as createSessionGenerationStreamClient, y as buildSpacePath, z as DESKTOP_COMMAND_PENDING_TTL_SECONDS } from "./chunks/http.js";
1
+ import { $ as BoardItemPatchSchema, A as SkillsApi, B as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, C as SessionPatchReducer, D as ReferencesApi, E as SessionAccessApi, F as ChannelsApi, G as defaultDesktopCommandTimeoutMs, H as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, I as DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS, J as parseDesktopCommand, K as isDesktopCallMethod, L as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, M as ModelsApi, N as GenerationsApi, O as SearchApi, P as CronJobsApi, Q as BoardAuthoringItemSchema, R as DESKTOP_COMMAND_MAX_TIMEOUT_MS, S as parseAssistantMessageCommit, T as ensureRealtimeConnected, U as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, V as DESKTOP_COMMAND_PENDING_TTL_SECONDS, W as DESKTOP_COMMAND_VERSION, X as resolveOpenSurface, Y as parseDesktopCommandId, Z as NAVIGATION_ERROR_MESSAGE_MAX_LENGTH, _ as PublicInviteApi, _t as DEFAULT_BOARD_RENDER_LIMITS, a as DesktopCommandsApi, at as BOARD_ANIMATION_CHANNELS, b as SessionGenerationStreamClient, c as ReferralsApi, ct as BoardCompositionSchema, d as TasksApi, dt as parseBoardCompositionInput, et as BoardSemanticCommandSchema, f as BoardClient, ft as BoardConnectionSchema, g as SpacesApi, gt as BOARD_BUILTIN_EFFECT_KINDS, h as SpaceWebhooksApi, ht as BOARD_BUILTIN_CLIP_KINDS, i as WorkCommerceApi, it as parseBoardEffectInput, j as PromptsApi, k as PublicAssetsApi, l as UsersApi, lt as BoardProceduralClipSchema, m as SpacePublicFilesApi, mt as BOARD_BUILTIN_CAPABILITIES, n as createHttpClient, nt as BoardEffectInputSchema, o as UiCommandsApi, ot as BOARD_ANIMATION_CHANNEL_CAPABILITIES, p as SpaceClient, pt as BOARD_ARROW_STROKE_SIZE, q as isTerminalDesktopCommandStatus, r as AppCommerceApi, rt as BoardEffectSchema, s as AppsApi, st as BoardCompositionInputSchema, t as CohubHttpClient, tt as BoardSemanticMutationSchema, u as UserApi, ut as BoardTrackSchema, v as buildSpaceInvitePath, w as createSessionPatchReducer, x as createSessionGenerationStreamClient, y as buildSpacePath, z as DESKTOP_COMMAND_OPEN_TIMEOUT_MS } 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 normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebBaseUrl, f as resolveWebsocketUrl, g as scopeListHasPermission, h as isUserLevelPermission, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, m as USER_LEVEL_PERMISSIONS, n as getCohubContext, o as resolveApiBaseUrl, p as 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";
@@ -14,15 +14,28 @@ const appAuthorizationTargetSchema = z.discriminatedUnion("kind", [
14
14
  }),
15
15
  z.object({ kind: z.literal("pick-space") })
16
16
  ]);
17
- const APP_SILENT_SHELL_SCOPE_SET = /* @__PURE__ */ new Set([
17
+ /**
18
+ * Read-only scopes the Shell may renew without a dialog for any App on the
19
+ * Space it shows. The API still checks the viewer's own permissions.
20
+ */
21
+ const APP_SILENT_SHELL_SCOPES = [
18
22
  "space.view",
19
23
  "file.view",
20
24
  "file.view.filtered",
21
25
  "session.view",
22
26
  "taskrun.view",
23
27
  "checkpoint.view"
24
- ]);
28
+ ];
29
+ const APP_SILENT_SHELL_SCOPE_SET = new Set(APP_SILENT_SHELL_SCOPES);
25
30
  const isAppSilentShellScope = (scope) => APP_SILENT_SHELL_SCOPE_SET.has(scope);
31
+ /**
32
+ * The API-enforced ceiling for Host consent (`consent: "host"`), given to Apps
33
+ * the Shell Space published or installed. Trust is decided by the Shell, the
34
+ * only holder of the viewer's token.
35
+ */
36
+ const APP_HOST_CONSENT_SCOPES = [...APP_SILENT_SHELL_SCOPES, "file.edit"];
37
+ const APP_HOST_CONSENT_SCOPE_SET = new Set(APP_HOST_CONSENT_SCOPES);
38
+ const isAppHostConsentScope = (scope) => APP_HOST_CONSENT_SCOPE_SET.has(scope);
26
39
  const appAuthorizationRequestSchema = z.object({
27
40
  target: appAuthorizationTargetSchema,
28
41
  scopes: z.array(z.string().min(1)).min(1).max(128),
@@ -281,8 +294,8 @@ const BUILTIN_VERSIONS = new Set(BOARD_BUILTIN_CAPABILITIES.map((c) => `${c.kind
281
294
  function isBuiltinBoardCapability(kind, id, version) {
282
295
  return BUILTIN_VERSIONS.has(`${kind}:${id}@${version}`);
283
296
  }
284
- const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
285
- const finite = (value) => typeof value === "number" && Number.isFinite(value);
297
+ const record$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
298
+ const finite$1 = (value) => typeof value === "number" && Number.isFinite(value);
286
299
  function validateBuiltinBoardClip(clip, path = "clip") {
287
300
  if (!CLIPS.has(clip.kind)) return [];
288
301
  const errors = [];
@@ -300,13 +313,13 @@ function validateBuiltinBoardClip(clip, path = "clip") {
300
313
  if (clip.kind === "motion.path") {
301
314
  const points = clip.params.points;
302
315
  if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) error("motion path must contain 2 to 10000 world-offset points", "params.points", "world-offset");
303
- else for (const [index, point] of points.entries()) if (!record(point) || !finite(point.x) || !finite(point.y)) error("motion path point must contain finite x and y", `params.points.${index}`, "world-offset");
316
+ else for (const [index, point] of points.entries()) if (!record$1(point) || !finite$1(point.x) || !finite$1(point.y)) error("motion path point must contain finite x and y", `params.points.${index}`, "world-offset");
304
317
  }
305
318
  if (clip.kind === "effects.particles") {
306
319
  const count = clip.params.count;
307
320
  if (!Number.isSafeInteger(count) || count < 1 || count > DEFAULT_BOARD_RENDER_LIMITS.particles) error(`particle count must be an integer from 1 to ${DEFAULT_BOARD_RENDER_LIMITS.particles}`, "params.count", void 0, "INVALID_PARTICLE_COUNT");
308
321
  const bounds = clip.params.bounds;
309
- if (!record(bounds) || !finite(bounds.x) || !finite(bounds.y) || !finite(bounds.width) || !finite(bounds.height) || bounds.width <= 0 || bounds.height <= 0) error("particles require positive finite world bounds", "params.bounds", "world", "PARTICLE_BOUNDS_REQUIRED");
322
+ if (!record$1(bounds) || !finite$1(bounds.x) || !finite$1(bounds.y) || !finite$1(bounds.width) || !finite$1(bounds.height) || bounds.width <= 0 || bounds.height <= 0) error("particles require positive finite world bounds", "params.bounds", "world", "PARTICLE_BOUNDS_REQUIRED");
310
323
  }
311
324
  if (clip.kind === "camera.focus") {
312
325
  const parsed = BoardCameraFocusParamsSchema.safeParse(clip.params);
@@ -1036,14 +1049,14 @@ var GenerationPolicyError = class extends Error {
1036
1049
  this.name = "GenerationPolicyError";
1037
1050
  }
1038
1051
  };
1039
- function isRecord$4(value) {
1052
+ function isRecord$5(value) {
1040
1053
  return !!value && typeof value === "object" && !Array.isArray(value);
1041
1054
  }
1042
1055
  function isPrimitiveEnumValue(value) {
1043
1056
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1044
1057
  }
1045
1058
  function normalizeConstraint(value) {
1046
- if (!isRecord$4(value) || typeof value.kind !== "string") return null;
1059
+ if (!isRecord$5(value) || typeof value.kind !== "string") return null;
1047
1060
  if (value.kind === "enum") {
1048
1061
  if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
1049
1062
  return {
@@ -1073,7 +1086,7 @@ function normalizeConstraint(value) {
1073
1086
  return null;
1074
1087
  }
1075
1088
  function normalizeGenerationPolicy(value) {
1076
- if (!isRecord$4(value) || value.version !== 1) return null;
1089
+ if (!isRecord$5(value) || value.version !== 1) return null;
1077
1090
  if (value.mode === "auto") return {
1078
1091
  version: 1,
1079
1092
  mode: "auto"
@@ -1081,10 +1094,10 @@ function normalizeGenerationPolicy(value) {
1081
1094
  if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
1082
1095
  const models = [];
1083
1096
  for (const item of value.models) {
1084
- if (!isRecord$4(item) || typeof item.model !== "string" || !item.model.trim()) return null;
1097
+ if (!isRecord$5(item) || typeof item.model !== "string" || !item.model.trim()) return null;
1085
1098
  const modelPolicy = { model: item.model.trim() };
1086
1099
  if (item.parameters !== void 0) {
1087
- if (!isRecord$4(item.parameters)) return null;
1100
+ if (!isRecord$5(item.parameters)) return null;
1088
1101
  const parameters = {};
1089
1102
  for (const [key, rawConstraint] of Object.entries(item.parameters)) {
1090
1103
  if (!key.trim()) return null;
@@ -2378,8 +2391,8 @@ const APP_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
2378
2391
  const APP_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
2379
2392
  const APP_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
2380
2393
  const APP_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32768;
2381
- const isRecord$3 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
2382
- const isSurfaceEnvelope = (value) => isRecord$3(value) && value.protocol === "cohub.app.surface" && value.version === 1;
2394
+ const isRecord$4 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
2395
+ const isSurfaceEnvelope = (value) => isRecord$4(value) && value.protocol === "cohub.app.surface" && value.version === 1;
2383
2396
  const parseAppSurfaceReady = (value) => {
2384
2397
  if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
2385
2398
  const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
@@ -2393,7 +2406,7 @@ const parseAppSurfaceReady = (value) => {
2393
2406
  const parseAppSurfaceResponse = (value) => {
2394
2407
  if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
2395
2408
  if (typeof value.requestId !== "string" || !value.requestId) return null;
2396
- const error = isRecord$3(value.error) ? {
2409
+ const error = isRecord$4(value.error) ? {
2397
2410
  code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
2398
2411
  message: typeof value.error.message === "string" ? value.error.message : "App surface call failed"
2399
2412
  } : void 0;
@@ -2413,7 +2426,7 @@ const parseComposerChipKey = (value) => {
2413
2426
  return key;
2414
2427
  };
2415
2428
  const parseAppComposerChipSet = (value) => {
2416
- if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$3(value.chip)) return null;
2429
+ if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$4(value.chip)) return null;
2417
2430
  const key = parseComposerChipKey(value.chip.key);
2418
2431
  if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
2419
2432
  const label = value.chip.label.trim();
@@ -2490,6 +2503,7 @@ const buildAppComposerChipClear = (key) => ({
2490
2503
  //#endregion
2491
2504
  //#region ../protocol/dist/app-runtime.js
2492
2505
  const APP_RUNTIME_PROTOCOL = "cohub.app.runtime";
2506
+ const isRecord$3 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
2493
2507
  const buildAppRuntimeReady = () => ({
2494
2508
  protocol: APP_RUNTIME_PROTOCOL,
2495
2509
  version: 1,
@@ -2500,6 +2514,7 @@ const buildAppRuntimeCloseRequest = () => ({
2500
2514
  version: 1,
2501
2515
  type: "close.request"
2502
2516
  });
2517
+ const finite = (value) => typeof value === "number" && Number.isFinite(value);
2503
2518
  const buildAppRuntimeConfigureRequest = (input) => ({
2504
2519
  protocol: APP_RUNTIME_PROTOCOL,
2505
2520
  version: 1,
@@ -2514,6 +2529,154 @@ const buildAppRuntimePointer = (input) => ({
2514
2529
  y: input.y,
2515
2530
  down: input.down
2516
2531
  });
2532
+ const isRuntimeMessage = (value, type) => isRecord$3(value) && value.protocol === "cohub.app.runtime" && value.version === 1 && value.type === type;
2533
+ const envelope$1 = (type) => ({
2534
+ protocol: APP_RUNTIME_PROTOCOL,
2535
+ version: 1,
2536
+ type
2537
+ });
2538
+ const boundedText = (value, max) => {
2539
+ if (typeof value !== "string") return null;
2540
+ const text = value.replace(/\s+/g, " ").trim();
2541
+ return text ? text.slice(0, max) : null;
2542
+ };
2543
+ const buildAppRuntimeAnnounce = () => envelope$1("announce");
2544
+ const parseAppRuntimeAnnounce = (value) => isRuntimeMessage(value, "announce") ? buildAppRuntimeAnnounce() : null;
2545
+ const buildAppRuntimeWindowState = (state) => ({
2546
+ ...envelope$1("window.state"),
2547
+ title: state.title,
2548
+ status: state.status,
2549
+ dirty: state.dirty
2550
+ });
2551
+ const requestIdOf = (value) => typeof value.requestId === "string" && value.requestId && value.requestId.length <= 128 ? value.requestId : null;
2552
+ const buildAppRuntimeClosePrepare = (requestId) => ({
2553
+ ...envelope$1("close.prepare"),
2554
+ requestId
2555
+ });
2556
+ const parseAppRuntimeClosePrepare = (value) => {
2557
+ if (!isRuntimeMessage(value, "close.prepare")) return null;
2558
+ const requestId = requestIdOf(value);
2559
+ return requestId ? buildAppRuntimeClosePrepare(requestId) : null;
2560
+ };
2561
+ const buildAppRuntimeClosePrepared = (input) => ({
2562
+ ...envelope$1("close.prepared"),
2563
+ requestId: input.requestId,
2564
+ ok: input.ok
2565
+ });
2566
+ /** Select all, copy, paste, cut, undo, redo: the browser's, and the App's. */
2567
+ const EDITING_CHORD_KEYS = /* @__PURE__ */ new Set([
2568
+ "a",
2569
+ "c",
2570
+ "v",
2571
+ "x",
2572
+ "y",
2573
+ "z"
2574
+ ]);
2575
+ /** Whether a Ctrl / Meta chord may reach the host; both sides apply this rule. */
2576
+ const isAppRuntimeHostChord = (input) => (input.ctrlKey || input.metaKey) && !EDITING_CHORD_KEYS.has(input.key.toLowerCase());
2577
+ const buildAppRuntimeKey = (input) => ({
2578
+ ...envelope$1("key"),
2579
+ key: input.key,
2580
+ code: input.code,
2581
+ altKey: input.altKey,
2582
+ ctrlKey: input.ctrlKey,
2583
+ metaKey: input.metaKey,
2584
+ shiftKey: input.shiftKey
2585
+ });
2586
+ /** Resource kinds the Cohub host can drop into an App. */
2587
+ const APP_DROP_RESOURCE_TYPES = [
2588
+ "file",
2589
+ "task",
2590
+ "app",
2591
+ "session",
2592
+ "checkpoint"
2593
+ ];
2594
+ const isDropResourceType = (value) => APP_DROP_RESOURCE_TYPES.includes(value);
2595
+ const MAX_DROP_RESOURCES = 100;
2596
+ const parseDropResource = (value) => {
2597
+ if (!isRecord$3(value) || !isDropResourceType(value.type)) return null;
2598
+ const ref = boundedText(value.ref, 4096);
2599
+ if (!ref) return null;
2600
+ const resource = {
2601
+ type: value.type,
2602
+ ref
2603
+ };
2604
+ for (const key of [
2605
+ "title",
2606
+ "path",
2607
+ "mimeType",
2608
+ "appId",
2609
+ "href",
2610
+ "taskRunId"
2611
+ ]) {
2612
+ const text = boundedText(value[key], 4096);
2613
+ if (text) resource[key] = text;
2614
+ }
2615
+ if (finite(value.size) && value.size >= 0) resource.size = value.size;
2616
+ if (finite(value.mtimeMs)) resource.mtimeMs = value.mtimeMs;
2617
+ if (isRecord$3(value.snapshot)) resource.snapshot = value.snapshot;
2618
+ return resource;
2619
+ };
2620
+ const parseDropTypes = (value) => Array.isArray(value) ? [...new Set(value.filter(isDropResourceType))] : [];
2621
+ const buildAppRuntimeDropConfig = (accept) => ({
2622
+ ...envelope$1("drop.config"),
2623
+ accept: [...accept]
2624
+ });
2625
+ const buildAppRuntimeDrag = (input) => ({
2626
+ ...envelope$1("drag"),
2627
+ phase: input.phase,
2628
+ x: input.x,
2629
+ y: input.y,
2630
+ types: [...input.types],
2631
+ ...input.phase === "drop" ? { resources: input.resources ?? [] } : {}
2632
+ });
2633
+ const parseAppRuntimeDrag = (value) => {
2634
+ if (!isRuntimeMessage(value, "drag")) return null;
2635
+ if (value.phase !== "over" && value.phase !== "leave" && value.phase !== "drop") return null;
2636
+ if (!finite(value.x) || !finite(value.y)) return null;
2637
+ const resources = value.phase === "drop" && Array.isArray(value.resources) ? value.resources.slice(0, MAX_DROP_RESOURCES).map(parseDropResource).filter((resource) => resource !== null) : void 0;
2638
+ return buildAppRuntimeDrag({
2639
+ phase: value.phase,
2640
+ x: value.x,
2641
+ y: value.y,
2642
+ types: parseDropTypes(value.types),
2643
+ ...resources ? { resources } : {}
2644
+ });
2645
+ };
2646
+ /** Public design tokens, sent as resolved CSS values. */
2647
+ const APP_APPEARANCE_TOKENS = [
2648
+ "bg-primary",
2649
+ "bg-content",
2650
+ "bg-surface",
2651
+ "bg-elevated",
2652
+ "bg-input",
2653
+ "bg-hover",
2654
+ "bg-active",
2655
+ "text-primary",
2656
+ "text-secondary",
2657
+ "text-tertiary",
2658
+ "text-placeholder",
2659
+ "text-disabled",
2660
+ "border-primary",
2661
+ "border-subtle",
2662
+ "brand",
2663
+ "brand-hover",
2664
+ "brand-soft",
2665
+ "brand-muted",
2666
+ "brand-border",
2667
+ "brand-ring",
2668
+ "brand-contrast-fg",
2669
+ "error-fg",
2670
+ "selection-bg",
2671
+ "overlay-scrim",
2672
+ "shadow-subtle",
2673
+ "shadow-medium",
2674
+ "shadow-strong",
2675
+ "font-sans",
2676
+ "font-mono"
2677
+ ];
2678
+ /** CSS custom property an App reads a shared token from, e.g. `--cohub-bg-primary`. */
2679
+ const appAppearanceVar = (token) => `--cohub-${token}`;
2517
2680
  //#endregion
2518
2681
  //#region ../protocol/dist/app-navigation.js
2519
2682
  const APP_NAVIGATION_PROTOCOL = "cohub.app.navigation";
@@ -3138,6 +3301,8 @@ var AppSurfaceApi = class {
3138
3301
  listening = false;
3139
3302
  allowedOrigins = null;
3140
3303
  trustedOrigin;
3304
+ /** The chip on show, so it can be announced again. */
3305
+ chip = null;
3141
3306
  allowHostOrigins(origins) {
3142
3307
  this.allowedOrigins = origins.map((origin) => origin.trim()).filter(Boolean).map((origin) => {
3143
3308
  try {
@@ -3168,11 +3333,14 @@ var AppSurfaceApi = class {
3168
3333
  setComposerChip(chip) {
3169
3334
  const message = parseAppComposerChipSet(buildAppComposerChipSet(chip));
3170
3335
  if (!message) throw new Error("Invalid App composer chip");
3336
+ this.chip = message.chip;
3337
+ this.start();
3171
3338
  this.post(message);
3172
3339
  }
3173
3340
  clearComposerChip(key) {
3174
3341
  const message = parseAppComposerChipClear(buildAppComposerChipClear(key));
3175
3342
  if (!message) throw new Error("Invalid App composer chip key");
3343
+ if (this.chip?.key === message.key) this.chip = null;
3176
3344
  this.post(message);
3177
3345
  }
3178
3346
  announce() {
@@ -3197,6 +3365,11 @@ var AppSurfaceApi = class {
3197
3365
  onMessage = (event) => {
3198
3366
  if (typeof window === "undefined" || event.source !== window.parent) return;
3199
3367
  if (!this.isTrusted(event.origin)) return;
3368
+ if (parseAppRuntimeAnnounce(event.data)) {
3369
+ if (this.handlers.size > 0) this.announce();
3370
+ if (this.chip) this.post(buildAppComposerChipSet(this.chip));
3371
+ return;
3372
+ }
3200
3373
  const request = parseAppSurfaceRequest(event.data);
3201
3374
  if (!request) return;
3202
3375
  const commandId = request.commandId;
@@ -3301,6 +3474,171 @@ function attachAppEmbed(frame, options) {
3301
3474
  };
3302
3475
  }
3303
3476
  //#endregion
3477
+ //#region src/app-window.ts
3478
+ /** One document listener for all clients, so a chord reaches the host once. */
3479
+ const keyTransports = /* @__PURE__ */ new Set();
3480
+ function forwardKey(event) {
3481
+ if (event.isComposing || !isAppRuntimeHostChord(event)) return;
3482
+ setTimeout(() => {
3483
+ if (event.defaultPrevented) return;
3484
+ [...keyTransports].find((candidate) => candidate.isHostConnected?.())?.notify?.(buildAppRuntimeKey({
3485
+ key: event.key,
3486
+ code: event.code,
3487
+ altKey: event.altKey,
3488
+ ctrlKey: event.ctrlKey,
3489
+ metaKey: event.metaKey,
3490
+ shiftKey: event.shiftKey
3491
+ }));
3492
+ }, 0);
3493
+ }
3494
+ function attachKeys(transport) {
3495
+ if (keyTransports.size === 0) window.addEventListener("keydown", forwardKey);
3496
+ keyTransports.add(transport);
3497
+ }
3498
+ function detachKeys(transport) {
3499
+ if (!keyTransports.delete(transport)) return;
3500
+ if (keyTransports.size === 0) window.removeEventListener("keydown", forwardKey);
3501
+ }
3502
+ /** The App's side of its host window; a no-op outside a Cohub host. */
3503
+ var AppWindowApi = class {
3504
+ transport;
3505
+ runtime;
3506
+ state = {
3507
+ title: null,
3508
+ status: "idle",
3509
+ dirty: false
3510
+ };
3511
+ /** Only a client that reported state re-announces it; others must not reset it. */
3512
+ stateReported = false;
3513
+ beforeClose = null;
3514
+ dropHandler = null;
3515
+ hostUnsubscribe = null;
3516
+ constructor(transport, runtime) {
3517
+ this.transport = transport;
3518
+ this.runtime = runtime;
3519
+ if (typeof window === "undefined" || !transport.notify) return;
3520
+ if (window.parent === window || typeof window.addEventListener !== "function") return;
3521
+ attachKeys(transport);
3522
+ }
3523
+ /** Report the tab title, save status, and whether work is still unsaved. */
3524
+ setState(patch) {
3525
+ this.state = {
3526
+ ...this.state,
3527
+ ...patch
3528
+ };
3529
+ this.stateReported = true;
3530
+ this.listen();
3531
+ this.transport.notify?.(buildAppRuntimeWindowState(this.state));
3532
+ }
3533
+ /** Persist work before a `dirty` window closes; `false` or throw keeps it open. */
3534
+ onBeforeClose(handler) {
3535
+ this.beforeClose = handler;
3536
+ this.listen();
3537
+ return () => {
3538
+ if (this.beforeClose === handler) this.beforeClose = null;
3539
+ };
3540
+ }
3541
+ /** Accept Cohub resources dragged from the host onto this App. */
3542
+ onDrop(handler) {
3543
+ this.dropHandler = handler;
3544
+ this.listen();
3545
+ this.transport.notify?.(buildAppRuntimeDropConfig(handler.accept));
3546
+ return () => {
3547
+ if (this.dropHandler !== handler) return;
3548
+ this.dropHandler = null;
3549
+ this.transport.notify?.(buildAppRuntimeDropConfig([]));
3550
+ };
3551
+ }
3552
+ /** The window's file: on open, on reopen, and after a rename or move. */
3553
+ onLaunch(listener) {
3554
+ let last = null;
3555
+ const handle = (context) => {
3556
+ const invocation = context?.invocation;
3557
+ if (!invocation?.file || !invocation.spaceId) return;
3558
+ const key = `${invocation.id ?? ""}:${invocation.spaceId}:${invocation.file.path}`;
3559
+ if (key === last) return;
3560
+ last = key;
3561
+ listener({ file: {
3562
+ spaceId: invocation.spaceId,
3563
+ path: invocation.file.path
3564
+ } });
3565
+ };
3566
+ const unsubscribe = this.runtime.onContextChanged(handle);
3567
+ this.runtime.context().then(handle, () => {});
3568
+ return unsubscribe;
3569
+ }
3570
+ dispose() {
3571
+ this.hostUnsubscribe?.();
3572
+ this.hostUnsubscribe = null;
3573
+ detachKeys(this.transport);
3574
+ }
3575
+ listen() {
3576
+ if (this.hostUnsubscribe || !this.transport.subscribeHostMessages) return;
3577
+ this.hostUnsubscribe = this.transport.subscribeHostMessages((data) => {
3578
+ if (parseAppRuntimeAnnounce(data)) {
3579
+ this.announce();
3580
+ return;
3581
+ }
3582
+ if (!this.transport.isHostConnected?.()) return;
3583
+ const prepare = parseAppRuntimeClosePrepare(data);
3584
+ if (prepare) {
3585
+ this.prepareClose(prepare.requestId);
3586
+ return;
3587
+ }
3588
+ const drag = parseAppRuntimeDrag(data);
3589
+ if (drag) this.dispatchDrag(drag);
3590
+ });
3591
+ this.runtime.context().catch(() => {});
3592
+ }
3593
+ /** A reloaded frame forgot the tab state and drops; report them again. */
3594
+ announce() {
3595
+ if (this.stateReported) this.transport.notify?.(buildAppRuntimeWindowState(this.state));
3596
+ if (this.dropHandler) this.transport.notify?.(buildAppRuntimeDropConfig(this.dropHandler.accept));
3597
+ }
3598
+ async prepareClose(requestId) {
3599
+ let ok;
3600
+ try {
3601
+ ok = this.beforeClose ? await this.beforeClose() !== false : !this.state.dirty;
3602
+ } catch {
3603
+ ok = false;
3604
+ }
3605
+ this.transport.notify?.(buildAppRuntimeClosePrepared({
3606
+ requestId,
3607
+ ok
3608
+ }));
3609
+ }
3610
+ dispatchDrag(drag) {
3611
+ const handler = this.dropHandler;
3612
+ if (!handler) return;
3613
+ const point = {
3614
+ x: drag.x,
3615
+ y: drag.y,
3616
+ types: drag.types
3617
+ };
3618
+ if (drag.phase === "over") handler.over?.(point);
3619
+ else if (drag.phase === "leave") handler.leave?.();
3620
+ else handler.drop({
3621
+ ...point,
3622
+ resources: drag.resources ?? []
3623
+ });
3624
+ }
3625
+ };
3626
+ /** Writes host tokens (`--cohub-*`), color scheme, and `lang` onto `root`. */
3627
+ function applyAppAppearance(root, context) {
3628
+ const appearance = context?.appearance;
3629
+ for (const token of APP_APPEARANCE_TOKENS) {
3630
+ const value = appearance?.tokens[token];
3631
+ if (value) root.style.setProperty(appAppearanceVar(token), value);
3632
+ else root.style.removeProperty(appAppearanceVar(token));
3633
+ }
3634
+ if (appearance) {
3635
+ root.style.colorScheme = appearance.colorScheme;
3636
+ root.dataset.cohubColorScheme = appearance.colorScheme;
3637
+ root.dataset.cohubReducedMotion = String(appearance.reducedMotion);
3638
+ }
3639
+ if (context?.locale) root.lang = context.locale;
3640
+ }
3641
+ //#endregion
3304
3642
  //#region src/app-runtime.ts
3305
3643
  var AppRuntimeError = class extends Error {
3306
3644
  code;
@@ -3340,6 +3678,36 @@ var ParentBridgeTransport = class {
3340
3678
  contextListener = null;
3341
3679
  diagnosticListeners = /* @__PURE__ */ new Set();
3342
3680
  diagnosticListener = null;
3681
+ hostListeners = /* @__PURE__ */ new Set();
3682
+ hostListener = null;
3683
+ announceListener = null;
3684
+ isHostConnected() {
3685
+ return this.trustedParentOrigin !== null;
3686
+ }
3687
+ isFromParent(event) {
3688
+ if (event.source !== window.parent) return false;
3689
+ const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
3690
+ return !parentOrigin || event.origin === parentOrigin;
3691
+ }
3692
+ subscribeHostMessages(listener) {
3693
+ if (!hasParent() || typeof window.addEventListener !== "function") return () => {};
3694
+ this.hostListeners.add(listener);
3695
+ this.announceReady();
3696
+ if (!this.hostListener) {
3697
+ this.hostListener = (event) => {
3698
+ if (!this.isFromParent(event)) return;
3699
+ for (const current of this.hostListeners) current(event.data);
3700
+ };
3701
+ window.addEventListener("message", this.hostListener);
3702
+ }
3703
+ return () => {
3704
+ this.hostListeners.delete(listener);
3705
+ if (this.hostListeners.size === 0 && this.hostListener) {
3706
+ window.removeEventListener("message", this.hostListener);
3707
+ this.hostListener = null;
3708
+ }
3709
+ };
3710
+ }
3343
3711
  /**
3344
3712
  * Tells the host the runtime is ready once, before any subscription. Hosts
3345
3713
  * gate unsolicited messages (context pushes, diagnostics) on this handshake,
@@ -3353,8 +3721,19 @@ var ParentBridgeTransport = class {
3353
3721
  try {
3354
3722
  window.parent.postMessage(buildAppRuntimeReady(), parentOrigin);
3355
3723
  this.readyAnnounced = true;
3724
+ this.answerAnnounceRequests();
3356
3725
  } catch {}
3357
3726
  }
3727
+ /** Says ready again when a reloaded host asks. */
3728
+ answerAnnounceRequests() {
3729
+ if (this.announceListener || typeof window.addEventListener !== "function") return;
3730
+ this.announceListener = (event) => {
3731
+ if (!this.isFromParent(event) || !parseAppRuntimeAnnounce(event.data)) return;
3732
+ this.readyAnnounced = false;
3733
+ this.announceReady();
3734
+ };
3735
+ window.addEventListener("message", this.announceListener);
3736
+ }
3358
3737
  subscribeDiagnostics(listener) {
3359
3738
  if (!hasParent() || typeof window.addEventListener !== "function") return () => {};
3360
3739
  this.diagnosticListeners.add(listener);
@@ -4333,6 +4712,7 @@ var CohubClient = class {
4333
4712
  transport;
4334
4713
  websocketClient;
4335
4714
  appRuntime;
4715
+ appWindow;
4336
4716
  constructor(options = {}) {
4337
4717
  const apiBaseUrl = resolveApiBaseUrl(options);
4338
4718
  const appRuntime = options.app ?? options.work;
@@ -4348,6 +4728,10 @@ var CohubClient = class {
4348
4728
  fetch: options.fetch
4349
4729
  }), resolveWebBaseUrl({ env: options.env }));
4350
4730
  this.appRuntime = createAppRuntime(appTransport, appRuntime?.appId, appIdResolver);
4731
+ this.appWindow = new AppWindowApi(appTransport, {
4732
+ context: () => this.appRuntime.context(),
4733
+ onContextChanged: (listener) => this.appRuntime.onContextChanged(listener)
4734
+ });
4351
4735
  const executionToken = resolveExecutionToken();
4352
4736
  const getAccessToken = options.getAccessToken ?? (executionToken ? () => executionToken : (tokenOptions) => this.appRuntime.getAccessToken(tokenOptions));
4353
4737
  const resolvedOptions = {
@@ -4403,6 +4787,7 @@ var CohubClient = class {
4403
4787
  /** Releases listeners this client registered. Safe to call more than once. */
4404
4788
  dispose() {
4405
4789
  this.appRuntime.dispose();
4790
+ this.appWindow.dispose();
4406
4791
  }
4407
4792
  auth = {
4408
4793
  authorize: (input) => this.appRuntime.authorize(input),
@@ -4424,6 +4809,26 @@ var CohubClient = class {
4424
4809
  /** Expose callable methods from inside a published app. */
4425
4810
  surface: new AppSurfaceApi(),
4426
4811
  onContextChanged: (listener) => this.appRuntime.onContextChanged(listener),
4812
+ /** Files the host opens with this App — the first one and every later open. */
4813
+ onLaunch: (listener) => this.appWindow.onLaunch(listener),
4814
+ /** Accept Cohub resources the viewer drags from the host onto this App. */
4815
+ onDrop: (handler) => this.appWindow.onDrop(handler),
4816
+ window: {
4817
+ /** Report the tab title, save status, and whether work is still unsaved. */
4818
+ setState: (patch) => this.appWindow.setState(patch),
4819
+ /** Persist pending work before the host closes a `dirty` window. */
4820
+ onBeforeClose: (handler) => this.appWindow.onBeforeClose(handler)
4821
+ },
4822
+ appearance: {
4823
+ /** Keep `root` (default `<html>`) in sync with the host theme and locale. */
4824
+ sync: (root) => {
4825
+ const target = root ?? (typeof document === "undefined" ? null : document.documentElement);
4826
+ if (!target) return () => {};
4827
+ const apply = (context) => applyAppAppearance(target, context);
4828
+ const stop = this.appRuntime.onContextChanged(apply);
4829
+ this.appRuntime.context().then(apply, () => {});
4830
+ return stop;
4831
+ } },
4427
4832
  /** Ask the host to close this App's surface. */
4428
4833
  requestClose: () => this.appRuntime.requestClose(),
4429
4834
  /**
@@ -4793,7 +5198,17 @@ function cloneInvocation(invocation) {
4793
5198
  ...invocation.embedder ? { embedder: {
4794
5199
  appId: invocation.embedder.appId,
4795
5200
  slug: invocation.embedder.slug
4796
- } } : {}
5201
+ } } : {},
5202
+ ...invocation.file ? { file: { path: invocation.file.path } } : {}
5203
+ };
5204
+ }
5205
+ /** Host appearance can be reactive state too; copy it into plain data. */
5206
+ function cloneAppearance(appearance) {
5207
+ return {
5208
+ colorScheme: appearance.colorScheme,
5209
+ theme: appearance.theme,
5210
+ tokens: { ...appearance.tokens },
5211
+ reducedMotion: appearance.reducedMotion
4797
5212
  };
4798
5213
  }
4799
5214
  /**
@@ -5046,6 +5461,9 @@ function createAppBridgeCore(config) {
5046
5461
  async function getContext() {
5047
5462
  const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
5048
5463
  const shell = config.getShell?.() ?? config.shell;
5464
+ const locale = config.getLocale?.();
5465
+ const appearance = config.getAppearance?.();
5466
+ const windowState = config.getWindow?.();
5049
5467
  const appScopes = clonePermissionScopes(app.appScopes);
5050
5468
  const viewerUuid = await getViewerUuid();
5051
5469
  synchronizeViewer(viewerUuid);
@@ -5074,6 +5492,9 @@ function createAppBridgeCore(config) {
5074
5492
  session: shell.session ? { ...shell.session } : null,
5075
5493
  turn: shell.turn ? { ...shell.turn } : null
5076
5494
  } } : {},
5495
+ ...locale ? { locale } : {},
5496
+ ...appearance ? { appearance: cloneAppearance(appearance) } : {},
5497
+ ...windowState ? { window: { visible: windowState.visible } } : {},
5077
5498
  permissions: {
5078
5499
  scopes: normalizePermissionScopes([...appScopes, ...viewerGrants.flatMap((grant) => grant.scopes)]),
5079
5500
  appScopes,
@@ -5251,8 +5672,7 @@ function createAppBridgeCore(config) {
5251
5672
  /**
5252
5673
  * Calls the authorize endpoint. `silent` marks a background refresh of a
5253
5674
  * previous consent: the server then only renews a live grant and never
5254
- * creates or revives one, so a revoked grant cannot come back without a
5255
- * fresh dialog.
5675
+ * creates or revives one. `consent: "host"` may also create or widen one.
5256
5676
  */
5257
5677
  async function authorize(scopes, spaceId, options) {
5258
5678
  const incremental = Boolean(state.pendingAuth && authorizationRequests.has(state.pendingAuth.requestId));
@@ -5276,6 +5696,7 @@ function createAppBridgeCore(config) {
5276
5696
  scopes,
5277
5697
  ...spaceId ? { spaceId } : {},
5278
5698
  ...options?.silent ? { silent: true } : {},
5699
+ ...options?.consent ? { consent: options.consent } : {},
5279
5700
  ...incremental ? { scopeMode: "extend" } : {}
5280
5701
  })
5281
5702
  });
@@ -5322,17 +5743,15 @@ function createAppBridgeCore(config) {
5322
5743
  }
5323
5744
  }
5324
5745
  /**
5325
- * Silently renews the approved read-only scopes on the current Shell Space.
5746
+ * Grants scopes on the current Shell Space without opening the dialog.
5326
5747
  *
5327
5748
  * Deciding that a request needs no dialog is the Host's job: it compares the
5328
- * target against the Space it is actually showing. That check only decides
5329
- * *whether to ask*; the server still decides *what may be granted* through
5330
- * the silent path, so a revoked consent can never come back on its own.
5749
+ * target and trust; the server still caps what may be granted.
5331
5750
  */
5332
5751
  const shellAuthorizationInFlight = /* @__PURE__ */ new Map();
5333
5752
  const shellAuthorizationCache = /* @__PURE__ */ new Map();
5334
5753
  const SHELL_AUTH_CACHE_MS = 3e4;
5335
- async function authorizeShellSilently(scopes, spaceId) {
5754
+ async function authorizeShellSilently(scopes, spaceId, consent) {
5336
5755
  const shellContext = config.getShell?.() ?? config.shell;
5337
5756
  const shell = shellContext?.space ?? null;
5338
5757
  if (!shellContext || authorizationContext.surface === "broker" || !shell || shell.id !== spaceId) throw new AppAuthorizationError("Shell authorization is unavailable for this surface.", 403, "consent_required");
@@ -5341,13 +5760,16 @@ function createAppBridgeCore(config) {
5341
5760
  await startSignIn();
5342
5761
  throw new AppLoginRedirect();
5343
5762
  }
5344
- const cacheKey = `${viewerUuid}:${app.id}:${spaceId}:${[...scopes].sort().join(",")}`;
5763
+ const cacheKey = `${viewerUuid}:${app.id}:${spaceId}:${consent}:${[...scopes].sort().join(",")}`;
5345
5764
  const cached = shellAuthorizationCache.get(cacheKey);
5346
5765
  if (cached && cached.expiresAt > Date.now()) return cached.result;
5347
5766
  if (cached) shellAuthorizationCache.delete(cacheKey);
5348
5767
  const pending = shellAuthorizationInFlight.get(cacheKey);
5349
5768
  if (pending) return pending;
5350
- const request = authorize(scopes, spaceId, { silent: true });
5769
+ const request = authorize(scopes, spaceId, consent === "host" ? {
5770
+ silent: true,
5771
+ consent: "host"
5772
+ } : { silent: true });
5351
5773
  shellAuthorizationInFlight.set(cacheKey, request);
5352
5774
  try {
5353
5775
  const result = await request;
@@ -5360,6 +5782,20 @@ function createAppBridgeCore(config) {
5360
5782
  shellAuthorizationInFlight.delete(cacheKey);
5361
5783
  }
5362
5784
  }
5785
+ /** Trusted Apps get Host consent; others may only renew read-only grants. */
5786
+ async function shellConsentFor(scopes, spaceId) {
5787
+ if (!scopes.every(isAppHostConsentScope)) return null;
5788
+ if (await isTrustedIn(spaceId)) return "host";
5789
+ return scopes.every(isAppSilentShellScope) ? "renew" : null;
5790
+ }
5791
+ async function isTrustedIn(spaceId) {
5792
+ if (app.spaceId === spaceId) return true;
5793
+ try {
5794
+ return await config.isInstalledIn?.(spaceId) === true;
5795
+ } catch {
5796
+ return false;
5797
+ }
5798
+ }
5363
5799
  /**
5364
5800
  * Reads the Shell Space supplied by the current Host. Invocation context is
5365
5801
  * deliberately excluded: an App may be opened from one Space while the Shell
@@ -5823,9 +6259,11 @@ function createAppBridgeCore(config) {
5823
6259
  return;
5824
6260
  }
5825
6261
  const shellSpace = resolveShellSpace();
5826
- const shellTargetId = spaceId ?? shellSpace?.id;
5827
- if (!alwaysAsk && !selectSpace && Boolean(shellTargetId && (!spaceId || shellSpace?.id === spaceId)) && scopes.every(isAppSilentShellScope) && shellTargetId) try {
5828
- const result = await authorizeShellSilently(scopes, shellTargetId);
6262
+ const shellTargetId = !alwaysAsk && !selectSpace && shellSpace && (!spaceId || spaceId === shellSpace.id) ? shellSpace.id : null;
6263
+ const shellConsent = shellTargetId ? await shellConsentFor(scopes, shellTargetId) : null;
6264
+ if (state.pendingAuth !== reserved) return;
6265
+ if (shellConsent && shellTargetId) try {
6266
+ const result = await authorizeShellSilently(scopes, shellTargetId, shellConsent);
5829
6267
  if (state.pendingAuth !== reserved) return;
5830
6268
  setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
5831
6269
  replyPendingAuth(reserved, authorizeResult(result.token, result.spaceId, shellSpace?.name ?? null));
@@ -6078,6 +6516,213 @@ function createAppBridgeCore(config) {
6078
6516
  };
6079
6517
  }
6080
6518
  //#endregion
6519
+ //#region src/generation-blocks.ts
6520
+ /** Tolerant readers for generation payloads, shared by every task projection. */
6521
+ const EXCERPT_LIMIT = 240;
6522
+ function record(value) {
6523
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
6524
+ }
6525
+ function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
6526
+ if (typeof value !== "string") return void 0;
6527
+ const clean = value.replace(/\s+/g, " ").trim();
6528
+ if (!clean) return void 0;
6529
+ return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
6530
+ }
6531
+ function blockText(block) {
6532
+ return cleanExcerpt(block.text ?? block.content ?? block.value);
6533
+ }
6534
+ function blockUrl(block) {
6535
+ const source = record(block.source);
6536
+ return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
6537
+ }
6538
+ function blockMimeType(block) {
6539
+ const source = record(block.source);
6540
+ const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
6541
+ return typeof value === "string" ? value : void 0;
6542
+ }
6543
+ function positiveNumber(value) {
6544
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
6545
+ }
6546
+ function blockNaturalSize(block) {
6547
+ const source = record(block.source);
6548
+ const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
6549
+ const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
6550
+ return {
6551
+ ...naturalWidth ? { naturalWidth } : {},
6552
+ ...naturalHeight ? { naturalHeight } : {}
6553
+ };
6554
+ }
6555
+ function contentBlocks(value) {
6556
+ return Array.isArray(value) ? value.filter((item) => record(item)) : [];
6557
+ }
6558
+ function taskData(run) {
6559
+ const payload = record(run.payload);
6560
+ return record(payload?.data) ?? payload;
6561
+ }
6562
+ function generationOutput(run) {
6563
+ return contentBlocks(record(run.result)?.output);
6564
+ }
6565
+ function generationPrompt(run) {
6566
+ for (const block of contentBlocks(taskData(run)?.content)) {
6567
+ const text = blockText(block);
6568
+ if (text) return text;
6569
+ }
6570
+ }
6571
+ function blockMeta(block) {
6572
+ return record(block.meta);
6573
+ }
6574
+ function blockIdentity(block) {
6575
+ const meta = blockMeta(block);
6576
+ const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
6577
+ if (typeof value !== "string" && typeof value !== "number") return void 0;
6578
+ return cleanExcerpt(String(value), 220);
6579
+ }
6580
+ function blockTitle(block) {
6581
+ return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
6582
+ }
6583
+ function blockDurationMs(block) {
6584
+ const source = record(block.source);
6585
+ const meta = blockMeta(block);
6586
+ const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
6587
+ if (milliseconds) return Math.round(milliseconds);
6588
+ const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
6589
+ return seconds ? Math.round(seconds * 1e3) : void 0;
6590
+ }
6591
+ function blockPreviewUrl(block) {
6592
+ const source = record(block.source);
6593
+ return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
6594
+ }
6595
+ /**
6596
+ * Image roles that depict playable media, by the media types they may cover;
6597
+ * `last_frame` stays a result of its own.
6598
+ */
6599
+ const COVER_ROLES = /* @__PURE__ */ new Map([
6600
+ ["first_frame", /* @__PURE__ */ new Set(["video"])],
6601
+ ["cover", /* @__PURE__ */ new Set(["video", "audio"])],
6602
+ ["poster", /* @__PURE__ */ new Set(["video", "audio"])],
6603
+ ["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
6604
+ ]);
6605
+ function isPlayable(block) {
6606
+ return block.type === "video" || block.type === "audio";
6607
+ }
6608
+ /**
6609
+ * Pair playable media with its cover image, returning media index → cover
6610
+ * index. An image sharing the provider id wins (music tracks and their art,
6611
+ * shared by every media of that id); otherwise cover-role images pair in
6612
+ * order with remaining media their role fits (a video and its `first_frame`).
6613
+ * Covers need a remote URL, so an inline image is never folded away.
6614
+ */
6615
+ function generationCovers(blocks) {
6616
+ const covers = /* @__PURE__ */ new Map();
6617
+ const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
6618
+ blocks.forEach((block, index) => {
6619
+ const id = blockIdentity(block);
6620
+ if (!isPlayable(block) || id === void 0) return;
6621
+ const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
6622
+ if (cover >= 0) covers.set(index, cover);
6623
+ });
6624
+ const paired = new Set(covers.values());
6625
+ const roleCovers = blocks.flatMap((block, index) => {
6626
+ const role = blockMeta(block)?.role;
6627
+ const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
6628
+ return isCover(block) && !paired.has(index) && types ? [{
6629
+ index,
6630
+ types
6631
+ }] : [];
6632
+ });
6633
+ blocks.forEach((block, index) => {
6634
+ if (!isPlayable(block) || covers.has(index)) return;
6635
+ const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
6636
+ const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
6637
+ if (cover) covers.set(index, cover.index);
6638
+ });
6639
+ return covers;
6640
+ }
6641
+ //#endregion
6642
+ //#region src/generation-task.ts
6643
+ const INLINE_KEYS = [
6644
+ "data",
6645
+ "base64",
6646
+ "contentBase64"
6647
+ ];
6648
+ function isDeferred(block) {
6649
+ return block.deferredBase64 === true || record(block.source)?.deferredBase64 === true;
6650
+ }
6651
+ const MEDIA_TYPE_PATTERN = /^(image|video|audio)\/[\w.+-]+$/;
6652
+ const DEFAULT_MEDIA_TYPES = {
6653
+ image: "image/png",
6654
+ video: "video/mp4",
6655
+ audio: "audio/mpeg"
6656
+ };
6657
+ function outputType(value) {
6658
+ return value === "image" || value === "video" || value === "audio" ? value : null;
6659
+ }
6660
+ /**
6661
+ * Only media blocks become outputs; text such as a revised prompt or a refusal
6662
+ * stays in the raw result. Covers fold into the media they depict.
6663
+ */
6664
+ function projectOutputs(run) {
6665
+ const blocks = generationOutput(run);
6666
+ const covers = generationCovers(blocks);
6667
+ const coverIndexes = new Set(covers.values());
6668
+ const coverUrl = (index) => {
6669
+ const cover = covers.get(index);
6670
+ return cover === void 0 ? void 0 : blockUrl(blocks[cover] ?? {});
6671
+ };
6672
+ return blocks.flatMap((block, index) => {
6673
+ const type = outputType(block.type);
6674
+ if (!type || coverIndexes.has(index)) return [];
6675
+ const size = blockNaturalSize(block);
6676
+ return [{
6677
+ index,
6678
+ type,
6679
+ url: blockUrl(block) ?? null,
6680
+ previewUrl: blockPreviewUrl(block) ?? coverUrl(index) ?? null,
6681
+ title: blockTitle(block) ?? null,
6682
+ mimeType: blockMimeType(block) ?? null,
6683
+ width: size.naturalWidth ?? null,
6684
+ height: size.naturalHeight ?? null,
6685
+ durationMs: blockDurationMs(block) ?? null,
6686
+ deferred: isDeferred(block)
6687
+ }];
6688
+ });
6689
+ }
6690
+ function toGenerationTaskView(run) {
6691
+ const data = taskData(run);
6692
+ const result = record(run.result);
6693
+ const model = data?.model ?? result?.model;
6694
+ return {
6695
+ id: run.id,
6696
+ status: run.status,
6697
+ spaceId: run.spaceId,
6698
+ sessionId: run.sessionId,
6699
+ turnId: run.turnId,
6700
+ model: typeof model === "string" && model ? model : null,
6701
+ prompt: generationPrompt(run) ?? null,
6702
+ errorMessage: run.errorMessage,
6703
+ outputs: run.status === "completed" ? projectOutputs(run) : [],
6704
+ createdAt: run.createdAt,
6705
+ startedAt: run.startedAt,
6706
+ finishedAt: run.finishedAt,
6707
+ updatedAt: run.updatedAt
6708
+ };
6709
+ }
6710
+ function isActiveGenerationTask(task) {
6711
+ return task.status === "pending" || task.status === "running";
6712
+ }
6713
+ /** Playable source of an output: its URL, or a data URL for inline payloads. */
6714
+ function generationOutputSource(result, outputIndex) {
6715
+ const block = contentBlocks(record(result)?.output)[outputIndex];
6716
+ if (!block) return null;
6717
+ const url = blockUrl(block);
6718
+ if (url) return url;
6719
+ const source = record(block.source);
6720
+ const data = INLINE_KEYS.map((key) => source?.[key] ?? block[key]).find((value) => typeof value === "string" && value !== "");
6721
+ if (!data) return null;
6722
+ const declared = blockMimeType(block);
6723
+ return `data:${declared && MEDIA_TYPE_PATTERN.test(declared) ? declared : DEFAULT_MEDIA_TYPES[String(block.type)] ?? "application/octet-stream"};base64,${data}`;
6724
+ }
6725
+ //#endregion
6081
6726
  //#region src/board/animation.ts
6082
6727
  function track(input) {
6083
6728
  return BoardTrackSchema.parse(input);
@@ -6323,4 +6968,4 @@ function createBoardExtensionRegistry() {
6323
6968
  }
6324
6969
  const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
6325
6970
  //#endregion
6326
- 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, AppRuntimeError, 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, NATIVE_INGEST_MAX_TURNS, NATIVE_SYNC_SOURCE, OriginBrokerTransport, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, RUNTIME_ARCHIVE_SEGMENT_BYTES, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, RUNTIME_RECOVERY_BATCH_SIZE, ReferencesApi, ReferralsApi, SETTLED_TURN_STATUSES, 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, contextToPiMessages, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createOriginAppResolver, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, fingerprintProjectionTurns, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isNativeClientTurn, isProjectionCompaction, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, nativeRuntimeEventSchema, nativeTurnCompleteSchema, nativeTurnProgressSchema, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, projectNativeMessageMeta, projectNativeSession, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebBaseUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeNativeStopSchema, runtimeReadySchema, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, serializeProjection, serializeProjectionRecords, setGrantedAppScopes, track, trimProjectionTurnsToCompaction, validateArchiveBoundary };
6971
+ export { APP_APPEARANCE_TOKENS, 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, AppRuntimeError, AppSurfaceApi, AppWindowApi, 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_ACCEPT_TIMEOUT_MS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_OPEN_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, NATIVE_INGEST_MAX_TURNS, NATIVE_SYNC_SOURCE, OriginBrokerTransport, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, RUNTIME_ARCHIVE_SEGMENT_BYTES, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, RUNTIME_RECOVERY_BATCH_SIZE, ReferencesApi, ReferralsApi, SETTLED_TURN_STATUSES, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, appAppearanceVar, applyAppAppearance, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, contextToPiMessages, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createOriginAppResolver, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, defaultDesktopCommandTimeoutMs, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, fingerprintProjectionTurns, formatAppRef, formatWorkRef, generationOutputSource, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isActiveGenerationTask, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isNativeClientTurn, isProjectionCompaction, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, nativeIngestTurnSchema, nativeRuntimeEventSchema, nativeTurnCompleteSchema, nativeTurnProgressSchema, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, projectNativeMessageMeta, projectNativeSession, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebBaseUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeNativeStopSchema, runtimeReadySchema, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, serializeProjection, serializeProjectionRecords, setGrantedAppScopes, toGenerationTaskView, track, trimProjectionTurnsToCompaction, validateArchiveBoundary };