@neta-art/cohub 6.0.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +6 -5
  2. package/dist/board/animation.d.ts +2 -1
  3. package/dist/board/animation.js +0 -1
  4. package/dist/board/core/connections.js +4 -4
  5. package/dist/board/core/export-plan.js +1 -1
  6. package/dist/board/core/shape-types.js +0 -1
  7. package/dist/board/index.d.ts +6 -5
  8. package/dist/board/index.js +7 -7
  9. package/dist/board/mutation.d.ts +2 -11
  10. package/dist/board/mutation.js +2 -56
  11. package/dist/board/render/renderers/text-card-renderer.js +1 -1
  12. package/dist/board/semantic-document.d.ts +30 -0
  13. package/dist/board/semantic-document.js +271 -0
  14. package/dist/board/semantic-mutation.d.ts +10 -0
  15. package/dist/board/semantic-mutation.js +203 -0
  16. package/dist/chunks/http.d.ts +340 -317
  17. package/dist/chunks/http.js +477 -1784
  18. package/dist/chunks/transport.js +1 -1
  19. package/dist/chunks/websocket.d.ts +3677 -3151
  20. package/dist/chunks/websocket.js +1 -1
  21. package/dist/http.d.ts +3 -3
  22. package/dist/index.d.ts +165 -119
  23. package/dist/index.js +1419 -446
  24. package/dist/protocol/dist/board-authoring.d.ts +1305 -1
  25. package/dist/protocol/dist/board-authoring.js +80 -6
  26. package/dist/protocol/dist/board-codec.d.ts +2 -2
  27. package/dist/protocol/dist/board-codec.js +19 -4
  28. package/dist/protocol/dist/board-composition.d.ts +126 -1
  29. package/dist/protocol/dist/board-composition.js +1 -9
  30. package/dist/protocol/dist/board-connection.d.ts +16 -16
  31. package/dist/protocol/dist/board-connection.js +16 -16
  32. package/dist/protocol/dist/board-document.d.ts +34 -3
  33. package/dist/protocol/dist/board-document.js +3 -1
  34. package/dist/protocol/dist/board-effect.d.ts +94 -0
  35. package/dist/protocol/dist/board-effect.js +53 -0
  36. package/dist/protocol/dist/board-json.js +16 -0
  37. package/dist/protocol/dist/board.d.ts +34 -164
  38. package/dist/protocol/dist/board.js +44 -45
  39. package/dist/protocol/dist/index.d.ts +8 -8
  40. package/dist/protocol/dist/realtime/types.d.ts +1 -1
  41. package/dist/types.d.ts +3 -1
  42. package/package.json +1 -2
  43. package/dist/board/codec.d.ts +0 -27
  44. package/dist/board/codec.js +0 -277
  45. package/dist/protocol/dist/board-content.js +0 -26
  46. package/dist/protocol/dist/board-upgrade.js +0 -2
  47. package/dist/protocol/dist/identifiers.js +0 -10
  48. package/dist/protocol/dist/index.js +0 -19
  49. package/dist/protocol/dist/provenance.js +0 -15
  50. package/dist/protocol/dist/public-identifiers.js +0 -38
  51. package/dist/protocol/dist/realtime/board-awareness.js +0 -119
  52. package/dist/protocol/dist/ui-command.js +0 -2
  53. package/dist/protocol/dist/work-promotion-stats.js +0 -11
  54. package/dist/protocol/dist/work-surface.js +0 -2
  55. package/dist/protocol/dist/work-view-stats.js +0 -3
  56. package/dist/protocol/dist/work.d.ts +0 -1
  57. /package/dist/protocol/dist/{board-upgrade.d.ts → app.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,9 +1,880 @@
1
- import { $ as BOARD_GEO_KINDS, A as buildWorkComposerChipSet, B as UI_COMMAND_MAX_TIMEOUT_MS, C as ensureRealtimeConnected, Ct as SkillsApi, D as WORK_SURFACE_READY_TIMEOUT_MS, Dt as CronJobsApi, E as WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH, Et as GenerationsApi, F as parseWorkComposerChipSet, G as UI_COMMAND_VERSION, H as UI_COMMAND_PENDING_TTL_SECONDS, I as parseWorkSurfaceReady, J as parseUiCommand, K as isTerminalUiCommandStatus, L as parseWorkSurfaceRequest, M as buildWorkSurfaceRequest, N as buildWorkSurfaceResponse, O as WORK_SURFACE_REQUEST_TIMEOUT_MS, Ot as ChannelsApi, P as parseWorkComposerChipClear, Q as BOARD_COLOR_IDS, R as parseWorkSurfaceResponse, S as createSessionPatchReducer, St as PublicAssetsApi, T as WORK_COMPOSER_CHIP_KEY_MAX_LENGTH, Tt as ModelsApi, U as UI_COMMAND_SETTLEMENT_GRACE_SECONDS, V as UI_COMMAND_PAYLOAD_MAX_BYTES, W as UI_COMMAND_TERMINAL_TTL_SECONDS, X as parseUsername, Y as parseSpaceSlug, Z as BoardAwarenessClientPayloadSchema, _ as buildSpacePath, _t as BOARD_BUILTIN_CAPABILITIES, a as ReferralsApi, at as BoardEffectSchema, b as parseAssistantMessageCommit, bt as ReferencesApi, c as UiCommandsApi, ct as BoardAuthoringItemSchema, d as BoardTransactionError, dt as BOARD_ANIMATION_CHANNEL_CAPABILITIES, et as BOARD_NATIVE_NODE_TYPES, f as SpaceClient, ft as BoardCompositionInputSchema, g as buildSpaceInvitePath, gt as parseBoardCompositionInput, h as PublicInviteApi, ht as BoardTrackSchema, i as WorksApi, it as validateBuiltinBoardClip, j as buildWorkSurfaceReady, k as buildWorkComposerChipClear, l as TasksApi, lt as BoardItemPatchSchema, m as SpacesApi, mt as BoardProceduralClipSchema, n as createHttpClient, nt as validateBoardNodeInput, o as UsersApi, ot as BoardPlaybackPolicySchema, p as SpacePublicFilesApi, pt as BoardCompositionSchema, q as isUiSurfaceMethod, r as WorkCommerceApi, rt as estimateBuiltinBoardClipCost, s as UserApi, st as parseBoardPlaybackPolicy, t as CohubHttpClient, tt as BOARD_NODE_CONTRACT, u as BoardClient, ut as BOARD_ANIMATION_CHANNELS, v as SessionGenerationStreamClient, vt as DEFAULT_BOARD_RENDER_LIMITS, w as WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES, wt as PromptsApi, x as SessionPatchReducer, xt as SearchApi, y as createSessionGenerationStreamClient, yt as SessionAccessApi, z as UI_COMMAND_DEFAULT_TIMEOUT_MS } from "./chunks/http.js";
2
- import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as REALTIME_DOMAINS, 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, x as REALTIME_ROOM_EVENT_NAME_PATTERN, y as isUuid } from "./chunks/transport.js";
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";
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";
5
5
  import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
6
6
  import { z } from "zod";
7
+ //#region ../protocol/dist/board.js
8
+ const BOARD_DOCUMENT_KIND = "cohub.board";
9
+ const BOARD_MANIFEST_KIND = "cohub.board.manifest";
10
+ const idSchema = z.string().min(1).max(160);
11
+ const jsonObjectSchema = z.record(z.string(), z.unknown());
12
+ const finiteSchema = z.number().finite();
13
+ z.object({
14
+ kind: z.literal(BOARD_MANIFEST_KIND),
15
+ version: z.literal(1),
16
+ boardId: z.string().uuid(),
17
+ title: z.string().min(1).max(255)
18
+ });
19
+ z.object({
20
+ centerX: finiteSchema,
21
+ centerY: finiteSchema,
22
+ zoom: finiteSchema.positive()
23
+ });
24
+ const BoardCameraFocusSchema = z.discriminatedUnion("type", [
25
+ z.object({
26
+ type: z.literal("rect"),
27
+ rect: z.object({
28
+ x: finiteSchema,
29
+ y: finiteSchema,
30
+ width: finiteSchema.positive(),
31
+ height: finiteSchema.positive()
32
+ })
33
+ }),
34
+ z.object({
35
+ type: z.literal("item"),
36
+ itemId: idSchema
37
+ }),
38
+ z.object({
39
+ type: z.literal("items"),
40
+ itemIds: z.array(idSchema).min(1).max(1e3)
41
+ }),
42
+ z.object({
43
+ type: z.literal("frame"),
44
+ frameId: idSchema
45
+ })
46
+ ]);
47
+ const BoardCameraFocusParamsSchema = z.object({
48
+ focus: BoardCameraFocusSchema,
49
+ fit: z.enum(["contain", "cover"]).default("contain"),
50
+ padding: finiteSchema.nonnegative().default(32),
51
+ minZoom: finiteSchema.positive().optional(),
52
+ maxZoom: finiteSchema.positive().optional()
53
+ }).superRefine((value, context) => {
54
+ if (value.minZoom !== void 0 && value.maxZoom !== void 0 && value.minZoom > value.maxZoom) context.addIssue({
55
+ code: "custom",
56
+ message: "minZoom must not exceed maxZoom",
57
+ path: ["minZoom"]
58
+ });
59
+ });
60
+ z.object({
61
+ nodeId: idSchema,
62
+ type: z.string().min(1).max(40),
63
+ parentId: idSchema.nullable(),
64
+ orderKey: z.string().max(4096).nullable(),
65
+ x: finiteSchema,
66
+ y: finiteSchema,
67
+ width: finiteSchema.positive(),
68
+ height: finiteSchema.positive(),
69
+ rotation: finiteSchema,
70
+ refKind: z.string().max(40).nullable(),
71
+ refPath: z.string().max(4096).nullable(),
72
+ refUrl: z.string().max(4096).nullable(),
73
+ view: jsonObjectSchema,
74
+ style: jsonObjectSchema,
75
+ data: jsonObjectSchema
76
+ });
77
+ const stripBoardServerFields = (value) => {
78
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
79
+ const { boardId: _boardId, revision: _revision, ...input } = value;
80
+ return input;
81
+ };
82
+ const BoardCreateEffectInputSchema = z.preprocess(stripBoardServerFields, BoardEffectInputSchema);
83
+ const BoardCreateCompositionInputSchema = z.preprocess(stripBoardServerFields, BoardCompositionInputSchema);
84
+ const BoardCreateInputSchema = z.object({
85
+ path: z.string().min(1),
86
+ /** Reused by clients when board creation is interrupted and retried. */
87
+ mutationId: z.string().max(128).optional(),
88
+ title: z.string().min(1).max(255).optional(),
89
+ metadata: jsonObjectSchema.optional(),
90
+ items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
91
+ connections: z.array(BoardConnectionSchema).max(5e4).optional(),
92
+ effects: z.array(BoardCreateEffectInputSchema).max(5e4).optional(),
93
+ compositions: z.array(BoardCreateCompositionInputSchema).max(5e4).optional()
94
+ });
95
+ const jsonSchema$1 = (schema) => z.toJSONSchema(schema);
96
+ const authoringSchemaFactories = {
97
+ item: () => jsonSchema$1(BoardAuthoringItemSchema),
98
+ itemPatch: () => jsonSchema$1(BoardItemPatchSchema),
99
+ mutation: () => jsonSchema$1(BoardSemanticMutationSchema),
100
+ composition: () => jsonSchema$1(BoardCompositionInputSchema),
101
+ effect: () => jsonSchema$1(BoardEffectInputSchema),
102
+ create: () => jsonSchema$1(BoardCreateInputSchema)
103
+ };
104
+ /**
105
+ * JSON Schemas for the semantic authoring surface. Each property is enumerable
106
+ * for Object.keys/JSON.stringify, but generated only when that property is
107
+ * requested. This keeps protocol imports cheap while capabilities still returns
108
+ * the complete machine-readable schema object.
109
+ */
110
+ const BOARD_AUTHORING_SCHEMAS = {};
111
+ for (const key of Object.keys(authoringSchemaFactories)) {
112
+ let cached;
113
+ Object.defineProperty(BOARD_AUTHORING_SCHEMAS, key, {
114
+ enumerable: true,
115
+ configurable: false,
116
+ get() {
117
+ cached ??= authoringSchemaFactories[key]();
118
+ return cached;
119
+ }
120
+ });
121
+ }
122
+ z.object({
123
+ include: z.array(z.enum([
124
+ "nodes",
125
+ "connections",
126
+ "effects",
127
+ "compositions",
128
+ "playback"
129
+ ])).optional(),
130
+ nodeIds: z.array(idSchema).max(5e4).optional(),
131
+ connectionIds: z.array(idSchema).max(5e4).optional(),
132
+ effectIds: z.array(idSchema).max(5e4).optional(),
133
+ compositionIds: z.array(idSchema).max(5e4).optional(),
134
+ viewport: z.object({
135
+ x: finiteSchema,
136
+ y: finiteSchema,
137
+ width: finiteSchema.positive(),
138
+ height: finiteSchema.positive()
139
+ }).optional()
140
+ });
141
+ /** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
142
+ const BoardPlaybackPolicySchema = z.object({
143
+ compositionId: idSchema,
144
+ /** Delay before the first local playback after opening the Board, in milliseconds. */
145
+ delayMs: finiteSchema.nonnegative().default(0)
146
+ });
147
+ function parseBoardPlaybackPolicy(metadata) {
148
+ const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
149
+ return parsed.success ? parsed.data : null;
150
+ }
151
+ z.discriminatedUnion("type", [
152
+ z.object({
153
+ commandId: idSchema,
154
+ type: z.literal("play"),
155
+ compositionId: idSchema,
156
+ position: finiteSchema.nonnegative().optional(),
157
+ timeScale: finiteSchema.positive().max(4).optional(),
158
+ shared: z.boolean().optional(),
159
+ seed: idSchema.optional()
160
+ }),
161
+ z.object({
162
+ commandId: idSchema,
163
+ type: z.literal("pause"),
164
+ playbackId: z.string().uuid()
165
+ }),
166
+ z.object({
167
+ commandId: idSchema,
168
+ type: z.literal("seek"),
169
+ playbackId: z.string().uuid(),
170
+ position: finiteSchema.nonnegative()
171
+ }),
172
+ z.object({
173
+ commandId: idSchema,
174
+ type: z.literal("stop"),
175
+ playbackId: z.string().uuid()
176
+ })
177
+ ]);
178
+ //#endregion
179
+ //#region ../protocol/dist/board-capability-registry.js
180
+ const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
181
+ new Set(BOARD_BUILTIN_EFFECT_KINDS);
182
+ const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
183
+ const finite = (value) => typeof value === "number" && Number.isFinite(value);
184
+ function validateBuiltinBoardClip(clip, path = "clip") {
185
+ if (!CLIPS.has(clip.kind)) return [];
186
+ const errors = [];
187
+ const error = (message, suffix, coordinateSpace, code = "INVALID_BOARD_CLIP") => {
188
+ errors.push({
189
+ severity: "error",
190
+ code,
191
+ message,
192
+ path: `${path}.${suffix}`,
193
+ ...coordinateSpace ? { coordinateSpace } : {}
194
+ });
195
+ };
196
+ if ((clip.kind.startsWith("motion.") || clip.kind.startsWith("draw.") || clip.kind === "text.reveal" || clip.kind === "effects.trail") && clip.target.type !== "item") error(`${clip.kind} must target an item`, "target");
197
+ if (clip.kind.startsWith("camera.") && clip.target.type !== "camera") error(`${clip.kind} must target the camera`, "target");
198
+ if (clip.kind === "motion.path") {
199
+ const points = clip.params.points;
200
+ 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");
201
+ 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");
202
+ }
203
+ if (clip.kind === "effects.particles") {
204
+ const count = clip.params.count;
205
+ 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");
206
+ const bounds = clip.params.bounds;
207
+ 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");
208
+ }
209
+ if (clip.kind === "camera.focus") {
210
+ const parsed = BoardCameraFocusParamsSchema.safeParse(clip.params);
211
+ if (!parsed.success) error(parsed.error.issues[0]?.message ?? "invalid camera focus", "params", "world");
212
+ }
213
+ return errors;
214
+ }
215
+ function estimateBuiltinBoardClipCost(clip) {
216
+ switch (clip.kind) {
217
+ case "effects.particles": {
218
+ const count = Number.isSafeInteger(clip.params.count) ? Math.max(0, clip.params.count) : 0;
219
+ return {
220
+ particles: count,
221
+ vertices: count * 4,
222
+ dynamicVertices: count * 4,
223
+ drawCalls: 1,
224
+ bufferBytes: count * 48,
225
+ simulationSteps: count
226
+ };
227
+ }
228
+ case "effects.trail": return {
229
+ vertices: 32,
230
+ dynamicVertices: 32,
231
+ drawCalls: 1,
232
+ bufferBytes: 1024,
233
+ simulationSteps: 16
234
+ };
235
+ case "effects.impact":
236
+ case "effects.flash": return {
237
+ vertices: 64,
238
+ drawCalls: 1
239
+ };
240
+ case "draw.reveal":
241
+ case "draw.handwrite": return {
242
+ drawCalls: 1,
243
+ dynamicVertices: 1
244
+ };
245
+ case "motion.path": return { simulationSteps: Array.isArray(clip.params.points) ? clip.params.points.length : 0 };
246
+ default: return {};
247
+ }
248
+ }
249
+ //#endregion
250
+ //#region ../protocol/dist/board-url.js
251
+ const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
252
+ function parseIpv4(host) {
253
+ const parts = host.split(".").map(Number);
254
+ return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
255
+ }
256
+ function isBlockedIpv4(host) {
257
+ const parts = parseIpv4(host);
258
+ if (!parts) return false;
259
+ const [first, second, third] = parts;
260
+ if (first === 0 || first === 10 || first === 127) return true;
261
+ if (first === 100 && second >= 64 && second <= 127) return true;
262
+ if (first === 169 && second === 254) return true;
263
+ if (first === 172 && second >= 16 && second <= 31) return true;
264
+ if (first === 192 && second === 168) return true;
265
+ if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
266
+ if (first === 192 && second === 88 && third === 99) return true;
267
+ if (first === 198 && (second === 18 || second === 19)) return true;
268
+ if (first === 198 && second === 51 && third === 100) return true;
269
+ if (first === 203 && second === 0 && third === 113) return true;
270
+ return first >= 224;
271
+ }
272
+ function expandIpv6(host) {
273
+ const [head, tail, extra] = host.toLowerCase().split("::");
274
+ if (extra !== void 0) return null;
275
+ const headParts = head ? head.split(":").filter(Boolean) : [];
276
+ const tailParts = tail ? tail.split(":").filter(Boolean) : [];
277
+ const missing = 8 - headParts.length - tailParts.length;
278
+ if (missing < 0 || tail === void 0 && missing !== 0) return null;
279
+ const parts = [
280
+ ...headParts,
281
+ ...Array.from({ length: missing }, () => "0"),
282
+ ...tailParts
283
+ ];
284
+ if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
285
+ return parts.map((part) => part.padStart(4, "0"));
286
+ }
287
+ function isBlockedIpv6(host) {
288
+ const parts = expandIpv6(host);
289
+ if (!parts) return true;
290
+ if (parts.every((part) => part === "0000")) return true;
291
+ if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
292
+ if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
293
+ const high = Number.parseInt(parts[6] ?? "0", 16);
294
+ const low = Number.parseInt(parts[7] ?? "0", 16);
295
+ return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
296
+ }
297
+ if (parts.slice(0, 6).every((part) => part === "0000")) return true;
298
+ const first = Number.parseInt(parts[0] ?? "0", 16);
299
+ if ((first & 65024) === 64512) return true;
300
+ if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
301
+ if ((first & 65280) === 65280) return true;
302
+ return parts[0] === "2001" && parts[1] === "0db8";
303
+ }
304
+ function isPublicBoardRemoteAddress(value) {
305
+ const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
306
+ if (parseIpv4(address)) return !isBlockedIpv4(address);
307
+ return address.includes(":") && !isBlockedIpv6(address);
308
+ }
309
+ function isBlockedHost(hostname) {
310
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
311
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
312
+ if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
313
+ return false;
314
+ }
315
+ /**
316
+ * Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
317
+ * addresses; any future server-side fetcher must additionally validate DNS
318
+ * resolution to defend against rebinding.
319
+ */
320
+ function normalizeBoardRemoteUrl(value) {
321
+ if (typeof value !== "string") return void 0;
322
+ const input = value.trim();
323
+ if (!input || input.length > 4096) return void 0;
324
+ try {
325
+ const url = new URL(input);
326
+ if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
327
+ if (url.username || url.password || isBlockedHost(url.hostname)) return;
328
+ const normalized = url.toString();
329
+ return normalized.length <= 4096 ? normalized : void 0;
330
+ } catch {
331
+ return;
332
+ }
333
+ }
334
+ const BoardRemoteUrlSchema = z.string().max(BOARD_REMOTE_URL_MAX_LENGTH).refine((value) => normalizeBoardRemoteUrl(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
335
+ //#endregion
336
+ //#region ../protocol/dist/board-document.js
337
+ const BoardFrameSchema = z.object({
338
+ x: z.number().finite(),
339
+ y: z.number().finite(),
340
+ width: z.number().finite().positive(),
341
+ height: z.number().finite().positive(),
342
+ rotation: z.number().finite().default(0)
343
+ });
344
+ /**
345
+ * The board camera. This is local UI state, not synced content: semantic ops
346
+ * (see diffBoardDocuments) never describe the viewport, and the editor holds
347
+ * the live camera separately from the persisted document. Here it only serves
348
+ * as an initial camera hint when a document is first loaded.
349
+ */
350
+ const BoardViewportSchema = z.object({
351
+ x: z.number().finite(),
352
+ y: z.number().finite(),
353
+ zoom: z.number().finite().min(.05).max(8)
354
+ });
355
+ const BoardAppearanceSchema = z.object({
356
+ theme: z.string().min(1).default("clean"),
357
+ background: z.object({
358
+ kind: z.enum([
359
+ "solid",
360
+ "dots",
361
+ "grid",
362
+ "image",
363
+ "shader",
364
+ "custom"
365
+ ]).default("dots"),
366
+ color: z.string().optional(),
367
+ imageUrl: BoardRemoteUrlSchema.optional(),
368
+ fit: z.enum([
369
+ "cover",
370
+ "contain",
371
+ "repeat"
372
+ ]).optional(),
373
+ position: z.enum([
374
+ "center",
375
+ "top",
376
+ "bottom",
377
+ "left",
378
+ "right"
379
+ ]).optional(),
380
+ opacity: z.number().finite().min(0).max(1).optional()
381
+ }).default({ kind: "solid" }),
382
+ grid: z.object({
383
+ visible: z.boolean().default(false),
384
+ size: z.number().finite().min(4).default(24),
385
+ opacity: z.number().finite().min(0).max(1).default(.12)
386
+ }).default({
387
+ visible: false,
388
+ size: 24,
389
+ opacity: .12
390
+ }),
391
+ mood: z.enum([
392
+ "clean",
393
+ "playful",
394
+ "arcane",
395
+ "cyber",
396
+ "natural"
397
+ ]).default("clean")
398
+ });
399
+ /** Optional visual chrome still carried by a few older shapes. */
400
+ const BoardItemStyleSchema = z.object({
401
+ variant: z.string().min(1).default("default"),
402
+ theme: z.string().min(1).optional(),
403
+ accentColor: z.string().optional(),
404
+ size: z.enum([
405
+ "sm",
406
+ "md",
407
+ "lg"
408
+ ]).default("md"),
409
+ emphasis: z.enum([
410
+ "normal",
411
+ "rare",
412
+ "epic",
413
+ "legendary"
414
+ ]).default("normal"),
415
+ effects: z.array(z.string().min(1)).default([])
416
+ });
417
+ const SpaceFileRefSchema = z.object({
418
+ kind: z.literal("space-file"),
419
+ path: z.string().min(1)
420
+ });
421
+ const BoardMediaSnapshotSchema = z.object({
422
+ title: z.string().optional(),
423
+ mimeType: z.string().optional(),
424
+ size: z.number().finite().nonnegative().optional(),
425
+ mtimeMs: z.number().finite().nonnegative().optional(),
426
+ /** Intrinsic pixel size once known. */
427
+ naturalWidth: z.number().finite().positive().optional(),
428
+ naturalHeight: z.number().finite().positive().optional()
429
+ });
430
+ const BoardItemBaseSchema = z.object({
431
+ id: z.string().min(1),
432
+ frame: BoardFrameSchema,
433
+ parentId: z.string().min(1).nullable().optional(),
434
+ /** When true the shape cannot be moved, resized, or deleted. */
435
+ locked: z.boolean().optional(),
436
+ style: BoardItemStyleSchema.optional(),
437
+ metadata: z.record(z.string(), z.unknown()).optional()
438
+ });
439
+ /** Freestanding text — no card chrome; its frame always follows the glyphs. */
440
+ const BoardTextItemSchema = BoardItemBaseSchema.extend({
441
+ type: z.literal("text"),
442
+ text: z.string().default(""),
443
+ color: z.string().min(1).default("neutral"),
444
+ fontSize: z.number().finite().min(2).max(512).default(24)
445
+ });
446
+ const BoardGeoItemSchema = BoardItemBaseSchema.extend({
447
+ type: z.literal("geo"),
448
+ geo: z.string().min(1).default("rectangle"),
449
+ text: z.string().default(""),
450
+ color: z.string().min(1).default("brand"),
451
+ fillOpacity: z.number().finite().min(0).max(1).default(0)
452
+ });
453
+ /** A raw freehand sample. Pressure defaults to 0.5 (mouse) when absent. */
454
+ const DrawPointSchema = z.object({
455
+ x: z.number().finite(),
456
+ y: z.number().finite(),
457
+ p: z.number().finite().min(0).max(1).default(.5)
458
+ });
459
+ /** A world-space point. */
460
+ const BoardPointSchema = z.object({
461
+ x: z.number().finite(),
462
+ y: z.number().finite()
463
+ });
464
+ const BoardDrawItemSchema = BoardItemBaseSchema.extend({
465
+ type: z.literal("draw"),
466
+ points: z.array(DrawPointSchema).default([]),
467
+ color: z.string().min(1).default("brand"),
468
+ size: z.number().finite().positive().default(4)
469
+ });
470
+ /**
471
+ * A free arrow — a standalone annotation stroke between two world points.
472
+ *
473
+ * Arrows do not relate nodes. A relation between two nodes is a
474
+ * `BoardConnection`, which is stored separately and resolves its geometry from
475
+ * the live node frames. Keeping the two apart is what lets an arrow be a plain
476
+ * shape (its own frame, freely movable) while a connection stays purely
477
+ * semantic — neither has to pretend to be the other.
478
+ */
479
+ const BoardArrowItemSchema = BoardItemBaseSchema.extend({
480
+ type: z.literal("arrow"),
481
+ start: BoardPointSchema,
482
+ end: BoardPointSchema,
483
+ bend: z.number().finite().min(-.85).max(.85).default(0),
484
+ color: z.string().min(1).default("brand"),
485
+ size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
486
+ arrowStart: z.boolean().default(false),
487
+ arrowEnd: z.boolean().default(true),
488
+ label: z.string().default("")
489
+ });
490
+ /** A frame container for organising shapes. */
491
+ const BoardFrameItemSchema = BoardItemBaseSchema.extend({
492
+ type: z.literal("frame"),
493
+ label: z.string().default("Frame"),
494
+ color: z.string().min(1).default("neutral")
495
+ });
496
+ /** Image node — space file only, natural aspect, no chrome. */
497
+ const BoardImageItemSchema = BoardItemBaseSchema.extend({
498
+ type: z.literal("image"),
499
+ ref: SpaceFileRefSchema,
500
+ snapshot: BoardMediaSnapshotSchema.optional(),
501
+ /** Optional normalized crop in source image space (0..1). */
502
+ crop: z.object({
503
+ x: z.number().finite().min(0).max(1).default(0),
504
+ y: z.number().finite().min(0).max(1).default(0),
505
+ w: z.number().finite().min(0).max(1).default(1),
506
+ h: z.number().finite().min(0).max(1).default(1)
507
+ }).optional()
508
+ });
509
+ /** Video node — space file only. Playback state is local UI, never synced. */
510
+ const BoardVideoItemSchema = BoardItemBaseSchema.extend({
511
+ type: z.literal("video"),
512
+ ref: SpaceFileRefSchema,
513
+ snapshot: BoardMediaSnapshotSchema.optional()
514
+ });
515
+ /** Audio node — space file only. Playback state is local UI, never synced. */
516
+ const BoardAudioItemSchema = BoardItemBaseSchema.extend({
517
+ type: z.literal("audio"),
518
+ ref: SpaceFileRefSchema,
519
+ snapshot: BoardMediaSnapshotSchema.extend({ durationMs: z.number().finite().nonnegative().optional() }).optional()
520
+ });
521
+ /**
522
+ * Cached display facts for a file card.
523
+ *
524
+ * Purely derived from the referenced file and versioned by `mtimeMs`, so it is a
525
+ * cache and never a second source of truth: the workspace file remains
526
+ * authoritative, and a stale snapshot is detectable rather than silently wrong.
527
+ * It is stored so that opening a board renders complete cards immediately, with
528
+ * no per-node file read on the first paint.
529
+ */
530
+ const BoardFileSnapshotSchema = z.object({
531
+ title: z.string().optional(),
532
+ mimeType: z.string().optional(),
533
+ size: z.number().finite().nonnegative().optional(),
534
+ mtimeMs: z.number().finite().nonnegative().optional(),
535
+ /** Cleaned leading prose. Capped when written (see FILE_EXCERPT_MAX_CHARS). */
536
+ excerpt: z.string().optional(),
537
+ /** Cover image inside the space, already resolved to a workspace path. */
538
+ coverPath: z.string().optional(),
539
+ /** Cover image at an absolute https URL, as declared by the file itself. */
540
+ coverUrl: z.string().optional()
541
+ });
542
+ /**
543
+ * File node — a thumbnail entry point to any workspace file.
544
+ *
545
+ * This is the fallback for every dropped file that is not natively an image or
546
+ * video, including binaries and unknown extensions: a board should never refuse
547
+ * a file, only present it with less detail. Presentation tiers are derived from
548
+ * the snapshot (see filePreviewKind), not stored, so there is no display state
549
+ * to drift from the facts.
550
+ */
551
+ const BoardFileItemSchema = BoardItemBaseSchema.extend({
552
+ type: z.literal("file"),
553
+ ref: SpaceFileRefSchema,
554
+ snapshot: BoardFileSnapshotSchema.optional()
555
+ });
556
+ const BoardTaskMediaArtifactFields = {
557
+ id: z.string().min(1).max(240),
558
+ title: z.string().max(240).optional(),
559
+ url: BoardRemoteUrlSchema,
560
+ mimeType: z.string().max(160).optional()
561
+ };
562
+ const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
563
+ z.object({
564
+ ...BoardTaskMediaArtifactFields,
565
+ type: z.literal("image"),
566
+ naturalWidth: z.number().positive().optional(),
567
+ naturalHeight: z.number().positive().optional()
568
+ }).strict(),
569
+ z.object({
570
+ ...BoardTaskMediaArtifactFields,
571
+ type: z.literal("video"),
572
+ previewUrl: BoardRemoteUrlSchema.optional(),
573
+ durationMs: z.number().int().positive().optional(),
574
+ naturalWidth: z.number().positive().optional(),
575
+ naturalHeight: z.number().positive().optional()
576
+ }).strict(),
577
+ z.object({
578
+ ...BoardTaskMediaArtifactFields,
579
+ type: z.literal("audio"),
580
+ previewUrl: BoardRemoteUrlSchema.optional(),
581
+ durationMs: z.number().int().positive().optional()
582
+ }).strict(),
583
+ z.object({
584
+ id: z.string().min(1).max(240),
585
+ type: z.literal("text"),
586
+ title: z.string().max(240).optional(),
587
+ textExcerpt: z.string().min(1).max(480)
588
+ }).strict()
589
+ ]);
590
+ /**
591
+ * Cached task facts used for an immediate first paint. The task run remains the
592
+ * source of truth and live clients refresh this projection by `taskRunId`.
593
+ */
594
+ const BoardTaskSnapshotSchema = z.object({
595
+ taskType: z.string().min(1).max(120),
596
+ status: z.enum([
597
+ "pending",
598
+ "running",
599
+ "completed",
600
+ "failed"
601
+ ]),
602
+ title: z.string().min(1).max(240),
603
+ model: z.string().max(160).optional(),
604
+ promptExcerpt: z.string().max(480).optional(),
605
+ artifactCount: z.number().int().nonnegative(),
606
+ artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
607
+ updatedAt: z.string().optional()
608
+ }).strict();
609
+ /** A stable reference to a task run with a small, replaceable display cache. */
610
+ const BoardTaskItemSchema = BoardItemBaseSchema.extend({
611
+ type: z.literal("task"),
612
+ taskRunId: z.string().min(1),
613
+ snapshot: BoardTaskSnapshotSchema
614
+ });
615
+ /**
616
+ * A forward-compatible carrier for shape types this client does not recognise.
617
+ * Its discriminant is the literal `"unknown"` so the item union still narrows
618
+ * cleanly on `type`. The real type string and every original field are preserved
619
+ * verbatim in `raw`.
620
+ */
621
+ const UNKNOWN_BOARD_ITEM_TYPE = "unknown";
622
+ const BoardItemSchema = z.any().transform((raw) => parseBoardItemLoose(raw));
623
+ /**
624
+ * Parse a single item leniently: known types are validated and normalised;
625
+ * anything else becomes a lossless unknown item. Never throws on shape data —
626
+ * a malformed known item degrades to unknown rather than failing the document.
627
+ */
628
+ function parseBoardItemLoose(raw) {
629
+ if (!raw || typeof raw !== "object") return makeUnknownItem(raw);
630
+ switch (raw.type) {
631
+ case "image": {
632
+ const parsed = BoardImageItemSchema.safeParse(raw);
633
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
634
+ }
635
+ case "video": {
636
+ const parsed = BoardVideoItemSchema.safeParse(raw);
637
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
638
+ }
639
+ case "audio": {
640
+ const parsed = BoardAudioItemSchema.safeParse(raw);
641
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
642
+ }
643
+ case "file": {
644
+ const parsed = BoardFileItemSchema.safeParse(raw);
645
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
646
+ }
647
+ case "task": {
648
+ const parsed = BoardTaskItemSchema.safeParse(raw);
649
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
650
+ }
651
+ case "text": {
652
+ const parsed = BoardTextItemSchema.safeParse(raw);
653
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
654
+ }
655
+ case "geo": {
656
+ const parsed = BoardGeoItemSchema.safeParse(raw);
657
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
658
+ }
659
+ case "draw": {
660
+ const parsed = BoardDrawItemSchema.safeParse(raw);
661
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
662
+ }
663
+ case "arrow": {
664
+ const parsed = BoardArrowItemSchema.safeParse(raw);
665
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
666
+ }
667
+ case "frame": {
668
+ const parsed = BoardFrameItemSchema.safeParse(raw);
669
+ return parsed.success ? parsed.data : makeUnknownItem(raw);
670
+ }
671
+ default: return makeUnknownItem(raw);
672
+ }
673
+ }
674
+ function makeUnknownItem(raw) {
675
+ const record = raw && typeof raw === "object" ? raw : {};
676
+ const frameParsed = BoardFrameSchema.safeParse(record.frame);
677
+ const styleParsed = BoardItemStyleSchema.safeParse(record.style);
678
+ return {
679
+ id: typeof record.id === "string" && record.id ? record.id : "unknown",
680
+ type: UNKNOWN_BOARD_ITEM_TYPE,
681
+ frame: frameParsed.success ? frameParsed.data : {
682
+ x: 0,
683
+ y: 0,
684
+ width: 120,
685
+ height: 80,
686
+ rotation: 0
687
+ },
688
+ ...typeof record.parentId === "string" || record.parentId === null ? { parentId: record.parentId } : {},
689
+ ...record.locked === true ? { locked: true } : {},
690
+ ...styleParsed.success && record.style ? { style: styleParsed.data } : {},
691
+ ...record.metadata && typeof record.metadata === "object" ? { metadata: record.metadata } : {},
692
+ raw: record
693
+ };
694
+ }
695
+ z.object({
696
+ kind: z.literal(BOARD_DOCUMENT_KIND),
697
+ version: z.literal(1),
698
+ appearance: BoardAppearanceSchema.default({
699
+ theme: "clean",
700
+ background: { kind: "solid" },
701
+ grid: {
702
+ visible: false,
703
+ size: 24,
704
+ opacity: .12
705
+ },
706
+ mood: "clean"
707
+ }),
708
+ viewport: BoardViewportSchema,
709
+ items: z.array(BoardItemSchema),
710
+ /**
711
+ * Node relations. Separate from `items` because a connection has no frame of
712
+ * its own — its geometry is derived from the nodes it joins, so it is a
713
+ * relation over the item set rather than a member of it.
714
+ *
715
+ * Connections referencing a missing node are dropped on parse: a relation to
716
+ * nothing is not a relation, and keeping one would let an invisible dangling
717
+ * edge accumulate silently. Callers that need to know write through the
718
+ * transaction API, which reports the reference error instead.
719
+ */
720
+ connections: z.array(BoardConnectionSchema).default([])
721
+ });
722
+ //#endregion
723
+ //#region ../protocol/dist/board-node.js
724
+ const BOARD_COLOR_IDS = [
725
+ "brand",
726
+ "neutral",
727
+ "black",
728
+ "white",
729
+ "blue",
730
+ "green",
731
+ "amber",
732
+ "violet",
733
+ "rose"
734
+ ];
735
+ const BoardColorIdSchema = z.enum(BOARD_COLOR_IDS);
736
+ const BOARD_GEO_KINDS = [
737
+ "rectangle",
738
+ "rounded",
739
+ "ellipse",
740
+ "diamond",
741
+ "triangle"
742
+ ];
743
+ const BoardGeoKindSchema = z.enum(BOARD_GEO_KINDS);
744
+ const BOARD_NATIVE_NODE_TYPES = [
745
+ "image",
746
+ "video",
747
+ "audio",
748
+ "file",
749
+ "task",
750
+ "text",
751
+ "geo",
752
+ "draw",
753
+ "arrow",
754
+ "frame"
755
+ ];
756
+ const metadataSchema = z.record(z.string(), z.unknown());
757
+ const commonData = {
758
+ locked: z.boolean().optional(),
759
+ metadata: metadataSchema.optional()
760
+ };
761
+ const pointSchema = z.object({
762
+ x: z.number().finite(),
763
+ y: z.number().finite(),
764
+ p: z.number().finite().min(0).max(1).default(.5)
765
+ }).strict();
766
+ const worldPointSchema = z.object({
767
+ x: z.number().finite(),
768
+ y: z.number().finite()
769
+ }).strict();
770
+ const strokeSizeSchema = z.number().finite().min(1).max(64);
771
+ const mediaViewSchema = z.object({
772
+ title: z.string().optional(),
773
+ mimeType: z.string().optional(),
774
+ size: z.number().finite().nonnegative().optional(),
775
+ mtimeMs: z.number().finite().nonnegative().optional(),
776
+ naturalWidth: z.number().finite().positive().optional(),
777
+ naturalHeight: z.number().finite().positive().optional()
778
+ }).strict();
779
+ const audioViewSchema = mediaViewSchema.extend({ durationMs: z.number().finite().nonnegative().optional() });
780
+ const dataSchemas = {
781
+ text: z.object({
782
+ ...commonData,
783
+ text: z.string().default(""),
784
+ color: BoardColorIdSchema.default("neutral"),
785
+ fontSize: z.number().finite().min(2).max(512).default(24)
786
+ }).strict(),
787
+ geo: z.object({
788
+ ...commonData,
789
+ geo: BoardGeoKindSchema.default("rectangle"),
790
+ text: z.string().default(""),
791
+ color: BoardColorIdSchema.default("brand"),
792
+ fillOpacity: z.number().finite().min(0).max(1).default(0)
793
+ }).strict(),
794
+ draw: z.object({
795
+ ...commonData,
796
+ points: z.array(pointSchema).min(1),
797
+ color: BoardColorIdSchema.default("brand"),
798
+ size: strokeSizeSchema.default(4)
799
+ }).strict(),
800
+ arrow: z.object({
801
+ ...commonData,
802
+ start: worldPointSchema,
803
+ end: worldPointSchema,
804
+ bend: z.number().finite().min(-.85).max(.85).default(0),
805
+ color: BoardColorIdSchema.default("brand"),
806
+ size: strokeSizeSchema.default(BOARD_ARROW_STROKE_SIZE),
807
+ arrowStart: z.boolean().default(false),
808
+ arrowEnd: z.boolean().default(true),
809
+ label: z.string().default("")
810
+ }).strict(),
811
+ frame: z.object({
812
+ ...commonData,
813
+ label: z.string().default("Frame"),
814
+ color: BoardColorIdSchema.default("neutral")
815
+ }).strict(),
816
+ image: z.object({
817
+ ...commonData,
818
+ crop: z.object({
819
+ x: z.number().finite().min(0).max(1),
820
+ y: z.number().finite().min(0).max(1),
821
+ w: z.number().finite().min(0).max(1),
822
+ h: z.number().finite().min(0).max(1)
823
+ }).strict().optional()
824
+ }).strict(),
825
+ video: z.object(commonData).strict(),
826
+ audio: z.object(commonData).strict(),
827
+ file: z.object(commonData).strict(),
828
+ task: z.object({
829
+ ...commonData,
830
+ taskRunId: z.string().min(1)
831
+ }).strict()
832
+ };
833
+ const fileViewSchema = z.object({
834
+ title: z.string().optional(),
835
+ mimeType: z.string().optional(),
836
+ size: z.number().finite().nonnegative().optional(),
837
+ mtimeMs: z.number().finite().nonnegative().optional(),
838
+ excerpt: z.string().optional(),
839
+ coverPath: z.string().optional(),
840
+ coverUrl: z.string().url().optional()
841
+ }).strict();
842
+ const taskViewSchema = BoardTaskSnapshotSchema;
843
+ const emptyViewSchema = z.object({}).strict();
844
+ const viewSchemas = {
845
+ image: mediaViewSchema,
846
+ video: mediaViewSchema,
847
+ audio: audioViewSchema,
848
+ file: fileViewSchema,
849
+ task: taskViewSchema,
850
+ text: emptyViewSchema,
851
+ geo: emptyViewSchema,
852
+ draw: emptyViewSchema,
853
+ arrow: emptyViewSchema,
854
+ frame: emptyViewSchema
855
+ };
856
+ const nodeEnvelopeSchema = z.object({
857
+ nodeId: z.string().min(1).max(160),
858
+ type: z.string().min(1).max(40),
859
+ parentId: z.string().min(1).max(160).nullable(),
860
+ orderKey: z.string().max(4096).nullable(),
861
+ x: z.number().finite(),
862
+ y: z.number().finite(),
863
+ width: z.number().finite().positive(),
864
+ height: z.number().finite().positive(),
865
+ rotation: z.number().finite(),
866
+ refKind: z.string().max(40).nullable(),
867
+ refPath: z.string().max(4096).nullable(),
868
+ refUrl: z.string().max(4096).nullable(),
869
+ view: z.record(z.string(), z.unknown()),
870
+ style: z.record(z.string(), z.unknown()),
871
+ data: z.record(z.string(), z.unknown())
872
+ }).strict();
873
+ function jsonSchema(schema) {
874
+ return z.toJSONSchema(schema);
875
+ }
876
+ jsonSchema(nodeEnvelopeSchema), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]));
877
+ //#endregion
7
878
  //#region ../protocol/dist/generation/catalog.js
8
879
  function isGenerationModelHidden(model) {
9
880
  return model.hidden === true;
@@ -21,14 +892,14 @@ var GenerationPolicyError = class extends Error {
21
892
  this.name = "GenerationPolicyError";
22
893
  }
23
894
  };
24
- function isRecord(value) {
895
+ function isRecord$1(value) {
25
896
  return !!value && typeof value === "object" && !Array.isArray(value);
26
897
  }
27
898
  function isPrimitiveEnumValue(value) {
28
899
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
29
900
  }
30
901
  function normalizeConstraint(value) {
31
- if (!isRecord(value) || typeof value.kind !== "string") return null;
902
+ if (!isRecord$1(value) || typeof value.kind !== "string") return null;
32
903
  if (value.kind === "enum") {
33
904
  if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
34
905
  return {
@@ -58,7 +929,7 @@ function normalizeConstraint(value) {
58
929
  return null;
59
930
  }
60
931
  function normalizeGenerationPolicy(value) {
61
- if (!isRecord(value) || value.version !== 1) return null;
932
+ if (!isRecord$1(value) || value.version !== 1) return null;
62
933
  if (value.mode === "auto") return {
63
934
  version: 1,
64
935
  mode: "auto"
@@ -66,10 +937,10 @@ function normalizeGenerationPolicy(value) {
66
937
  if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
67
938
  const models = [];
68
939
  for (const item of value.models) {
69
- if (!isRecord(item) || typeof item.model !== "string" || !item.model.trim()) return null;
940
+ if (!isRecord$1(item) || typeof item.model !== "string" || !item.model.trim()) return null;
70
941
  const modelPolicy = { model: item.model.trim() };
71
942
  if (item.parameters !== void 0) {
72
- if (!isRecord(item.parameters)) return null;
943
+ if (!isRecord$1(item.parameters)) return null;
73
944
  const parameters = {};
74
945
  for (const [key, rawConstraint] of Object.entries(item.parameters)) {
75
946
  if (!key.trim()) return null;
@@ -244,6 +1115,181 @@ function filterGenerationDeclarationsByPolicy(declarations, policy) {
244
1115
  });
245
1116
  }
246
1117
  //#endregion
1118
+ //#region ../protocol/dist/public-identifiers.js
1119
+ const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
1120
+ const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
1121
+ /**
1122
+ * Platform-owned path segments that must not be newly assigned to public
1123
+ * identities. Existing stored values remain readable through parse helpers.
1124
+ */
1125
+ const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
1126
+ "admin",
1127
+ "api",
1128
+ "assets",
1129
+ "auth",
1130
+ "callback",
1131
+ "changelog",
1132
+ "docs",
1133
+ "explore",
1134
+ "invite",
1135
+ "landing",
1136
+ "login",
1137
+ "logout",
1138
+ "new",
1139
+ "org",
1140
+ "pricing",
1141
+ "pwa",
1142
+ "referrals",
1143
+ "sessions",
1144
+ "settings",
1145
+ "spaces",
1146
+ "static",
1147
+ "teams",
1148
+ "trending",
1149
+ "u",
1150
+ "user",
1151
+ "users",
1152
+ "work-auth",
1153
+ "app-auth"
1154
+ ]);
1155
+ new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
1156
+ [...RESERVED_PLATFORM_PATH_SEGMENTS];
1157
+ function parseUsername(value) {
1158
+ if (value === null || value === void 0) return null;
1159
+ const normalized = value.trim().toLowerCase();
1160
+ return USERNAME_PATTERN.test(normalized) ? normalized : null;
1161
+ }
1162
+ function parseSpaceSlug(value) {
1163
+ if (value === null || value === void 0) return null;
1164
+ const normalized = value.trim();
1165
+ return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
1166
+ }
1167
+ //#endregion
1168
+ //#region ../protocol/dist/app-surface.js
1169
+ const APP_SURFACE_PROTOCOL = "cohub.app.surface";
1170
+ const APP_SURFACE_READY_TIMEOUT_MS = 1e4;
1171
+ const APP_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
1172
+ const APP_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
1173
+ const APP_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
1174
+ const APP_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
1175
+ const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
1176
+ const isSurfaceEnvelope = (value) => isRecord(value) && value.protocol === "cohub.app.surface" && value.version === 1;
1177
+ const parseAppSurfaceReady = (value) => {
1178
+ if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
1179
+ const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
1180
+ return {
1181
+ protocol: APP_SURFACE_PROTOCOL,
1182
+ version: 1,
1183
+ type: "ready",
1184
+ methods
1185
+ };
1186
+ };
1187
+ const parseAppSurfaceResponse = (value) => {
1188
+ if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
1189
+ if (typeof value.requestId !== "string" || !value.requestId) return null;
1190
+ const error = isRecord(value.error) ? {
1191
+ code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
1192
+ message: typeof value.error.message === "string" ? value.error.message : "App surface call failed"
1193
+ } : void 0;
1194
+ return {
1195
+ protocol: APP_SURFACE_PROTOCOL,
1196
+ version: 1,
1197
+ type: "response",
1198
+ requestId: value.requestId,
1199
+ ok: value.ok === true,
1200
+ ...error ? { error } : {}
1201
+ };
1202
+ };
1203
+ const parseComposerChipKey = (value) => {
1204
+ if (typeof value !== "string") return null;
1205
+ const key = value.trim();
1206
+ if (!key || key.length > 80) return null;
1207
+ return key;
1208
+ };
1209
+ const parseAppComposerChipSet = (value) => {
1210
+ if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord(value.chip)) return null;
1211
+ const key = parseComposerChipKey(value.chip.key);
1212
+ if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
1213
+ const label = value.chip.label.trim();
1214
+ if (!label || label.length > 120) return null;
1215
+ if (!value.chip.content.trim()) return null;
1216
+ if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
1217
+ return {
1218
+ protocol: APP_SURFACE_PROTOCOL,
1219
+ version: 1,
1220
+ type: "composer.chip.set",
1221
+ chip: {
1222
+ key,
1223
+ label,
1224
+ content: value.chip.content
1225
+ }
1226
+ };
1227
+ };
1228
+ const parseAppComposerChipClear = (value) => {
1229
+ if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
1230
+ const key = parseComposerChipKey(value.key);
1231
+ return key ? {
1232
+ protocol: APP_SURFACE_PROTOCOL,
1233
+ version: 1,
1234
+ type: "composer.chip.clear",
1235
+ key
1236
+ } : null;
1237
+ };
1238
+ const parseAppSurfaceRequest = (value) => {
1239
+ if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
1240
+ if (typeof value.requestId !== "string" || !value.requestId) return null;
1241
+ if (typeof value.method !== "string" || !value.method) return null;
1242
+ const commandId = parseDesktopCommandId(value.commandId);
1243
+ if (!commandId) return null;
1244
+ return {
1245
+ protocol: APP_SURFACE_PROTOCOL,
1246
+ version: 1,
1247
+ type: "request",
1248
+ requestId: value.requestId,
1249
+ method: value.method,
1250
+ ...value.input === void 0 ? {} : { input: value.input },
1251
+ commandId
1252
+ };
1253
+ };
1254
+ const buildAppSurfaceReady = (methods) => ({
1255
+ protocol: APP_SURFACE_PROTOCOL,
1256
+ version: 1,
1257
+ type: "ready",
1258
+ methods: [...methods]
1259
+ });
1260
+ const buildAppSurfaceRequest = (input) => ({
1261
+ protocol: APP_SURFACE_PROTOCOL,
1262
+ version: 1,
1263
+ type: "request",
1264
+ ...input
1265
+ });
1266
+ const buildAppSurfaceResponse = (input) => ({
1267
+ protocol: APP_SURFACE_PROTOCOL,
1268
+ version: 1,
1269
+ type: "response",
1270
+ ...input
1271
+ });
1272
+ const buildAppComposerChipSet = (chip) => ({
1273
+ protocol: APP_SURFACE_PROTOCOL,
1274
+ version: 1,
1275
+ type: "composer.chip.set",
1276
+ chip
1277
+ });
1278
+ const buildAppComposerChipClear = (key) => ({
1279
+ protocol: APP_SURFACE_PROTOCOL,
1280
+ version: 1,
1281
+ type: "composer.chip.clear",
1282
+ key
1283
+ });
1284
+ //#endregion
1285
+ //#region ../protocol/dist/app-runtime.js
1286
+ const APP_RUNTIME_PROTOCOL = "cohub.app.runtime";
1287
+ const buildAppRuntimeReady = () => ({
1288
+ protocol: APP_RUNTIME_PROTOCOL,
1289
+ version: 1,
1290
+ type: "ready"
1291
+ });
1292
+ //#endregion
247
1293
  //#region src/apis/billing.ts
248
1294
  var BillingApi = class {
249
1295
  transport;
@@ -316,194 +1362,7 @@ var BillingApi = class {
316
1362
  }
317
1363
  };
318
1364
  //#endregion
319
- //#region ../protocol/dist/realtime/schema.js
320
- const contentBlockMetaSchema = z.record(z.string(), z.unknown());
321
- const realtimeRoomSchema = z.string().regex(/^(space|user|board|room):[^:]+$/);
322
- const contentBlockSchema = z.discriminatedUnion("type", [
323
- z.object({
324
- type: z.literal("text"),
325
- text: z.string(),
326
- _meta: contentBlockMetaSchema.optional()
327
- }),
328
- z.object({
329
- type: z.literal("thinking"),
330
- thinking: z.string(),
331
- signature: z.string().optional(),
332
- _meta: contentBlockMetaSchema.optional()
333
- }),
334
- z.object({
335
- type: z.literal("image"),
336
- source: z.union([z.object({
337
- type: z.literal("url"),
338
- url: z.string().url()
339
- }), z.object({
340
- type: z.literal("base64"),
341
- media_type: z.string(),
342
- data: z.string()
343
- })]),
344
- _meta: contentBlockMetaSchema.optional()
345
- }),
346
- z.object({
347
- type: z.literal("shell_command"),
348
- command: z.string(),
349
- rawText: z.string(),
350
- _meta: contentBlockMetaSchema.optional()
351
- }),
352
- z.object({
353
- type: z.literal("tool_use"),
354
- id: z.string(),
355
- name: z.string(),
356
- input: z.record(z.string(), z.unknown()),
357
- _meta: contentBlockMetaSchema.optional()
358
- }),
359
- z.object({
360
- type: z.literal("tool_result"),
361
- tool_use_id: z.string(),
362
- content: z.union([z.string(), z.array(z.unknown())]),
363
- is_error: z.boolean().optional(),
364
- _meta: contentBlockMetaSchema.optional()
365
- }),
366
- z.object({
367
- type: z.literal("system_note"),
368
- note_type: z.enum([
369
- "session_created",
370
- "forked",
371
- "compacted",
372
- "info"
373
- ]),
374
- text: z.string(),
375
- _meta: contentBlockMetaSchema.optional()
376
- })
377
- ]);
378
- z.discriminatedUnion("type", [
379
- z.object({
380
- type: z.literal("auth"),
381
- requestId: z.string().optional(),
382
- payload: z.object({
383
- token: z.string().min(1),
384
- capabilities: z.array(z.string().min(1)).optional()
385
- })
386
- }),
387
- z.object({
388
- type: z.literal("subscribe"),
389
- requestId: z.string().optional(),
390
- payload: z.object({ rooms: z.array(realtimeRoomSchema).min(1) })
391
- }),
392
- z.object({
393
- type: z.literal("unsubscribe"),
394
- requestId: z.string().optional(),
395
- payload: z.object({ rooms: z.array(realtimeRoomSchema).min(1) })
396
- }),
397
- z.object({
398
- type: z.literal("session.message.create"),
399
- requestId: z.string().optional(),
400
- payload: z.object({
401
- spaceId: z.string().uuid(),
402
- sessionId: z.string().uuid(),
403
- clientMessageId: z.string().optional(),
404
- content: z.array(contentBlockSchema).min(1),
405
- model: z.string().optional(),
406
- provider: z.string().optional(),
407
- thinkingLevel: z.enum([
408
- "off",
409
- "minimal",
410
- "low",
411
- "medium",
412
- "high",
413
- "xhigh",
414
- "max"
415
- ]).optional()
416
- })
417
- }),
418
- z.object({
419
- type: z.literal("presence.update"),
420
- requestId: z.string().optional(),
421
- payload: z.object({
422
- spaceId: z.string().uuid(),
423
- meta: z.record(z.string(), z.unknown()).nullable().optional()
424
- })
425
- }),
426
- z.object({
427
- type: z.literal("board.awareness.update"),
428
- requestId: z.string().optional(),
429
- payload: BoardAwarenessClientPayloadSchema
430
- }),
431
- z.object({
432
- type: z.literal("realtime.room.join"),
433
- requestId: z.string().optional(),
434
- payload: z.object({
435
- roomId: z.string().uuid(),
436
- ticket: z.string().min(1)
437
- })
438
- }),
439
- z.object({
440
- type: z.literal("realtime.room.publish"),
441
- requestId: z.string().optional(),
442
- payload: z.object({
443
- roomId: z.string().uuid(),
444
- event: z.string().regex(REALTIME_ROOM_EVENT_NAME_PATTERN),
445
- data: z.unknown(),
446
- clientEventId: z.string().max(128).optional()
447
- })
448
- }),
449
- z.object({
450
- type: z.literal("realtime.room.leave"),
451
- requestId: z.string().optional(),
452
- payload: z.object({ roomId: z.string().uuid() })
453
- }),
454
- z.object({
455
- type: z.literal("realtime.room.presence.update"),
456
- requestId: z.string().optional(),
457
- payload: z.object({
458
- roomId: z.string().uuid(),
459
- presence: z.record(z.string(), z.unknown()).nullable()
460
- })
461
- }),
462
- z.object({
463
- type: z.literal("ping"),
464
- requestId: z.string().optional(),
465
- payload: z.record(z.string(), z.unknown()).optional()
466
- }),
467
- z.object({
468
- type: z.literal("ack"),
469
- requestId: z.string().optional(),
470
- payload: z.object({ eventId: z.string().optional() }).optional()
471
- })
472
- ]);
473
- z.object({
474
- id: z.string(),
475
- timestamp: z.number(),
476
- domain: z.enum(REALTIME_DOMAINS),
477
- type: z.string(),
478
- requestId: z.string().nullable().optional(),
479
- spaceId: z.string().nullable().optional(),
480
- sessionId: z.string().nullable().optional(),
481
- rooms: z.array(realtimeRoomSchema).optional(),
482
- payload: z.record(z.string(), z.unknown())
483
- });
484
- z.discriminatedUnion("t", [z.object({
485
- t: z.literal("d"),
486
- sid: z.string().min(1),
487
- s: z.number().int().nonnegative(),
488
- b: z.number().int().nonnegative(),
489
- v: z.unknown()
490
- }), z.object({
491
- t: z.literal("p"),
492
- sid: z.string().min(1),
493
- s: z.number().int().nonnegative(),
494
- b: z.number().int().nonnegative(),
495
- o: z.enum([
496
- "append",
497
- "replace",
498
- "add",
499
- "merge",
500
- "remove"
501
- ]),
502
- p: z.string().min(1),
503
- v: z.unknown().optional()
504
- })]);
505
- //#endregion
506
- //#region src/apis/work-realtime.ts
1365
+ //#region src/apis/app-realtime.ts
507
1366
  const randomId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
508
1367
  const textEncoder = new TextEncoder();
509
1368
  const byteLength = (text) => textEncoder.encode(text).byteLength;
@@ -527,7 +1386,7 @@ const isRoomEvent = (event, roomId) => {
527
1386
  if (event.domain !== "room") return false;
528
1387
  return event.payload.roomId === roomId || event.rooms?.includes(`room:${roomId}`);
529
1388
  };
530
- var WorkRoom = class {
1389
+ var AppRoom = class {
531
1390
  websocket;
532
1391
  admission;
533
1392
  id;
@@ -719,7 +1578,7 @@ var WorkRoom = class {
719
1578
  if (this.state === "expired") return;
720
1579
  this.setState("closed");
721
1580
  this.dispose();
722
- console.warn("[Cohub WorkRoom] failed to rejoin room", error);
1581
+ console.warn("[Cohub AppRoom] failed to rejoin room", error);
723
1582
  }
724
1583
  }
725
1584
  async joinOverWebsocket(isReconnect) {
@@ -884,7 +1743,7 @@ var WorkRoom = class {
884
1743
  this.expiryTimer = null;
885
1744
  }
886
1745
  };
887
- var WorkRealtimeApi = class {
1746
+ var AppRealtimeApi = class {
888
1747
  transport;
889
1748
  websocket;
890
1749
  getContext;
@@ -894,7 +1753,7 @@ var WorkRealtimeApi = class {
894
1753
  this.getContext = getContext;
895
1754
  }
896
1755
  async createRoom(input = {}) {
897
- const admission = await this.transport.request(`/api/works/${encodeURIComponent(await this.requireWorkId())}/realtime/rooms`, {
1756
+ const admission = await this.transport.request(`/api/apps/${encodeURIComponent(await this.requireAppId())}/realtime/rooms`, {
898
1757
  method: "POST",
899
1758
  headers: { "Content-Type": "application/json" },
900
1759
  body: JSON.stringify(input)
@@ -902,20 +1761,20 @@ var WorkRealtimeApi = class {
902
1761
  return this.openRoom(admission);
903
1762
  }
904
1763
  async joinRoom(input) {
905
- const admission = await this.transport.request(`/api/works/${encodeURIComponent(await this.requireWorkId())}/realtime/rooms/join`, {
1764
+ const admission = await this.transport.request(`/api/apps/${encodeURIComponent(await this.requireAppId())}/realtime/rooms/join`, {
906
1765
  method: "POST",
907
1766
  headers: { "Content-Type": "application/json" },
908
1767
  body: JSON.stringify(input)
909
1768
  });
910
1769
  return this.openRoom(admission);
911
1770
  }
912
- async requireWorkId() {
1771
+ async requireAppId() {
913
1772
  const context = await this.getContext();
914
- if (!context?.work?.id) throw new Error("Work context is unavailable — not running inside a published Work runtime.");
915
- return context.work.id;
1773
+ if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
1774
+ return context.app.id;
916
1775
  }
917
1776
  async openRoom(admission) {
918
- const room = new WorkRoom(this.websocket, admission);
1777
+ const room = new AppRoom(this.websocket, admission);
919
1778
  try {
920
1779
  await room.connect();
921
1780
  return room;
@@ -925,11 +1784,15 @@ var WorkRealtimeApi = class {
925
1784
  }
926
1785
  }
927
1786
  };
1787
+ /** @deprecated Use `AppRealtimeApi`. */
1788
+ var WorkRealtimeApi = class extends AppRealtimeApi {};
1789
+ /** @deprecated Use `AppRoom`. */
1790
+ var WorkRoom = class extends AppRoom {};
928
1791
  //#endregion
929
- //#region src/work-surface.ts
1792
+ //#region src/app-surface.ts
930
1793
  /**
931
- * Explicit app origins, not a Cohub suffix match: Works themselves are
932
- * served from Cohub subdomains, so a suffix match would let one Work call into
1794
+ * Explicit app origins, not a Cohub suffix match: apps themselves are
1795
+ * served from Cohub subdomains, so a suffix match would let one app call into
933
1796
  * another and turn a subdomain takeover into surface access.
934
1797
  */
935
1798
  const COHUB_APP_ORIGINS = [
@@ -952,7 +1815,7 @@ const resolveEmbedderOrigin = () => {
952
1815
  return null;
953
1816
  }
954
1817
  };
955
- var WorkSurfaceApi = class {
1818
+ var AppSurfaceApi = class {
956
1819
  handlers = /* @__PURE__ */ new Map();
957
1820
  listening = false;
958
1821
  allowedOrigins = null;
@@ -970,7 +1833,7 @@ var WorkSurfaceApi = class {
970
1833
  }
971
1834
  handle(method, handler) {
972
1835
  const name = method.trim();
973
- if (!name) throw new Error("Work surface method name is required");
1836
+ if (!name) throw new Error("App surface method name is required");
974
1837
  this.handlers.set(name, handler);
975
1838
  this.start();
976
1839
  this.announce();
@@ -985,17 +1848,17 @@ var WorkSurfaceApi = class {
985
1848
  return [...this.handlers.keys()];
986
1849
  }
987
1850
  setComposerChip(chip) {
988
- const message = parseWorkComposerChipSet(buildWorkComposerChipSet(chip));
989
- if (!message) throw new Error("Invalid Work composer chip");
1851
+ const message = parseAppComposerChipSet(buildAppComposerChipSet(chip));
1852
+ if (!message) throw new Error("Invalid App composer chip");
990
1853
  this.post(message);
991
1854
  }
992
1855
  clearComposerChip(key) {
993
- const message = parseWorkComposerChipClear(buildWorkComposerChipClear(key));
994
- if (!message) throw new Error("Invalid Work composer chip key");
1856
+ const message = parseAppComposerChipClear(buildAppComposerChipClear(key));
1857
+ if (!message) throw new Error("Invalid App composer chip key");
995
1858
  this.post(message);
996
1859
  }
997
1860
  announce() {
998
- this.post(buildWorkSurfaceReady(this.methods));
1861
+ this.post(buildAppSurfaceReady(this.methods));
999
1862
  }
1000
1863
  isTrusted(origin) {
1001
1864
  if (!origin || origin === "null") return false;
@@ -1016,7 +1879,7 @@ var WorkSurfaceApi = class {
1016
1879
  onMessage = (event) => {
1017
1880
  if (typeof window === "undefined" || event.source !== window.parent) return;
1018
1881
  if (!this.isTrusted(event.origin)) return;
1019
- const request = parseWorkSurfaceRequest(event.data);
1882
+ const request = parseAppSurfaceRequest(event.data);
1020
1883
  if (!request) return;
1021
1884
  const commandId = request.commandId;
1022
1885
  if (!commandId) return;
@@ -1026,24 +1889,24 @@ var WorkSurfaceApi = class {
1026
1889
  async dispatch(requestId, method, input, commandId) {
1027
1890
  const handler = this.handlers.get(method);
1028
1891
  if (!handler) {
1029
- this.post(buildWorkSurfaceResponse({
1892
+ this.post(buildAppSurfaceResponse({
1030
1893
  requestId,
1031
1894
  ok: false,
1032
1895
  error: {
1033
1896
  code: "method_not_found",
1034
- message: `This Work does not expose "${method}".`
1897
+ message: `This app does not expose "${method}".`
1035
1898
  }
1036
1899
  }));
1037
1900
  return;
1038
1901
  }
1039
1902
  try {
1040
1903
  await handler(input, { commandId });
1041
- this.post(buildWorkSurfaceResponse({
1904
+ this.post(buildAppSurfaceResponse({
1042
1905
  requestId,
1043
1906
  ok: true
1044
1907
  }));
1045
1908
  } catch (error) {
1046
- this.post(buildWorkSurfaceResponse({
1909
+ this.post(buildAppSurfaceResponse({
1047
1910
  requestId,
1048
1911
  ok: false,
1049
1912
  error: {
@@ -1062,8 +1925,10 @@ var WorkSurfaceApi = class {
1062
1925
  } catch {}
1063
1926
  }
1064
1927
  };
1928
+ /** @deprecated Use `AppSurfaceApi`. */
1929
+ var WorkSurfaceApi = class extends AppSurfaceApi {};
1065
1930
  //#endregion
1066
- //#region src/work-runtime.ts
1931
+ //#region src/app-runtime.ts
1067
1932
  const isBrowser$1 = () => typeof window !== "undefined" && typeof window.parent !== "undefined";
1068
1933
  const hasParent = () => isBrowser$1() && window.parent !== window;
1069
1934
  const getParentOrigin = () => {
@@ -1079,11 +1944,39 @@ const getParentOrigin = () => {
1079
1944
  const generateRequestId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1080
1945
  /**
1081
1946
  * Bridge-mode transport: posts messages to `window.parent` (the Cohub host
1082
- * embedding the work in an iframe) and listens for the matching reply.
1947
+ * embedding the app in an iframe) and listens for the matching reply.
1083
1948
  * Behaviorally identical to the previous module-level `request()` helper.
1084
1949
  */
1085
1950
  var ParentBridgeTransport = class {
1086
1951
  trustedParentOrigin = null;
1952
+ contextListeners = /* @__PURE__ */ new Set();
1953
+ contextListener = null;
1954
+ subscribeContextChanged(listener) {
1955
+ if (!hasParent()) return () => {};
1956
+ this.contextListeners.add(listener);
1957
+ if (!this.contextListener) {
1958
+ this.contextListener = (event) => {
1959
+ if (event.source !== window.parent) return;
1960
+ const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
1961
+ if (parentOrigin && event.origin !== parentOrigin) return;
1962
+ const data = event.data;
1963
+ if (data?.type !== "cohub.app.context.changed" || !data.context) return;
1964
+ for (const current of this.contextListeners) current(data.context);
1965
+ };
1966
+ window.addEventListener("message", this.contextListener);
1967
+ const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
1968
+ if (parentOrigin) try {
1969
+ window.parent.postMessage(buildAppRuntimeReady(), parentOrigin);
1970
+ } catch {}
1971
+ }
1972
+ return () => {
1973
+ this.contextListeners.delete(listener);
1974
+ if (this.contextListeners.size === 0 && this.contextListener) {
1975
+ window.removeEventListener("message", this.contextListener);
1976
+ this.contextListener = null;
1977
+ }
1978
+ };
1979
+ }
1087
1980
  request(message, options) {
1088
1981
  const timeoutMs = options?.timeoutMs ?? 1200;
1089
1982
  const retryIntervalMs = options?.retryIntervalMs;
@@ -1118,7 +2011,7 @@ var ParentBridgeTransport = class {
1118
2011
  if (!data || data.requestId !== requestId) return;
1119
2012
  cleanup();
1120
2013
  this.trustedParentOrigin = event.origin;
1121
- if (data.type === "cohub.work.error") {
2014
+ if (data.type === "cohub.app.error") {
1122
2015
  reject(new Error(data.message));
1123
2016
  return;
1124
2017
  }
@@ -1131,64 +2024,64 @@ var ParentBridgeTransport = class {
1131
2024
  }
1132
2025
  };
1133
2026
  /**
1134
- * Broker-mode transport for standalone-deployed works. Opens a popup window to
2027
+ * Broker-mode transport for standalone-deployed apps. Opens a popup window to
1135
2028
  * the Cohub auth broker page, performs a ready-handshake, sends the request via
1136
2029
  * postMessage, and resolves with the broker's response. The popup is closed
1137
2030
  * after a single request is fulfilled (one-shot, per §7.2 of the plan).
1138
2031
  *
1139
2032
  * Non-interactive messages (`context`, `checkout-state`) are answered locally
1140
- * without opening a popup — the work already knows its own workId, and
1141
- * checkout state is not available on the work's own origin in broker mode.
2033
+ * without opening a popup — the app already knows its own appId, and
2034
+ * checkout state is not available on the app's own origin in broker mode.
1142
2035
  */
1143
2036
  var PopupBrokerTransport = class {
1144
2037
  brokerOrigin;
1145
- workId;
1146
- getWorkId;
2038
+ appId;
2039
+ getAppId;
1147
2040
  constructor(config) {
1148
2041
  this.brokerOrigin = config.brokerOrigin;
1149
- this.workId = config.workId;
1150
- this.getWorkId = config.getWorkId;
1151
- if (!this.workId && this.getWorkId) this.getWorkId();
2042
+ this.appId = config.appId;
2043
+ this.getAppId = config.getAppId;
2044
+ if (!this.appId && this.getAppId) this.getAppId();
1152
2045
  }
1153
- resolveWorkId() {
1154
- if (this.workId) return Promise.resolve(this.workId);
1155
- if (this.getWorkId) return this.getWorkId();
2046
+ resolveAppId() {
2047
+ if (this.appId) return Promise.resolve(this.appId);
2048
+ if (this.getAppId) return this.getAppId();
1156
2049
  return Promise.resolve(null);
1157
2050
  }
1158
2051
  async request(message, options) {
1159
- if (message.type === "cohub.work.context") return {
1160
- type: "cohub.work.context.result",
2052
+ if (message.type === "cohub.app.context") return {
2053
+ type: "cohub.app.context.result",
1161
2054
  context: {
1162
- work: {
1163
- id: await this.resolveWorkId() ?? "",
2055
+ app: {
2056
+ id: await this.resolveAppId() ?? "",
1164
2057
  slug: "",
1165
2058
  url: null
1166
2059
  },
1167
2060
  space: { id: "" },
1168
2061
  permissions: {
1169
2062
  scopes: [],
1170
- workScopes: [],
2063
+ appScopes: [],
1171
2064
  viewerScopes: []
1172
2065
  }
1173
2066
  }
1174
2067
  };
1175
- if (message.type === "cohub.work.checkout-state") return {
1176
- type: "cohub.work.checkout-state.result",
2068
+ if (message.type === "cohub.app.checkout-state") return {
2069
+ type: "cohub.app.checkout-state.result",
1177
2070
  status: null,
1178
2071
  orderId: null
1179
2072
  };
1180
2073
  const timeoutMs = options?.timeoutMs ?? 12e4;
1181
2074
  const requestId = generateRequestId();
1182
- const workId = await this.resolveWorkId();
1183
- if (!workId) throw new Error("Unable to resolve the work id for broker mode. Provide `workId` or a valid slug triple (ownerUsername, spaceSlug, workSlug).");
2075
+ const appId = await this.resolveAppId();
2076
+ if (!appId) throw new Error("Unable to resolve the app id for broker mode. Provide `appId` or a valid slug triple (ownerUsername, spaceSlug, appSlug).");
1184
2077
  return new Promise((resolve, reject) => {
1185
2078
  if (typeof window === "undefined" || typeof window.open !== "function") {
1186
2079
  resolve(null);
1187
2080
  return;
1188
2081
  }
1189
- const workOrigin = window.location.origin;
1190
- const brokerUrl = `${this.brokerOrigin}/work-auth?work=${encodeURIComponent(workId)}&origin=${encodeURIComponent(workOrigin)}`;
1191
- const popup = window.open(brokerUrl, "cohub-work-auth", "popup,width=480,height=640");
2082
+ const appOrigin = window.location.origin;
2083
+ const brokerUrl = `${this.brokerOrigin}/app-auth?app=${encodeURIComponent(appId)}&origin=${encodeURIComponent(appOrigin)}`;
2084
+ const popup = window.open(brokerUrl, "cohub-app-auth", "popup,width=480,height=640");
1192
2085
  if (!popup) {
1193
2086
  reject(/* @__PURE__ */ new Error("Failed to open authorization window. Please allow popups for this site."));
1194
2087
  return;
@@ -1219,7 +2112,7 @@ var PopupBrokerTransport = class {
1219
2112
  if (event.origin !== this.brokerOrigin) return;
1220
2113
  const data = event.data;
1221
2114
  if (!data) return;
1222
- if (data.type === "cohub.work.broker.ready" && !ready) {
2115
+ if (data.type === "cohub.app.broker.ready" && !ready) {
1223
2116
  ready = true;
1224
2117
  try {
1225
2118
  popup.postMessage({
@@ -1233,7 +2126,7 @@ var PopupBrokerTransport = class {
1233
2126
  }
1234
2127
  if (data.requestId !== requestId) return;
1235
2128
  finish(() => {
1236
- if (data.type === "cohub.work.error") {
2129
+ if (data.type === "cohub.app.error") {
1237
2130
  reject(new Error(data.message));
1238
2131
  return;
1239
2132
  }
@@ -1250,30 +2143,30 @@ var PopupBrokerTransport = class {
1250
2143
  });
1251
2144
  }
1252
2145
  };
1253
- const TOKEN_STORAGE_PREFIX = "cohub:work-token";
1254
- const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:work-auth-scopes";
1255
- var WorkRuntimeApi = class {
2146
+ const TOKEN_STORAGE_PREFIX = "cohub:app-token";
2147
+ const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:app-auth-scopes";
2148
+ var AppRuntimeApi = class {
1256
2149
  token = null;
1257
2150
  transport;
1258
2151
  tokenStorageKey;
1259
2152
  scopesStorageKey;
1260
- workIdResolver;
2153
+ appIdResolver;
1261
2154
  /** Ensures storage keys are resolved (via slug lookup) at most once. */
1262
2155
  storageKeysReady;
1263
2156
  /** Scopes previously granted via requestAuthorization, retained so token
1264
2157
  * refreshes can re-authorize (preserving viewerScopes) instead of falling
1265
- * back to a base session token that only carries workScopes. */
2158
+ * back to a base session token that only carries appScopes. */
1266
2159
  authorizedScopes = null;
1267
- constructor(transport = new ParentBridgeTransport(), workId, workIdResolver) {
2160
+ constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
1268
2161
  this.transport = transport;
1269
- this.workIdResolver = workIdResolver;
1270
- if (workId) {
1271
- this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${workId}`;
1272
- this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${workId}`;
2162
+ this.appIdResolver = appIdResolver;
2163
+ if (appId) {
2164
+ this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
2165
+ this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${appId}`;
1273
2166
  this.storageKeysReady = Promise.resolve();
1274
2167
  this.token = this.readStoredToken();
1275
2168
  this.authorizedScopes = this.readStoredScopes();
1276
- } else if (workIdResolver) {
2169
+ } else if (appIdResolver) {
1277
2170
  this.tokenStorageKey = null;
1278
2171
  this.scopesStorageKey = null;
1279
2172
  this.storageKeysReady = null;
@@ -1284,17 +2177,17 @@ var WorkRuntimeApi = class {
1284
2177
  }
1285
2178
  }
1286
2179
  /**
1287
- * Resolves the localStorage keys once the workId is known. When the workId is
2180
+ * Resolves the localStorage keys once the appId is known. When the appId is
1288
2181
  * only available via slug reverse lookup, this performs the lookup on first
1289
- * use and then restores any cached token/scopes for that workId.
2182
+ * use and then restores any cached token/scopes for that appId.
1290
2183
  */
1291
2184
  ensureStorageKeys() {
1292
2185
  if (this.storageKeysReady) return this.storageKeysReady;
1293
2186
  this.storageKeysReady = (async () => {
1294
- const workId = this.workIdResolver ? await this.workIdResolver() : null;
1295
- if (workId) {
1296
- this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${workId}`;
1297
- this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${workId}`;
2187
+ const appId = this.appIdResolver ? await this.appIdResolver() : null;
2188
+ if (appId) {
2189
+ this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
2190
+ this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${appId}`;
1298
2191
  const stored = this.readStoredToken();
1299
2192
  if (stored && !this.token) this.token = stored;
1300
2193
  const storedScopes = this.readStoredScopes();
@@ -1337,11 +2230,14 @@ var WorkRuntimeApi = class {
1337
2230
  } catch {}
1338
2231
  }
1339
2232
  async context() {
1340
- return (await this.transport.request({ type: "cohub.work.context" }, {
2233
+ return (await this.transport.request({ type: "cohub.app.context" }, {
1341
2234
  timeoutMs: 8e3,
1342
2235
  retryIntervalMs: 250
1343
2236
  }))?.context ?? null;
1344
2237
  }
2238
+ onContextChanged(listener) {
2239
+ return this.transport.subscribeContextChanged?.(listener) ?? (() => {});
2240
+ }
1345
2241
  async getAccessToken(options) {
1346
2242
  await this.ensureStorageKeys();
1347
2243
  if (this.token && !options?.forceRefresh) return this.token;
@@ -1351,7 +2247,7 @@ var WorkRuntimeApi = class {
1351
2247
  }
1352
2248
  if (options?.forceRefresh && this.authorizedScopes && this.authorizedScopes.length > 0) {
1353
2249
  const response = await this.transport.request({
1354
- type: "cohub.work.authorize",
2250
+ type: "cohub.app.authorize",
1355
2251
  scopes: this.authorizedScopes
1356
2252
  }, { timeoutMs: 12e4 });
1357
2253
  this.token = response?.token ?? null;
@@ -1359,7 +2255,7 @@ var WorkRuntimeApi = class {
1359
2255
  return this.token;
1360
2256
  }
1361
2257
  const response = await this.transport.request({
1362
- type: "cohub.work.token",
2258
+ type: "cohub.app.token",
1363
2259
  forceRefresh: Boolean(options?.forceRefresh)
1364
2260
  }, { timeoutMs: 2e4 });
1365
2261
  this.token = response?.token ?? null;
@@ -1369,7 +2265,7 @@ var WorkRuntimeApi = class {
1369
2265
  async requestAuthorization(input) {
1370
2266
  await this.ensureStorageKeys();
1371
2267
  const response = await this.transport.request({
1372
- type: "cohub.work.authorize",
2268
+ type: "cohub.app.authorize",
1373
2269
  scopes: input.scopes,
1374
2270
  reason: input.reason
1375
2271
  }, { timeoutMs: 12e4 });
@@ -1384,32 +2280,34 @@ var WorkRuntimeApi = class {
1384
2280
  async purchase(input) {
1385
2281
  const purchaseAttemptId = input.purchaseAttemptId?.trim() || generateRequestId();
1386
2282
  return (await this.transport.request({
1387
- type: "cohub.work.purchase",
2283
+ type: "cohub.app.purchase",
1388
2284
  productKey: input.productKey,
1389
2285
  purchaseAttemptId
1390
2286
  }, { timeoutMs: 12e4 }))?.checkout ?? null;
1391
2287
  }
1392
2288
  async checkoutState() {
1393
- return await this.transport.request({ type: "cohub.work.checkout-state" }, {
2289
+ return await this.transport.request({ type: "cohub.app.checkout-state" }, {
1394
2290
  timeoutMs: 8e3,
1395
2291
  retryIntervalMs: 250
1396
2292
  }) ?? null;
1397
2293
  }
1398
2294
  };
1399
2295
  /**
1400
- * Builds a memoized workId resolver that reverse-looks-up the workId from the
1401
- * public slug triple via `GET /api/works/by-slug/:username/:spaceSlug/:workSlug`.
1402
- * The endpoint is anonymous (no auth) for public works, so no token is needed.
1403
- * The result is cached; a failed lookup is not cached so it can be retried.
2296
+ * Builds a memoized appId resolver that reverse-looks-up the appId from the
2297
+ * public slug triple via `GET /api/apps/by-slug/:username/:spaceSlug/:appSlug`
2298
+ * (the works REST routes are dual-mounted; `/api/works` keeps serving older
2299
+ * consumers with identical payloads). The endpoint is
2300
+ * anonymous (no auth) for public apps, so no token is needed. The result is
2301
+ * cached; a failed lookup is not cached so it can be retried.
1404
2302
  */
1405
- function createSlugWorkIdResolver(deps) {
2303
+ function createSlugAppIdResolver(deps) {
1406
2304
  let cached = null;
1407
2305
  return () => {
1408
2306
  if (cached) return cached;
1409
2307
  const run = (async () => {
1410
2308
  const doFetch = deps.fetch ?? globalThis.fetch;
1411
2309
  if (typeof doFetch !== "function") return null;
1412
- const response = await doFetch(`${deps.apiBaseUrl}/api/works/by-slug/${encodeURIComponent(deps.ownerUsername)}/${encodeURIComponent(deps.spaceSlug)}/${encodeURIComponent(deps.workSlug)}`);
2310
+ const response = await doFetch(`${deps.apiBaseUrl}/api/apps/by-slug/${encodeURIComponent(deps.ownerUsername)}/${encodeURIComponent(deps.spaceSlug)}/${encodeURIComponent(deps.appSlug)}`);
1413
2311
  if (!response.ok) throw new Error(`getBySlug failed: ${response.status}`);
1414
2312
  return (await response.json())?.work?.id ?? null;
1415
2313
  })().catch(() => {
@@ -1421,27 +2319,27 @@ function createSlugWorkIdResolver(deps) {
1421
2319
  };
1422
2320
  }
1423
2321
  /**
1424
- * Resolves the appropriate transport based on the work mode configuration.
2322
+ * Resolves the appropriate transport based on the app mode configuration.
1425
2323
  * Auto-detection: inside an iframe → bridge; standalone with broker config →
1426
- * broker; otherwise → bridge (returns null for non-work contexts).
2324
+ * broker; otherwise → bridge (returns null for non-app contexts).
1427
2325
  */
1428
- function resolveWorkTransport(config, workIdResolver) {
2326
+ function resolveAppTransport(config, appIdResolver) {
1429
2327
  const explicitMode = config?.mode;
1430
2328
  const brokerOrigin = config?.brokerOrigin;
1431
- const workId = config?.workId;
1432
- const canResolveWorkId = Boolean(workId || workIdResolver);
1433
- const hasBrokerConfig = Boolean(brokerOrigin && canResolveWorkId);
1434
- const createBroker = () => brokerOrigin && canResolveWorkId ? new PopupBrokerTransport({
2329
+ const appId = config?.appId;
2330
+ const canResolveAppId = Boolean(appId || appIdResolver);
2331
+ const hasBrokerConfig = Boolean(brokerOrigin && canResolveAppId);
2332
+ const createBroker = () => brokerOrigin && canResolveAppId ? new PopupBrokerTransport({
1435
2333
  brokerOrigin,
1436
- workId,
1437
- getWorkId: workIdResolver
2334
+ appId,
2335
+ getAppId: appIdResolver
1438
2336
  }) : new ParentBridgeTransport();
1439
2337
  if (explicitMode === "bridge") return new ParentBridgeTransport();
1440
2338
  if (explicitMode === "broker") return createBroker();
1441
2339
  if (typeof window !== "undefined" && window.parent !== window) return new ParentBridgeTransport();
1442
2340
  return hasBrokerConfig ? createBroker() : new ParentBridgeTransport();
1443
2341
  }
1444
- const createWorkRuntime = (transport, workId, workIdResolver) => new WorkRuntimeApi(transport, workId, workIdResolver);
2342
+ const createAppRuntime = (transport, appId, appIdResolver) => new AppRuntimeApi(transport, appId, appIdResolver);
1445
2343
  //#endregion
1446
2344
  //#region src/client.ts
1447
2345
  var CohubClient = class {
@@ -1460,27 +2358,40 @@ var CohubClient = class {
1460
2358
  references;
1461
2359
  tasks;
1462
2360
  cronJobs;
1463
- ui;
2361
+ desktop;
1464
2362
  invite;
1465
2363
  referrals;
1466
2364
  voice;
1467
- works;
1468
- workCommerce;
2365
+ apps;
2366
+ appCommerce;
2367
+ /** @deprecated Use `client.desktop`. */
2368
+ get ui() {
2369
+ return this.desktop;
2370
+ }
2371
+ /** @deprecated Use `client.apps`. */
2372
+ get works() {
2373
+ return this.apps;
2374
+ }
2375
+ /** @deprecated Use `client.appCommerce`. */
2376
+ get workCommerce() {
2377
+ return this.appCommerce;
2378
+ }
1469
2379
  transport;
1470
2380
  websocketClient;
1471
- workRuntime;
2381
+ appRuntime;
1472
2382
  constructor(options = {}) {
1473
2383
  const apiBaseUrl = resolveApiBaseUrl(options);
1474
- const workIdResolver = !options.work?.workId && options.work?.ownerUsername && options.work?.spaceSlug && options.work?.workSlug ? createSlugWorkIdResolver({
2384
+ const appRuntime = options.app ?? options.work;
2385
+ const appIdResolver = !appRuntime?.appId && appRuntime?.ownerUsername && appRuntime?.spaceSlug && appRuntime?.appSlug ? createSlugAppIdResolver({
1475
2386
  apiBaseUrl,
1476
2387
  fetch: options.fetch,
1477
- ownerUsername: options.work.ownerUsername,
1478
- spaceSlug: options.work.spaceSlug,
1479
- workSlug: options.work.workSlug
2388
+ ownerUsername: appRuntime.ownerUsername,
2389
+ spaceSlug: appRuntime.spaceSlug,
2390
+ appSlug: appRuntime.appSlug
1480
2391
  }) : void 0;
1481
- const workTransport = resolveWorkTransport(options.work, workIdResolver);
1482
- this.workRuntime = createWorkRuntime(workTransport, options.work?.workId, workIdResolver);
1483
- const getAccessToken = options.getAccessToken ?? ((tokenOptions) => this.workRuntime.getAccessToken(tokenOptions));
2392
+ const appTransport = resolveAppTransport(appRuntime, appIdResolver);
2393
+ this.appRuntime = createAppRuntime(appTransport, appRuntime?.appId, appIdResolver);
2394
+ const getAccessToken = options.getAccessToken ?? ((tokenOptions) => this.appRuntime.getAccessToken(tokenOptions));
1484
2395
  const resolvedOptions = {
1485
2396
  ...options,
1486
2397
  getAccessToken
@@ -1517,55 +2428,56 @@ var CohubClient = class {
1517
2428
  this.references = new ReferencesApi(this.transport);
1518
2429
  this.tasks = new TasksApi(this.transport);
1519
2430
  this.cronJobs = new CronJobsApi(this.transport);
1520
- this.ui = new UiCommandsApi(this.transport);
2431
+ this.desktop = new DesktopCommandsApi(this.transport);
1521
2432
  this.invite = new PublicInviteApi(this.transport);
1522
2433
  this.referrals = new ReferralsApi(this.transport);
1523
- this.works = new WorksApi(this.transport);
1524
- this.workCommerce = new WorkCommerceApi(this.transport);
1525
- this.work.realtime = new WorkRealtimeApi(this.transport, this.websocketClient, () => this.workRuntime.context());
2434
+ this.apps = new AppsApi(this.transport);
2435
+ this.appCommerce = new AppCommerceApi(this.transport);
2436
+ this.app.realtime = new AppRealtimeApi(this.transport, this.websocketClient, () => this.appRuntime.context());
1526
2437
  }
1527
2438
  context() {
1528
- return this.workRuntime.context();
2439
+ return this.appRuntime.context();
1529
2440
  }
1530
- auth = { request: (input) => this.workRuntime.requestAuthorization(input) };
1531
- work = {
2441
+ auth = { request: (input) => this.appRuntime.requestAuthorization(input) };
2442
+ app = {
1532
2443
  realtime: null,
1533
- /** Expose callable methods from inside a published Work. */
1534
- surface: new WorkSurfaceApi(),
2444
+ /** Expose callable methods from inside a published app. */
2445
+ surface: new AppSurfaceApi(),
2446
+ onContextChanged: (listener) => this.appRuntime.onContextChanged(listener),
1535
2447
  composer: {
1536
- /** Attach or update context from this Work in the Cohub composer. */
1537
- setChip: (chip) => this.work.surface.setComposerChip(chip),
1538
- /** Remove context previously attached by this Work. */
1539
- clearChip: (key) => this.work.surface.clearComposerChip(key)
2448
+ /** Attach or update context from this app in the Cohub composer. */
2449
+ setChip: (chip) => this.app.surface.setComposerChip(chip),
2450
+ /** Remove context previously attached by this app. */
2451
+ clearChip: (key) => this.app.surface.clearComposerChip(key)
1540
2452
  },
1541
2453
  commerce: {
1542
2454
  resolveProducts: async (input) => {
1543
- const context = await this.workRuntime.context();
1544
- if (!context?.work?.id) throw new Error("Work context is unavailable — not running inside a published Work runtime.");
1545
- return this.workCommerce.resolveProducts(context.work.id, input);
2455
+ const context = await this.appRuntime.context();
2456
+ if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
2457
+ return this.appCommerce.resolveProducts(context.app.id, input);
1546
2458
  },
1547
2459
  getEntitlements: async () => {
1548
- const context = await this.workRuntime.context();
1549
- if (!context?.work?.id) throw new Error("Work context is unavailable — not running inside a published Work runtime.");
1550
- return this.workCommerce.getEntitlements(context.work.id);
2460
+ const context = await this.appRuntime.context();
2461
+ if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
2462
+ return this.appCommerce.getEntitlements(context.app.id);
1551
2463
  },
1552
2464
  consumeCredits: async (input) => {
1553
- const context = await this.workRuntime.context();
1554
- if (!context?.work?.id) throw new Error("Work context is unavailable — not running inside a published Work runtime.");
1555
- return this.workCommerce.consumeCredits(context.work.id, input);
2465
+ const context = await this.appRuntime.context();
2466
+ if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
2467
+ return this.appCommerce.consumeCredits(context.app.id, input);
1556
2468
  },
1557
- purchase: async (input) => this.workRuntime.purchase(input),
2469
+ purchase: async (input) => this.appRuntime.purchase(input),
1558
2470
  getCheckoutState: async () => {
1559
- const result = await this.workRuntime.checkoutState();
2471
+ const result = await this.appRuntime.checkoutState();
1560
2472
  return {
1561
2473
  status: result?.status ?? null,
1562
2474
  orderId: result?.orderId ?? null
1563
2475
  };
1564
2476
  },
1565
2477
  getOrder: async (orderId) => {
1566
- const context = await this.workRuntime.context();
1567
- if (!context?.work?.id) throw new Error("Work context is unavailable — not running inside a published Work runtime.");
1568
- return this.workCommerce.getOrder(context.work.id, orderId);
2478
+ const context = await this.appRuntime.context();
2479
+ if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
2480
+ return this.appCommerce.getOrder(context.app.id, orderId);
1569
2481
  }
1570
2482
  }
1571
2483
  };
@@ -1623,8 +2535,8 @@ var CohubClient = class {
1623
2535
  };
1624
2536
  const createCohubClient = (options) => new CohubClient(options);
1625
2537
  //#endregion
1626
- //#region src/work-ref.ts
1627
- const isWorkId = (value) => isUuid(value.trim());
2538
+ //#region src/app-ref.ts
2539
+ const isAppId = (value) => isUuid(value.trim());
1628
2540
  function decodePart(value) {
1629
2541
  try {
1630
2542
  return decodeURIComponent(value).trim();
@@ -1634,14 +2546,14 @@ function decodePart(value) {
1634
2546
  }
1635
2547
  function publicRef(parts) {
1636
2548
  if (parts.length !== 3) return null;
1637
- const [usernameRaw = "", spaceSlugRaw = "", workSlugRaw = ""] = parts.map(decodePart);
2549
+ const [usernameRaw = "", spaceSlugRaw = "", appSlugRaw = ""] = parts.map(decodePart);
1638
2550
  const username = parseUsername(usernameRaw);
1639
2551
  const spaceSlug = parseSpaceSlug(spaceSlugRaw);
1640
- const workSlug = parseSpaceSlug(workSlugRaw);
1641
- return username && spaceSlug && workSlug ? {
2552
+ const appSlug = parseSpaceSlug(appSlugRaw);
2553
+ return username && spaceSlug && appSlug ? {
1642
2554
  username,
1643
2555
  spaceSlug,
1644
- workSlug
2556
+ appSlug
1645
2557
  } : null;
1646
2558
  }
1647
2559
  function launchState(url) {
@@ -1658,15 +2570,22 @@ function parseUrlRef(value) {
1658
2570
  return null;
1659
2571
  }
1660
2572
  const parts = url.pathname.split("/").filter(Boolean);
1661
- if (url.protocol === "cohub:" && url.hostname === "works") {
2573
+ if (url.protocol === "cohub:" && (url.hostname === "apps" || url.hostname === "works")) {
1662
2574
  const ref = publicRef(parts);
1663
2575
  return ref ? {
1664
2576
  ...ref,
1665
2577
  ...launchState(url)
1666
2578
  } : null;
1667
2579
  }
2580
+ if (url.protocol === "app:" || url.protocol === "work:") {
2581
+ const ref = publicRef([url.hostname, ...parts]);
2582
+ return ref ? {
2583
+ ...ref,
2584
+ ...launchState(url)
2585
+ } : null;
2586
+ }
1668
2587
  if (url.protocol !== "http:" && url.protocol !== "https:") return null;
1669
- if (parts.length === 4 && parts[0] === "spaces" && isUuid(parts[1] ?? "") && parts[2] === "works" && isUuid(parts[3] ?? "")) return { id: parts[3] };
2588
+ if (parts.length === 4 && parts[0] === "spaces" && isUuid(parts[1] ?? "") && (parts[2] === "apps" || parts[2] === "works") && isUuid(parts[3] ?? "")) return { id: parts[3] };
1670
2589
  if (parts.length === 4 && parts[2] === "w") {
1671
2590
  const ref = publicRef([
1672
2591
  parts[0],
@@ -1680,13 +2599,13 @@ function parseUrlRef(value) {
1680
2599
  }
1681
2600
  return null;
1682
2601
  }
1683
- var WorkRefParseError = class extends Error {
2602
+ var AppRefParseError = class extends Error {
1684
2603
  constructor() {
1685
- super("Work must be an id, public URL, cohub://works URI, or username/space/work reference");
1686
- this.name = "WorkRefParseError";
2604
+ super("App must be an id, public URL, cohub://apps URI, or username/space/app reference");
2605
+ this.name = "AppRefParseError";
1687
2606
  }
1688
2607
  };
1689
- function parseWorkRef(input) {
2608
+ function parseAppRef(input) {
1690
2609
  const value = input.trim();
1691
2610
  if (isUuid(value)) return { id: value };
1692
2611
  const parsedUrl = parseUrlRef(value.includes("://") ? value : value.startsWith("/") ? `https://cohub.invalid${value}` : value);
@@ -1694,21 +2613,29 @@ function parseWorkRef(input) {
1694
2613
  const parts = value.split("/").filter(Boolean);
1695
2614
  const parsedPublic = parts.length === 3 ? publicRef(parts) : null;
1696
2615
  if (parsedPublic) return parsedPublic;
1697
- throw new WorkRefParseError();
2616
+ throw new AppRefParseError();
1698
2617
  }
1699
- function formatWorkRef(ref) {
1700
- return "id" in ref ? ref.id : `${ref.username}/${ref.spaceSlug}/${ref.workSlug}`;
2618
+ function formatAppRef(ref) {
2619
+ return "id" in ref ? ref.id : `${ref.username}/${ref.spaceSlug}/${ref.appSlug}`;
1701
2620
  }
2621
+ /** @deprecated Use `isAppId`. */
2622
+ const isWorkId = isAppId;
2623
+ /** @deprecated Use `AppRefParseError`. */
2624
+ const WorkRefParseError = AppRefParseError;
2625
+ /** @deprecated Use `parseAppRef`. */
2626
+ const parseWorkRef = parseAppRef;
2627
+ /** @deprecated Use `formatAppRef`. */
2628
+ const formatWorkRef = formatAppRef;
1702
2629
  //#endregion
1703
- //#region src/work-grant-cache.ts
2630
+ //#region src/app-grant-cache.ts
1704
2631
  const STORAGE_PREFIX = "cohub:work-grants";
1705
2632
  const CACHE_VERSION = 1;
1706
2633
  const MAX_AGE_MS = 336 * 60 * 60 * 1e3;
1707
2634
  function isBrowser() {
1708
2635
  return typeof localStorage !== "undefined";
1709
2636
  }
1710
- function storageKey(userUuid, workId) {
1711
- return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(workId)}:v${CACHE_VERSION}`;
2637
+ function storageKey(userUuid, appId) {
2638
+ return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}:v${CACHE_VERSION}`;
1712
2639
  }
1713
2640
  function userPrefix(userUuid) {
1714
2641
  return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:`;
@@ -1719,16 +2646,16 @@ function isPermissionArray(value) {
1719
2646
  function isCachedWorkGrant(value) {
1720
2647
  if (!value || typeof value !== "object") return false;
1721
2648
  const record = value;
1722
- return record.version === CACHE_VERSION && typeof record.userUuid === "string" && typeof record.workId === "string" && isPermissionArray(record.scopes) && typeof record.updatedAt === "number";
2649
+ return record.version === CACHE_VERSION && typeof record.userUuid === "string" && typeof record.appId === "string" && isPermissionArray(record.scopes) && typeof record.updatedAt === "number";
1723
2650
  }
1724
- function readEntry(userUuid, workId) {
2651
+ function readEntry(userUuid, appId) {
1725
2652
  if (!isBrowser()) return null;
1726
- const key = storageKey(userUuid, workId);
2653
+ const key = storageKey(userUuid, appId);
1727
2654
  try {
1728
2655
  const raw = localStorage.getItem(key);
1729
2656
  if (!raw) return null;
1730
2657
  const parsed = JSON.parse(raw);
1731
- if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.workId !== workId) {
2658
+ if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId) {
1732
2659
  localStorage.removeItem(key);
1733
2660
  return null;
1734
2661
  }
@@ -1748,9 +2675,9 @@ function readEntry(userUuid, workId) {
1748
2675
  * Returns true when the viewer has previously granted every requested scope
1749
2676
  * for this work, allowing a silent re-authorization.
1750
2677
  */
1751
- function hasGrantedWorkScopes(userUuid, workId, scopes) {
1752
- if (!userUuid || !workId || scopes.length === 0) return false;
1753
- const entry = readEntry(userUuid, workId);
2678
+ function hasGrantedAppScopes(userUuid, appId, scopes) {
2679
+ if (!userUuid || !appId || scopes.length === 0) return false;
2680
+ const entry = readEntry(userUuid, appId);
1754
2681
  if (!entry) return false;
1755
2682
  const granted = new Set(entry.scopes);
1756
2683
  return scopes.every((scope) => granted.has(scope));
@@ -1759,31 +2686,31 @@ function hasGrantedWorkScopes(userUuid, workId, scopes) {
1759
2686
  * Records the granted scopes for a work, merged with any previously granted
1760
2687
  * scopes so a growing permission set stays covered.
1761
2688
  */
1762
- function setGrantedWorkScopes(userUuid, workId, scopes) {
1763
- if (!userUuid || !workId || scopes.length === 0) return;
1764
- const existing = readEntry(userUuid, workId);
2689
+ function setGrantedAppScopes(userUuid, appId, scopes) {
2690
+ if (!userUuid || !appId || scopes.length === 0) return;
2691
+ const existing = readEntry(userUuid, appId);
1765
2692
  const merged = Array.from(/* @__PURE__ */ new Set([...existing?.scopes ?? [], ...scopes]));
1766
2693
  const entry = {
1767
2694
  version: CACHE_VERSION,
1768
2695
  userUuid,
1769
- workId,
2696
+ appId,
1770
2697
  scopes: merged,
1771
2698
  updatedAt: Date.now()
1772
2699
  };
1773
2700
  if (!isBrowser()) return;
1774
2701
  try {
1775
- localStorage.setItem(storageKey(userUuid, workId), JSON.stringify(entry));
2702
+ localStorage.setItem(storageKey(userUuid, appId), JSON.stringify(entry));
1776
2703
  } catch {}
1777
2704
  }
1778
2705
  /**
1779
- * Clears cached grants. Pass a workId to clear a single work, or omit it to
2706
+ * Clears cached grants. Pass a appId to clear a single work, or omit it to
1780
2707
  * clear every cached grant for the user (used on sign-out).
1781
2708
  */
1782
- function clearGrantedWorkScopes(userUuid, workId) {
2709
+ function clearGrantedAppScopes(userUuid, appId) {
1783
2710
  if (!isBrowser() || !userUuid) return;
1784
2711
  try {
1785
- if (workId) {
1786
- localStorage.removeItem(storageKey(userUuid, workId));
2712
+ if (appId) {
2713
+ localStorage.removeItem(storageKey(userUuid, appId));
1787
2714
  return;
1788
2715
  }
1789
2716
  const prefix = userPrefix(userUuid);
@@ -1794,7 +2721,7 @@ function clearGrantedWorkScopes(userUuid, workId) {
1794
2721
  } catch {}
1795
2722
  }
1796
2723
  //#endregion
1797
- //#region src/work-bridge-core.ts
2724
+ //#region src/app-bridge-core.ts
1798
2725
  function readTokenResponse(value) {
1799
2726
  if (!value || typeof value !== "object") return null;
1800
2727
  const token = value.token;
@@ -1804,20 +2731,78 @@ function clonePermissionScopes(scopes) {
1804
2731
  return Array.from(scopes ?? []).filter((scope) => typeof scope === "string");
1805
2732
  }
1806
2733
  /**
1807
- * Framework-agnostic work bridge host core — message handling, work session
2734
+ * Framework-agnostic app bridge host core — message handling, app session
1808
2735
  * token minting, authorization (with silent re-grant cache), and
1809
2736
  * purchase/checkout flow — without any rendering or reactive primitives.
1810
2737
  *
1811
- * Both the Cohub iframe host (WorkSurface, Svelte) and the standalone broker
2738
+ * Both the Cohub iframe host (AppSurface, Svelte) and the standalone broker
1812
2739
  * page compose this with their own transport-specific reply and auth
1813
2740
  * dependencies. External hosts (e.g. Neta-Studio in React) can do the same.
1814
2741
  */
1815
- function createWorkBridgeCore(config) {
1816
- const { work, reply, getCheckoutState, getAccessToken, getViewerUuid } = config;
2742
+ function createAppBridgeCore(config) {
2743
+ const { app, reply, getCheckoutState, getAccessToken, getViewerUuid } = config;
1817
2744
  const apiOrigin = config.apiOrigin;
1818
2745
  const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
1819
2746
  const onStateChange = config.onStateChange;
1820
- let workToken = null;
2747
+ let appToken = null;
2748
+ let activeInvocation;
2749
+ let contextChangeVersion = 0;
2750
+ const legacyRequestIds = /* @__PURE__ */ new Set();
2751
+ async function getContext() {
2752
+ const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
2753
+ const appScopes = clonePermissionScopes(app.appScopes);
2754
+ const viewerUuid = await getViewerUuid();
2755
+ return {
2756
+ app: {
2757
+ id: app.id,
2758
+ slug: app.slug,
2759
+ url: typeof location !== "undefined" ? location.href : ""
2760
+ },
2761
+ space: { id: app.spaceId },
2762
+ viewer: viewerUuid ? { userUuid: viewerUuid } : null,
2763
+ ...invocation ? { invocation: { ...invocation } } : {},
2764
+ permissions: {
2765
+ scopes: appScopes,
2766
+ appScopes,
2767
+ viewerScopes: []
2768
+ }
2769
+ };
2770
+ }
2771
+ function replyForRequest(requestId, payload, complete = false) {
2772
+ const namespace = legacyRequestIds.has(requestId) ? "work" : "app";
2773
+ const type = payload.type;
2774
+ reply(requestId, {
2775
+ ...payload,
2776
+ ...typeof type === "string" && type.startsWith("cohub.app.") ? { type: type.replace("cohub.app.", `cohub.${namespace}.`) } : {}
2777
+ });
2778
+ if (complete) legacyRequestIds.delete(requestId);
2779
+ }
2780
+ function toLegacyWorkContext(context) {
2781
+ const permissions = context.permissions ? {
2782
+ scopes: context.permissions.scopes,
2783
+ workScopes: context.permissions.appScopes,
2784
+ appScopes: context.permissions.appScopes,
2785
+ viewerScopes: context.permissions.viewerScopes
2786
+ } : void 0;
2787
+ return {
2788
+ work: context.app,
2789
+ space: context.space,
2790
+ ...context.viewer !== void 0 ? { viewer: context.viewer } : {},
2791
+ ...context.invocation ? { invocation: context.invocation } : {},
2792
+ ...permissions ? { permissions } : {}
2793
+ };
2794
+ }
2795
+ async function notifyContextChanged(invocation) {
2796
+ activeInvocation = invocation;
2797
+ const version = ++contextChangeVersion;
2798
+ if (!config.notify) return;
2799
+ const context = await getContext();
2800
+ if (version !== contextChangeVersion) return;
2801
+ config.notify({
2802
+ type: "cohub.app.context.changed",
2803
+ context
2804
+ });
2805
+ }
1821
2806
  const state = {
1822
2807
  authOpen: false,
1823
2808
  pendingAuth: null,
@@ -1831,30 +2816,30 @@ function createWorkBridgeCore(config) {
1831
2816
  function notify() {
1832
2817
  onStateChange?.({ ...state });
1833
2818
  }
1834
- const pendingPurchaseStorageKey = `cohub-work-purchase:${work.id}`;
1835
- async function isCurrentViewerWorkOwner() {
2819
+ const pendingPurchaseStorageKey = `cohub-app-purchase:${app.id}`;
2820
+ async function isCurrentViewerAppOwner() {
1836
2821
  const viewerUuid = await getViewerUuid();
1837
- return Boolean(viewerUuid && viewerUuid === work.userUuid);
2822
+ return Boolean(viewerUuid && viewerUuid === app.userUuid);
1838
2823
  }
1839
2824
  function allowsOwnerAutoAuthorization() {
1840
- return authorizationContext.surface === "background" || authorizationContext.surface === "preview";
2825
+ return authorizationContext.surface === "background" || authorizationContext.surface === "app";
1841
2826
  }
1842
2827
  async function ensureBaseToken(forceRefresh = false) {
1843
- if (workToken && !forceRefresh) return workToken;
2828
+ if (appToken && !forceRefresh) return appToken;
1844
2829
  const userToken = await getAccessToken({ forceRefresh });
1845
2830
  if (!userToken) {
1846
2831
  await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
1847
2832
  return null;
1848
2833
  }
1849
- const response = await fetch(`${apiOrigin}/api/works/${work.id}/session`, {
2834
+ const response = await fetch(`${apiOrigin}/api/apps/${app.id}/session`, {
1850
2835
  method: "POST",
1851
2836
  headers: { Authorization: `Bearer ${userToken}` }
1852
2837
  });
1853
- if (!response.ok) throw new Error("Failed to create work session.");
2838
+ if (!response.ok) throw new Error("Failed to create app session.");
1854
2839
  const token = readTokenResponse(await response.json());
1855
- if (!token) throw new Error("Invalid work session response.");
1856
- workToken = token;
1857
- return workToken;
2840
+ if (!token) throw new Error("Invalid app session response.");
2841
+ appToken = token;
2842
+ return appToken;
1858
2843
  }
1859
2844
  async function authorize(scopes) {
1860
2845
  const userToken = await getAccessToken();
@@ -1862,7 +2847,7 @@ function createWorkBridgeCore(config) {
1862
2847
  await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
1863
2848
  return null;
1864
2849
  }
1865
- const response = await fetch(`${apiOrigin}/api/works/${work.id}/authorize`, {
2850
+ const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
1866
2851
  method: "POST",
1867
2852
  headers: {
1868
2853
  Authorization: `Bearer ${userToken}`,
@@ -1872,9 +2857,9 @@ function createWorkBridgeCore(config) {
1872
2857
  });
1873
2858
  if (!response.ok) throw new Error((await response.json().catch(() => null))?.message ?? "Authorization failed.");
1874
2859
  const token = readTokenResponse(await response.json());
1875
- if (!token) throw new Error("Invalid work authorization response.");
1876
- workToken = token;
1877
- return workToken;
2860
+ if (!token) throw new Error("Invalid app authorization response.");
2861
+ appToken = token;
2862
+ return appToken;
1878
2863
  }
1879
2864
  function writePendingPurchase(input) {
1880
2865
  if (typeof sessionStorage === "undefined") return;
@@ -1913,7 +2898,7 @@ function createWorkBridgeCore(config) {
1913
2898
  return null;
1914
2899
  }
1915
2900
  const promotionAttribution = config.getPromotionAttribution?.() ?? null;
1916
- const response = await fetch(`${apiOrigin}/api/works/${work.id}/commerce/purchase`, {
2901
+ const response = await fetch(`${apiOrigin}/api/apps/${app.id}/commerce/purchase`, {
1917
2902
  method: "POST",
1918
2903
  headers: {
1919
2904
  Authorization: `Bearer ${userToken}`,
@@ -1931,62 +2916,49 @@ function createWorkBridgeCore(config) {
1931
2916
  async function handleMessage(event) {
1932
2917
  const data = event.data;
1933
2918
  if (!data?.requestId) return;
2919
+ const isLegacyWork = data.type?.startsWith("cohub.work.") === true;
2920
+ if (isLegacyWork) legacyRequestIds.add(data.requestId);
1934
2921
  try {
1935
- if (data.type === "cohub.work.context") {
1936
- const workScopes = clonePermissionScopes(work.workScopes);
1937
- const viewerUuid = await getViewerUuid();
1938
- reply(data.requestId, {
1939
- type: "cohub.work.context.result",
1940
- context: {
1941
- work: {
1942
- id: work.id,
1943
- slug: work.slug,
1944
- url: typeof location !== "undefined" ? location.href : ""
1945
- },
1946
- space: { id: work.spaceId },
1947
- viewer: viewerUuid ? { userUuid: viewerUuid } : null,
1948
- ...config.invocation ? { invocation: { ...config.invocation } } : {},
1949
- permissions: {
1950
- scopes: workScopes,
1951
- workScopes,
1952
- viewerScopes: []
1953
- }
1954
- }
1955
- });
2922
+ if (data.type === "cohub.app.context" || data.type === "cohub.work.context") {
2923
+ const context = await getContext();
2924
+ replyForRequest(data.requestId, {
2925
+ type: "cohub.app.context.result",
2926
+ context: isLegacyWork ? toLegacyWorkContext(context) : context
2927
+ }, true);
1956
2928
  }
1957
- if (data.type === "cohub.work.token") {
2929
+ if (data.type === "cohub.app.token" || data.type === "cohub.work.token") {
1958
2930
  const token = await ensureBaseToken(Boolean(data.forceRefresh));
1959
- reply(data.requestId, {
1960
- type: "cohub.work.token.result",
2931
+ replyForRequest(data.requestId, {
2932
+ type: "cohub.app.token.result",
1961
2933
  token
1962
- });
2934
+ }, true);
1963
2935
  }
1964
- if (data.type === "cohub.work.checkout-state") {
2936
+ if (data.type === "cohub.app.checkout-state" || data.type === "cohub.work.checkout-state") {
1965
2937
  const pending = readPendingPurchase();
1966
2938
  const checkoutState = getCheckoutState();
1967
2939
  const orderId = checkoutState.orderId ?? pending?.orderId ?? null;
1968
2940
  if (checkoutState.status && checkoutState.orderId) clearPendingPurchase();
1969
- reply(data.requestId, {
1970
- type: "cohub.work.checkout-state.result",
2941
+ replyForRequest(data.requestId, {
2942
+ type: "cohub.app.checkout-state.result",
1971
2943
  status: checkoutState.status,
1972
2944
  orderId
1973
- });
2945
+ }, true);
1974
2946
  }
1975
- if (data.type === "cohub.work.purchase") {
2947
+ if (data.type === "cohub.app.purchase" || data.type === "cohub.work.purchase") {
1976
2948
  const productKey = typeof data.productKey === "string" ? data.productKey.trim() : "";
1977
2949
  if (!productKey) {
1978
- reply(data.requestId, {
1979
- type: "cohub.work.error",
2950
+ replyForRequest(data.requestId, {
2951
+ type: "cohub.app.error",
1980
2952
  message: "Product key is required."
1981
- });
2953
+ }, true);
1982
2954
  return;
1983
2955
  }
1984
2956
  const purchaseAttemptId = (typeof data.purchaseAttemptId === "string" ? data.purchaseAttemptId.trim() : "") || data.requestId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 128);
1985
2957
  if (!/^[a-zA-Z0-9_-]{1,128}$/.test(purchaseAttemptId)) {
1986
- reply(data.requestId, {
1987
- type: "cohub.work.error",
2958
+ replyForRequest(data.requestId, {
2959
+ type: "cohub.app.error",
1988
2960
  message: "Purchase attempt id is invalid."
1989
- });
2961
+ }, true);
1990
2962
  return;
1991
2963
  }
1992
2964
  state.pendingPurchase = {
@@ -1999,34 +2971,34 @@ function createWorkBridgeCore(config) {
1999
2971
  notify();
2000
2972
  config.onPurchaseRequested?.({ ...state.pendingPurchase });
2001
2973
  }
2002
- if (data.type === "cohub.work.authorize") {
2003
- const allowedViewerScopes = clonePermissionScopes(work.allowedViewerScopes);
2974
+ if (data.type === "cohub.app.authorize" || data.type === "cohub.work.authorize") {
2975
+ const allowedViewerScopes = clonePermissionScopes(app.allowedViewerScopes);
2004
2976
  const scopes = clonePermissionScopes(data.scopes).filter((scope) => allowedViewerScopes.includes(scope));
2005
2977
  if (scopes.length === 0) {
2006
- reply(data.requestId, {
2007
- type: "cohub.work.error",
2978
+ replyForRequest(data.requestId, {
2979
+ type: "cohub.app.error",
2008
2980
  message: "No allowed scopes requested."
2009
- });
2981
+ }, true);
2010
2982
  return;
2011
2983
  }
2012
- if (allowsOwnerAutoAuthorization() && await isCurrentViewerWorkOwner()) {
2984
+ if (allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) {
2013
2985
  const token = await authorize(scopes);
2014
- reply(data.requestId, {
2015
- type: "cohub.work.authorize.result",
2986
+ replyForRequest(data.requestId, {
2987
+ type: "cohub.app.authorize.result",
2016
2988
  token
2017
- });
2989
+ }, true);
2018
2990
  return;
2019
2991
  }
2020
2992
  const viewerUuid = await getViewerUuid();
2021
- if (viewerUuid && hasGrantedWorkScopes(viewerUuid, work.id, scopes)) try {
2993
+ if (viewerUuid && hasGrantedAppScopes(viewerUuid, app.id, scopes)) try {
2022
2994
  const token = await authorize(scopes);
2023
- reply(data.requestId, {
2024
- type: "cohub.work.authorize.result",
2995
+ replyForRequest(data.requestId, {
2996
+ type: "cohub.app.authorize.result",
2025
2997
  token
2026
- });
2998
+ }, true);
2027
2999
  return;
2028
3000
  } catch {
2029
- clearGrantedWorkScopes(viewerUuid, work.id);
3001
+ clearGrantedAppScopes(viewerUuid, app.id);
2030
3002
  }
2031
3003
  state.pendingAuth = {
2032
3004
  requestId: data.requestId,
@@ -2038,19 +3010,19 @@ function createWorkBridgeCore(config) {
2038
3010
  notify();
2039
3011
  }
2040
3012
  } catch (error) {
2041
- reply(data.requestId, {
2042
- type: "cohub.work.error",
3013
+ replyForRequest(data.requestId, {
3014
+ type: "cohub.app.error",
2043
3015
  message: error instanceof Error ? error.message : "Request failed."
2044
- });
3016
+ }, true);
2045
3017
  }
2046
3018
  }
2047
3019
  function cancelAuth() {
2048
3020
  if (state.authSaving) return;
2049
3021
  if (!state.pendingAuth) return;
2050
- reply(state.pendingAuth.requestId, {
2051
- type: "cohub.work.authorize.result",
3022
+ replyForRequest(state.pendingAuth.requestId, {
3023
+ type: "cohub.app.authorize.result",
2052
3024
  token: null
2053
- });
3025
+ }, true);
2054
3026
  state.authOpen = false;
2055
3027
  state.pendingAuth = null;
2056
3028
  state.authError = null;
@@ -2060,10 +3032,10 @@ function createWorkBridgeCore(config) {
2060
3032
  function cancelPurchase() {
2061
3033
  if (state.purchaseSaving) return;
2062
3034
  if (!state.pendingPurchase) return;
2063
- reply(state.pendingPurchase.requestId, {
2064
- type: "cohub.work.purchase.result",
3035
+ replyForRequest(state.pendingPurchase.requestId, {
3036
+ type: "cohub.app.purchase.result",
2065
3037
  checkout: null
2066
- });
3038
+ }, true);
2067
3039
  state.purchaseOpen = false;
2068
3040
  state.purchaseError = null;
2069
3041
  state.pendingPurchase = null;
@@ -2077,10 +3049,10 @@ function createWorkBridgeCore(config) {
2077
3049
  notify();
2078
3050
  try {
2079
3051
  const checkout = await createPurchase(state.pendingPurchase.productKey, state.pendingPurchase.purchaseAttemptId);
2080
- reply(state.pendingPurchase.requestId, {
2081
- type: "cohub.work.purchase.result",
3052
+ replyForRequest(state.pendingPurchase.requestId, {
3053
+ type: "cohub.app.purchase.result",
2082
3054
  checkout
2083
- });
3055
+ }, true);
2084
3056
  if (checkout && typeof checkout === "object") {
2085
3057
  const next = checkout;
2086
3058
  if (typeof next.orderId === "string" && typeof next.productKey === "string") writePendingPurchase({
@@ -2113,11 +3085,11 @@ function createWorkBridgeCore(config) {
2113
3085
  notify();
2114
3086
  try {
2115
3087
  const token = await authorize(state.pendingAuth.scopes);
2116
- setGrantedWorkScopes(await getViewerUuid(), work.id, state.pendingAuth.scopes);
2117
- reply(state.pendingAuth.requestId, {
2118
- type: "cohub.work.authorize.result",
3088
+ setGrantedAppScopes(await getViewerUuid(), app.id, state.pendingAuth.scopes);
3089
+ replyForRequest(state.pendingAuth.requestId, {
3090
+ type: "cohub.app.authorize.result",
2119
3091
  token
2120
- });
3092
+ }, true);
2121
3093
  state.authOpen = false;
2122
3094
  state.pendingAuth = null;
2123
3095
  } catch (error) {
@@ -2130,6 +3102,7 @@ function createWorkBridgeCore(config) {
2130
3102
  return {
2131
3103
  getState: () => ({ ...state }),
2132
3104
  handleMessage,
3105
+ notifyContextChanged,
2133
3106
  confirmAuth,
2134
3107
  cancelAuth,
2135
3108
  confirmPurchase,
@@ -2370,4 +3343,4 @@ function createBoardExtensionRegistry() {
2370
3343
  }
2371
3344
  const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
2372
3345
  //#endregion
2373
- export { BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BOARD_NATIVE_NODE_TYPES, BOARD_NODE_CONTRACT, BillingApi, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardTransactionError, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UI_COMMAND_DEFAULT_TIMEOUT_MS, UI_COMMAND_MAX_TIMEOUT_MS, UI_COMMAND_PAYLOAD_MAX_BYTES, UI_COMMAND_PENDING_TTL_SECONDS, UI_COMMAND_SETTLEMENT_GRACE_SECONDS, UI_COMMAND_TERMINAL_TTL_SECONDS, UI_COMMAND_VERSION, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES, WORK_COMPOSER_CHIP_KEY_MAX_LENGTH, WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH, WORK_SURFACE_READY_TIMEOUT_MS, WORK_SURFACE_REQUEST_TIMEOUT_MS, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkRuntimeApi, WorkSurfaceApi, WorksApi, assertGenerationRequestAllowedByPolicy, buildSpaceInvitePath, buildSpacePath, buildWorkSurfaceRequest, clearGrantedWorkScopes, compileComposition, composition, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, createWorkBridgeCore, createWorkRuntime, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatWorkRef, getAllowedGenerationModelIds, hasGrantedWorkScopes, hasRequestSourceIdentity, isBillingAccessBlockedCode, isBillingAccessBlockedError, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalUiCommandStatus, isUiSurfaceMethod, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardPlaybackPolicy, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseUiCommand, parseWorkRef, parseWorkSurfaceReady, parseWorkSurfaceResponse, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveCohubEnvironment, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, resolveWorkTransport, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, setGrantedWorkScopes, track, validateBoardNodeInput };
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 };