@neta-art/cohub 8.24.0 → 9.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 (159) hide show
  1. package/dist/board/animation.d.ts +57 -181
  2. package/dist/board/animation.js +374 -220
  3. package/dist/board/core/arrow-geometry.d.ts +33 -18
  4. package/dist/board/core/arrow-geometry.js +283 -52
  5. package/dist/board/core/draw-geometry.d.ts +22 -36
  6. package/dist/board/core/draw-geometry.js +148 -85
  7. package/dist/board/core/export-assets.d.ts +3 -6
  8. package/dist/board/core/export-assets.js +0 -2
  9. package/dist/board/core/export-plan.d.ts +12 -63
  10. package/dist/board/core/export-plan.js +55 -94
  11. package/dist/board/core/palette.d.ts +4 -21
  12. package/dist/board/core/palette.js +8 -13
  13. package/dist/board/core/scene.d.ts +33 -0
  14. package/dist/board/core/scene.js +302 -0
  15. package/dist/board/core/shape-definition.d.ts +12 -24
  16. package/dist/board/core/shape-definition.js +0 -19
  17. package/dist/board/core/shape-types.d.ts +6 -120
  18. package/dist/board/core/shape-types.js +5 -5
  19. package/dist/board/core/text-metrics.d.ts +8 -11
  20. package/dist/board/core/text-metrics.js +5 -34
  21. package/dist/board/core/tool-styles.d.ts +6 -16
  22. package/dist/board/core/tool-styles.js +17 -25
  23. package/dist/board/export/index.d.ts +10 -17
  24. package/dist/board/export/index.js +22 -21
  25. package/dist/board/export/scene.d.ts +10 -16
  26. package/dist/board/export/scene.js +17 -30
  27. package/dist/board/geometry.d.ts +27 -56
  28. package/dist/board/geometry.js +7 -67
  29. package/dist/board/headless/index.d.ts +6 -27
  30. package/dist/board/headless/index.js +4 -19
  31. package/dist/board/headless/sketch.d.ts +19 -0
  32. package/dist/board/headless/sketch.js +146 -0
  33. package/dist/board/image-key.d.ts +2 -12
  34. package/dist/board/image-key.js +3 -13
  35. package/dist/board/index.d.ts +18 -20
  36. package/dist/board/index.js +17 -17
  37. package/dist/board/media-playback.d.ts +3 -6
  38. package/dist/board/media-playback.js +6 -6
  39. package/dist/board/presets.d.ts +150 -0
  40. package/dist/board/presets.js +226 -0
  41. package/dist/board/render/board-background.d.ts +5 -3
  42. package/dist/board/render/board-background.js +8 -7
  43. package/dist/board/render/clip.d.ts +14 -0
  44. package/dist/board/render/clip.js +47 -0
  45. package/dist/board/render/index.d.ts +8 -5
  46. package/dist/board/render/index.js +7 -4
  47. package/dist/board/render/media-interaction.d.ts +4 -5
  48. package/dist/board/render/media-interaction.js +1 -2
  49. package/dist/board/render/palette.d.ts +6 -2
  50. package/dist/board/render/palette.js +7 -1
  51. package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
  52. package/dist/board/render/renderers/audio-card-renderer.js +3 -3
  53. package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
  54. package/dist/board/render/renderers/base-card-renderer.js +1 -24
  55. package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
  56. package/dist/board/render/renderers/board-renderer-registry.js +6 -7
  57. package/dist/board/render/renderers/draw-card-renderer.js +89 -56
  58. package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
  59. package/dist/board/render/renderers/effect-card-renderer.js +197 -0
  60. package/dist/board/render/renderers/far-plate.d.ts +4 -21
  61. package/dist/board/render/renderers/far-plate.js +15 -34
  62. package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
  63. package/dist/board/render/renderers/file-card-renderer.js +8 -58
  64. package/dist/board/render/renderers/frame-card-renderer.js +28 -28
  65. package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
  66. package/dist/board/render/renderers/image-card-renderer.js +0 -9
  67. package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
  68. package/dist/board/render/renderers/shape-card-renderer.js +240 -0
  69. package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
  70. package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
  71. package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
  72. package/dist/board/render/renderers/task-card-renderer.js +14 -16
  73. package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
  74. package/dist/board/render/renderers/text-card-renderer.js +56 -46
  75. package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
  76. package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
  77. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  78. package/dist/board/render/sketch-host.d.ts +14 -0
  79. package/dist/board/render/sketch-host.js +216 -0
  80. package/dist/board/render/stroke.d.ts +12 -0
  81. package/dist/board/render/stroke.js +90 -0
  82. package/dist/board/render/text-measurement.d.ts +6 -26
  83. package/dist/board/render/text-measurement.js +11 -30
  84. package/dist/board/replay.d.ts +6 -31
  85. package/dist/board/replay.js +45 -203
  86. package/dist/board/runtime/board-player.d.ts +24 -0
  87. package/dist/board/runtime/board-player.js +161 -0
  88. package/dist/board/task.d.ts +2 -10
  89. package/dist/board/task.js +1 -10
  90. package/dist/chunks/environment.d.ts +2291 -3864
  91. package/dist/chunks/host-bridge.d.ts +253 -0
  92. package/dist/chunks/http.d.ts +233 -58
  93. package/dist/chunks/http.js +1046 -1028
  94. package/dist/chunks/transport.js +113 -59
  95. package/dist/chunks/websocket.d.ts +1 -1
  96. package/dist/chunks/websocket.js +1 -1
  97. package/dist/host-bridge.d.ts +54 -0
  98. package/dist/host-bridge.js +322 -0
  99. package/dist/http.d.ts +3 -3
  100. package/dist/index.d.ts +74 -191
  101. package/dist/index.js +2032 -1186
  102. package/dist/protocol/dist/board-constants.d.ts +1 -29
  103. package/dist/protocol/dist/board-constants.js +2 -115
  104. package/dist/protocol/dist/board-layout.d.ts +59 -0
  105. package/dist/protocol/dist/board-layout.js +482 -0
  106. package/dist/protocol/dist/board-model.d.ts +2052 -0
  107. package/dist/protocol/dist/board-model.js +592 -0
  108. package/dist/protocol/dist/board-patch.d.ts +86 -0
  109. package/dist/protocol/dist/board-patch.js +734 -0
  110. package/dist/protocol/dist/board-schema.d.ts +33 -0
  111. package/dist/protocol/dist/board-schema.js +59 -0
  112. package/dist/protocol/dist/board-upgrade.d.ts +1 -0
  113. package/dist/protocol/dist/board-url.d.ts +1 -3
  114. package/dist/protocol/dist/board-url.js +2 -2
  115. package/dist/protocol/dist/board.d.ts +37 -232
  116. package/dist/protocol/dist/board.js +62 -160
  117. package/dist/protocol/dist/display/actions.d.ts +1 -0
  118. package/dist/protocol/dist/display/index.d.ts +2 -0
  119. package/dist/protocol/dist/index.d.ts +11 -12
  120. package/dist/protocol/dist/model/metrics.d.ts +2 -0
  121. package/dist/protocol/dist/model/session.d.ts +2 -1
  122. package/dist/protocol/dist/realtime/types.d.ts +2 -2
  123. package/dist/protocol/dist/runtime/index.d.ts +1 -0
  124. package/dist/space-picker.d.ts +1 -0
  125. package/dist/space-picker.js +1 -0
  126. package/dist/types.d.ts +2 -5
  127. package/package.json +9 -1
  128. package/dist/board/core/connections.d.ts +0 -108
  129. package/dist/board/core/connections.js +0 -455
  130. package/dist/board/mutation.d.ts +0 -6
  131. package/dist/board/mutation.js +0 -14
  132. package/dist/board/render/connection-layer.d.ts +0 -53
  133. package/dist/board/render/connection-layer.js +0 -375
  134. package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
  135. package/dist/board/render/renderers/geo-card-renderer.js +0 -127
  136. package/dist/board/semantic-document.d.ts +0 -37
  137. package/dist/board/semantic-document.js +0 -288
  138. package/dist/board/semantic-mutation.d.ts +0 -10
  139. package/dist/board/semantic-mutation.js +0 -209
  140. package/dist/protocol/dist/board-animation.js +0 -34
  141. package/dist/protocol/dist/board-authoring.d.ts +0 -1326
  142. package/dist/protocol/dist/board-authoring.js +0 -302
  143. package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
  144. package/dist/protocol/dist/board-capability-registry.js +0 -123
  145. package/dist/protocol/dist/board-codec.d.ts +0 -3
  146. package/dist/protocol/dist/board-codec.js +0 -383
  147. package/dist/protocol/dist/board-composition.d.ts +0 -383
  148. package/dist/protocol/dist/board-composition.js +0 -310
  149. package/dist/protocol/dist/board-connection.d.ts +0 -317
  150. package/dist/protocol/dist/board-connection.js +0 -219
  151. package/dist/protocol/dist/board-document.d.ts +0 -1566
  152. package/dist/protocol/dist/board-document.js +0 -456
  153. package/dist/protocol/dist/board-effect.d.ts +0 -96
  154. package/dist/protocol/dist/board-effect.js +0 -54
  155. package/dist/protocol/dist/board-geometry.d.ts +0 -15
  156. package/dist/protocol/dist/board-geometry.js +0 -137
  157. package/dist/protocol/dist/board-node.d.ts +0 -8
  158. package/dist/protocol/dist/board-node.js +0 -252
  159. /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
@@ -1,894 +1,397 @@
1
- import { D as getRealtimeSpaceRoom, E as getRealtimeBoardRoom, n as HttpTransport, t as HttpError, y as isUuid } from "./transport.js";
1
+ import { A as isUuid, f as getRealtimeBoardRoom, n as HttpTransport, p as getRealtimeSpaceRoom, t as HttpError } from "./transport.js";
2
2
  import { l as resolveExecutionToken, o as resolveApiBaseUrl } from "./environment.js";
3
3
  import { z } from "zod";
4
- //#region ../protocol/dist/board-constants.js
5
- const DEFAULT_BOARD_RENDER_LIMITS = {
6
- particles: 2e4,
7
- vertices: 5e5,
8
- dynamicVertices: 15e4,
9
- drawCalls: 400,
10
- filterPasses: 24,
11
- renderTexturePixels: 16777216,
12
- textureBytes: 536870912,
13
- bufferBytes: 268435456,
14
- simulationSteps: 1e5
15
- };
16
- const BOARD_BUILTIN_CLIP_KINDS = [
17
- "motion.path",
18
- "draw.reveal",
19
- "text.reveal",
20
- "effects.particles",
21
- "effects.trail",
22
- "effects.impact",
23
- "effects.flash",
24
- "camera.focus",
25
- "camera.shake"
26
- ];
27
- const BOARD_BUILTIN_EFFECT_KINDS = [
28
- "effects.pulse",
29
- "effects.float",
30
- "effects.deal"
31
- ];
32
- const BOARD_ARROW_STROKE_SIZE = 2.5;
33
- const BOARD_CONNECTION_STROKE_SIZE = 2.5;
34
- function clipSchema(id) {
35
- switch (id) {
36
- case "motion.path": return { params: { points: {
37
- coordinateSpace: "world-offset",
38
- unit: "board"
39
- } } };
40
- case "effects.particles": return { params: { bounds: {
41
- coordinateSpace: "world",
42
- unit: "board"
43
- } } };
44
- case "camera.focus": return { params: {
45
- focus: {
46
- coordinateSpace: "world",
47
- unit: "board"
48
- },
49
- padding: {
50
- coordinateSpace: "screen",
51
- unit: "css-px"
52
- },
53
- minZoom: { unit: "ratio" },
54
- maxZoom: { unit: "ratio" }
55
- } };
56
- case "camera.shake": return { params: { amount: {
57
- coordinateSpace: "screen-offset",
58
- unit: "css-px"
59
- } } };
60
- default: return;
61
- }
62
- }
63
- function effectSchema(id) {
64
- switch (id) {
65
- case "effects.deal": return { params: {
66
- lift: { unit: "board" },
67
- swing: { unit: "board" },
68
- curve: { unit: "board" },
69
- tilt: { unit: "degree" },
70
- scale: { unit: "ratio" },
71
- duration: { unit: "ms" },
72
- landing: { unit: "ms" }
73
- } };
74
- default: return;
75
- }
4
+ //#region ../protocol/dist/model/metrics.js
5
+ const count = z.number().finite().nonnegative();
6
+ const usageSchema = z.object({
7
+ input: count.optional(),
8
+ output: count.optional(),
9
+ cacheRead: count.optional(),
10
+ cacheWrite: count.optional(),
11
+ totalTokens: count.optional(),
12
+ cost: z.object({
13
+ input: count.optional(),
14
+ output: count.optional(),
15
+ cacheRead: count.optional(),
16
+ cacheWrite: count.optional(),
17
+ total: count.optional()
18
+ }).nullable().optional()
19
+ });
20
+ /** One provider attempt, independent of whether its response becomes a chat message. */
21
+ const requestMetricSchema = z.object({
22
+ id: z.string(),
23
+ provider: z.string(),
24
+ model: z.string(),
25
+ startedAt: count,
26
+ completedAt: count.optional(),
27
+ durationMs: count.optional(),
28
+ firstTokenMs: count.optional(),
29
+ outputDurationMs: count.optional(),
30
+ status: z.enum([
31
+ "running",
32
+ "completed",
33
+ "failed",
34
+ "interrupted"
35
+ ]),
36
+ usage: usageSchema.nullable().optional(),
37
+ omitted: z.boolean().optional(),
38
+ imageToText: z.object({
39
+ calls: count,
40
+ durationMs: count,
41
+ usage: usageSchema.nullable(),
42
+ unknownUsageCalls: count
43
+ }).optional()
44
+ });
45
+ const toolMetricsSchema = z.object({
46
+ calls: count,
47
+ errors: count,
48
+ timedCalls: count,
49
+ activeMs: count.nullable()
50
+ });
51
+ z.object({
52
+ version: z.literal(1),
53
+ executionStartedAt: count.optional(),
54
+ requests: z.record(z.string(), requestMetricSchema).optional(),
55
+ retryWaitMs: count.optional(),
56
+ retryCount: count.optional(),
57
+ tools: toolMetricsSchema.optional(),
58
+ imageToText: z.object({
59
+ durationMs: count.nullable(),
60
+ timedCalls: count
61
+ }).optional()
62
+ });
63
+ function metricsRecord(value) {
64
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
76
65
  }
77
- const BOARD_BUILTIN_CAPABILITIES = [...BOARD_BUILTIN_CLIP_KINDS.map((id) => {
78
- const schema = clipSchema(id);
66
+ const optionalCount = count.nullable();
67
+ /** Additive facts only. Rates are computed from matching samples, never averaged averages. */
68
+ const executionStatsSchema = z.object({
69
+ turns: count,
70
+ generations: count,
71
+ calls: optionalCount,
72
+ failedCalls: optionalCount,
73
+ unknownUsageCalls: count,
74
+ toolCalls: optionalCount,
75
+ toolErrors: optionalCount,
76
+ elapsedMs: optionalCount,
77
+ queueMs: optionalCount,
78
+ modelMs: optionalCount,
79
+ toolMs: optionalCount,
80
+ retryCount: optionalCount,
81
+ retryWaitMs: optionalCount,
82
+ ttftMs: count,
83
+ ttftSamples: count,
84
+ outputMs: count,
85
+ timedOutputTokens: count,
86
+ compactions: count,
87
+ compactionMs: optionalCount,
88
+ compactionUsage: usageSchema.nullable(),
89
+ imageToTextCalls: count,
90
+ imageToTextMs: optionalCount,
91
+ usage: usageSchema.nullable(),
92
+ modelCostUsd: optionalCount,
93
+ generationCostUsd: optionalCount,
94
+ /** Shared statistics never expose actual charges. */
95
+ chargedCostUsd: z.null().default(null),
96
+ partial: z.boolean()
97
+ });
98
+ const sessionStatsSchema = z.object({
99
+ version: z.literal(1),
100
+ updatedAt: z.string(),
101
+ revision: count,
102
+ own: executionStatsSchema,
103
+ inherited: executionStatsSchema,
104
+ auxiliaryUsage: usageSchema.nullable()
105
+ });
106
+ const normalizeExecutionStatsAliases = (value) => {
107
+ const record = metricsRecord(value);
108
+ const modelAlias = !Object.hasOwn(record, "modelCostUsd") && Object.hasOwn(record, "estimatedCostUsd");
109
+ const generationAlias = !Object.hasOwn(record, "generationCostUsd") && Object.hasOwn(record, "providerCostUsd");
110
+ if (!modelAlias && !generationAlias) return value;
79
111
  return {
80
- kind: "clip",
81
- id,
82
- version: 1,
83
- renderers: ["webgpu", "webgl"],
84
- ...schema ? { schema } : {}
112
+ ...record,
113
+ ...modelAlias ? { modelCostUsd: record.estimatedCostUsd } : {},
114
+ ...generationAlias ? { generationCostUsd: record.providerCostUsd } : {}
85
115
  };
86
- }), ...BOARD_BUILTIN_EFFECT_KINDS.map((id) => {
87
- const schema = effectSchema(id);
116
+ };
117
+ function sanitizeSessionStatsMeta(meta) {
118
+ const source = metricsRecord(meta);
119
+ if (!source.stats || typeof source.stats !== "object" || Array.isArray(source.stats)) return meta;
120
+ const stats = metricsRecord(source.stats);
121
+ const stripCharge = (value) => {
122
+ const record = metricsRecord(value);
123
+ return Object.hasOwn(record, "chargedCostUsd") && record.chargedCostUsd !== null ? {
124
+ ...record,
125
+ chargedCostUsd: null
126
+ } : value;
127
+ };
128
+ const own = stripCharge(stats.own);
129
+ const inherited = stripCharge(stats.inherited);
130
+ if (own === stats.own && inherited === stats.inherited) return meta;
88
131
  return {
89
- kind: "effect",
90
- id,
91
- version: 1,
92
- renderers: ["webgpu", "webgl"],
93
- ...schema ? { schema } : {}
132
+ ...source,
133
+ stats: {
134
+ ...stats,
135
+ own,
136
+ inherited
137
+ }
94
138
  };
95
- })];
139
+ }
140
+ function readSessionStats(meta) {
141
+ const raw = metricsRecord(sanitizeSessionStatsMeta(meta)).stats;
142
+ const record = metricsRecord(raw);
143
+ const own = normalizeExecutionStatsAliases(record.own);
144
+ const inherited = normalizeExecutionStatsAliases(record.inherited);
145
+ const normalized = own === record.own && inherited === record.inherited ? raw : {
146
+ ...record,
147
+ own,
148
+ inherited
149
+ };
150
+ const result = sessionStatsSchema.safeParse(normalized);
151
+ return result.success ? result.data : null;
152
+ }
153
+ //#endregion
154
+ //#region ../protocol/dist/display/index.js
96
155
  /**
97
- * Where a connection meets its node.
98
- *
99
- * - `auto` — the side is chosen from the live geometry of both endpoints, so the
100
- * connection stays sensible through any move or resize. This is the default and
101
- * what almost every connection should use.
102
- * - `side` — the user pinned a side; `offset` (0..1) positions it along that edge.
103
- * - `fixed` — the user pinned an exact normalized point on the frame.
156
+ * Displays: a machine's screens, shared with people over WebRTC and with
157
+ * agents as stills and input. Mirrored in Go by `apps/sandbox/display` and
158
+ * `apps/sandbox/rtc`, and in Kotlin by the Android display provider; the
159
+ * vocabulary and limits below are generated into both
160
+ * (`pnpm --filter @cohub/protocol generate:display`).
104
161
  *
105
- * `auto` is a *declaration of intent*, not a computed value: the resolved side is
106
- * never written back, so the connection keeps adapting instead of freezing the
107
- * first layout it happened to have.
162
+ * Coordinates are normalized to the display (0..1, origin top-left), so
163
+ * viewers and agents never need its pixel size to act on it.
108
164
  */
109
- const BoardConnectionAnchorSchema = z.union([
110
- z.object({ kind: z.literal("auto") }),
111
- z.object({
112
- kind: z.literal("side"),
113
- side: z.enum([
114
- "top",
115
- "right",
116
- "bottom",
117
- "left"
118
- ]),
119
- offset: z.number().finite().min(0).max(1).default(.5)
120
- }),
121
- z.object({
122
- kind: z.literal("fixed"),
123
- nx: z.number().finite().min(0).max(1),
124
- ny: z.number().finite().min(0).max(1)
125
- })
126
- ]);
127
- const BoardConnectionEndpointSchema = z.object({
128
- itemId: z.string().min(1).max(160),
129
- /** Optional semantic port. Older connections omit it and remain valid. */
130
- portId: z.string().min(1).max(120).optional(),
131
- anchor: BoardConnectionAnchorSchema.default({ kind: "auto" })
165
+ const unit = z.number().min(0).max(1);
166
+ const displayIdSchema = z.string().regex(/^[A-Za-z0-9._-]{1,64}$/);
167
+ const DISPLAY_DIMENSION_MAX = 16384;
168
+ const DISPLAY_SYSTEM_ACTIONS = [
169
+ "back",
170
+ "home",
171
+ "recents",
172
+ "notifications",
173
+ "quickSettings",
174
+ "lock"
175
+ ];
176
+ const DISPLAY_PERMISSIONS = ["screenRecording", "accessibility"];
177
+ const DISPLAY_POINTER_ACTIONS = [
178
+ "down",
179
+ "move",
180
+ "up",
181
+ "cancel"
182
+ ];
183
+ const DISPLAY_KEY_ACTIONS = [
184
+ "down",
185
+ "up",
186
+ "press"
187
+ ];
188
+ const DISPLAY_BUTTONS = [
189
+ "primary",
190
+ "secondary",
191
+ "middle"
192
+ ];
193
+ /** A list of known names; names a newer machine adds are dropped, so older clients keep working. */
194
+ const knownNames = (names, max) => z.array(z.string().max(32)).max(max).transform((values) => values.filter((value) => names.includes(value)));
195
+ const displayViewerSchema = z.object({
196
+ userId: z.string().min(1).max(64),
197
+ control: z.boolean().optional()
132
198
  });
133
- /**
134
- * Which ends carry an arrowhead.
135
- *
136
- * This is the *semantic* direction, not a style flag: `forward` means the
137
- * relation reads source → target, `backward` means target → source, and `none`
138
- * means the relation is symmetric. Renderers derive arrowheads from it, so the
139
- * drawing can never disagree with the meaning.
140
- */
141
- const BOARD_CONNECTION_DIRECTIONS = [
142
- "none",
143
- "forward",
144
- "backward",
145
- "both"
199
+ const displayInfoSchema = z.object({
200
+ id: displayIdSchema,
201
+ name: z.string().max(200),
202
+ width: z.number().int().min(0).max(DISPLAY_DIMENSION_MAX),
203
+ height: z.number().int().min(0).max(DISPLAY_DIMENSION_MAX),
204
+ stream: z.boolean(),
205
+ capture: z.boolean(),
206
+ input: z.boolean(),
207
+ system: knownNames(DISPLAY_SYSTEM_ACTIONS, 16).optional(),
208
+ desktop: z.boolean().optional(),
209
+ tree: z.boolean().optional(),
210
+ needs: knownNames(DISPLAY_PERMISSIONS, 8).optional(),
211
+ viewers: z.array(displayViewerSchema).max(16).optional()
212
+ });
213
+ z.object({ displays: z.array(displayInfoSchema).max(16) });
214
+ z.object({ size: z.string().regex(/^\d{3,4}x\d{3,4}$/).optional() });
215
+ const DISPLAY_ELEMENT_ROLES = [
216
+ "window",
217
+ "dialog",
218
+ "group",
219
+ "list",
220
+ "listItem",
221
+ "text",
222
+ "heading",
223
+ "image",
224
+ "button",
225
+ "link",
226
+ "textField",
227
+ "checkbox",
228
+ "radio",
229
+ "switch",
230
+ "slider",
231
+ "tab",
232
+ "menu",
233
+ "menuItem",
234
+ "web",
235
+ "other"
146
236
  ];
147
- /** How the line travels between its two resolved endpoints. */
148
- const BOARD_CONNECTION_ROUTINGS = [
149
- "straight",
150
- "curve",
151
- "orthogonal"
237
+ const DISPLAY_ELEMENT_STATES = [
238
+ "focused",
239
+ "selected",
240
+ "checked",
241
+ "disabled",
242
+ "editable",
243
+ "password",
244
+ "scrollable",
245
+ "expanded"
152
246
  ];
153
- const BOARD_CONNECTION_LINES = ["solid", "dashed"];
154
- /**
155
- * Relation kind — a free-form slug, not an enum.
156
- *
157
- * Boards are used for domains we do not control, so a closed vocabulary would
158
- * force unrelated meanings into the wrong bucket. The format is constrained
159
- * (lowercase, dash/dot separated) so relations stay comparable and queryable
160
- * across clients instead of accumulating near-duplicate spellings.
161
- */
162
- const BoardRelationSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/, "relation must be a lowercase slug").default("related");
163
- /**
164
- * Waypoints a user dragged the line through, in world space.
165
- *
166
- * Stored because they are *input*, not output: the resolved path is recomputed
167
- * from them on every read, but the intent behind a hand-routed line cannot be
168
- * recovered once discarded.
169
- */
170
- const BoardConnectionWaypointSchema = z.object({
171
- x: z.number().finite(),
172
- y: z.number().finite()
247
+ const DISPLAY_ELEMENT_ACTIONS = [
248
+ "click",
249
+ "longPress",
250
+ "focus",
251
+ "setText",
252
+ "scrollForward",
253
+ "scrollBackward"
254
+ ];
255
+ const DISPLAY_ELEMENT_REF_PATTERN = /^e\d{1,9}\.\d{1,5}$/;
256
+ const DISPLAY_TREE_MAX_ELEMENTS = 1e3;
257
+ const displayElementSchema = z.object({
258
+ ref: z.string().regex(DISPLAY_ELEMENT_REF_PATTERN),
259
+ depth: z.number().int().min(0).max(64),
260
+ role: z.string().max(32).transform((role) => DISPLAY_ELEMENT_ROLES.includes(role) ? role : "other"),
261
+ name: z.string().max(500).optional(),
262
+ /** The current text of a field; never reported for passwords. */
263
+ value: z.string().max(500).optional(),
264
+ bounds: z.tuple([
265
+ unit,
266
+ unit,
267
+ unit,
268
+ unit
269
+ ]),
270
+ states: knownNames(DISPLAY_ELEMENT_STATES, 16).optional(),
271
+ actions: knownNames(DISPLAY_ELEMENT_ACTIONS, 16).optional()
173
272
  });
174
- const BoardConnectionRoutingSchema = z.object({
175
- kind: z.enum(BOARD_CONNECTION_ROUTINGS).default("curve"),
176
- /** Curve bow as a fraction of endpoint distance (-0.85..0.85). */
177
- bend: z.number().finite().min(-.85).max(.85).default(0),
178
- waypoints: z.array(BoardConnectionWaypointSchema).max(64).default([])
273
+ z.object({
274
+ maxElements: z.number().int().min(1).max(DISPLAY_TREE_MAX_ELEMENTS).optional(),
275
+ actionable: z.boolean().optional()
179
276
  });
180
- const BoardConnectionStyleSchema = z.object({
181
- /** Palette color id, resolved to a theme token at render time. */
182
- color: z.string().min(1).max(40).default("brand"),
183
- size: z.number().finite().positive().default(BOARD_CONNECTION_STROKE_SIZE),
184
- line: z.enum(BOARD_CONNECTION_LINES).default("solid")
277
+ z.object({
278
+ width: z.number().int().min(0).max(DISPLAY_DIMENSION_MAX),
279
+ height: z.number().int().min(0).max(DISPLAY_DIMENSION_MAX),
280
+ elements: z.array(displayElementSchema).max(DISPLAY_TREE_MAX_ELEMENTS),
281
+ truncated: z.boolean().optional()
185
282
  });
186
- const DEFAULT_BOARD_CONNECTION_ROUTING = {
187
- kind: "curve",
188
- bend: 0,
189
- waypoints: []
190
- };
191
- const DEFAULT_BOARD_CONNECTION_STYLE = {
192
- color: "brand",
193
- size: BOARD_CONNECTION_STROKE_SIZE,
194
- line: "solid"
195
- };
196
- const BoardConnectionSchema = z.object({
197
- id: z.string().min(1).max(160),
198
- source: BoardConnectionEndpointSchema,
199
- target: BoardConnectionEndpointSchema,
200
- relation: BoardRelationSchema,
201
- direction: z.enum(BOARD_CONNECTION_DIRECTIONS).default("forward"),
202
- label: z.string().max(280).default(""),
203
- routing: BoardConnectionRoutingSchema.default(DEFAULT_BOARD_CONNECTION_ROUTING),
204
- style: BoardConnectionStyleSchema.default(DEFAULT_BOARD_CONNECTION_STYLE),
205
- metadata: z.record(z.string(), z.unknown()).default({})
206
- }).strict();
207
- /**
208
- * A patch to an existing connection.
209
- *
210
- * `id` is excluded: a connection's identity never changes, and re-pointing both
211
- * endpoints is an edit of the same relation, not a new one.
212
- */
213
- const BoardConnectionPatchSchema = BoardConnectionSchema.omit({ id: true }).partial();
214
- //#endregion
215
- //#region ../protocol/dist/board-composition.js
216
- const idSchema$2 = z.string().min(1).max(160);
217
- const finiteSchema$1 = z.number().finite();
218
- const jsonObjectSchema$2 = z.record(z.string(), z.unknown());
219
- const BoardEasingSchema = z.enum([
220
- "linear",
221
- "ease-in-quad",
222
- "ease-out-quad",
223
- "ease-in-out-quad",
224
- "ease-in-cubic",
225
- "ease-out-cubic",
226
- "ease-in-out-cubic",
227
- "ease-out-quart",
228
- "ease-out-expo"
229
- ]);
230
- const BoardAnimationTargetSchema = z.discriminatedUnion("type", [
283
+ const DISPLAY_INPUT_MAX_TEXT = 4096;
284
+ const DISPLAY_INPUT_MAX_SCHEDULE_MS = 6e4;
285
+ const delta = z.number().min(-10).max(10);
286
+ const at = z.number().int().min(0).max(DISPLAY_INPUT_MAX_SCHEDULE_MS).optional();
287
+ const displayInputEventSchema = z.discriminatedUnion("type", [
288
+ z.object({
289
+ type: z.literal("pointer"),
290
+ action: z.enum(DISPLAY_POINTER_ACTIONS),
291
+ x: unit,
292
+ y: unit,
293
+ button: z.enum(DISPLAY_BUTTONS).optional(),
294
+ t: at
295
+ }),
296
+ z.object({
297
+ type: z.literal("scroll"),
298
+ x: unit,
299
+ y: unit,
300
+ dx: delta.optional(),
301
+ dy: delta.optional(),
302
+ t: at
303
+ }),
304
+ z.object({
305
+ type: z.literal("key"),
306
+ action: z.enum(DISPLAY_KEY_ACTIONS),
307
+ key: z.string().min(1).max(64),
308
+ t: at
309
+ }),
310
+ z.object({
311
+ type: z.literal("text"),
312
+ text: z.string().min(1).max(DISPLAY_INPUT_MAX_TEXT),
313
+ t: at
314
+ }),
231
315
  z.object({
232
- type: z.literal("item"),
233
- itemId: idSchema$2
234
- }).strict(),
316
+ type: z.literal("system"),
317
+ action: z.enum(DISPLAY_SYSTEM_ACTIONS),
318
+ t: at
319
+ }),
235
320
  z.object({
236
- type: z.literal("effect"),
237
- effectId: idSchema$2
238
- }).strict(),
239
- z.object({ type: z.literal("camera") }).strict(),
240
- z.object({ type: z.literal("board") }).strict()
321
+ type: z.literal("element"),
322
+ ref: z.string().regex(DISPLAY_ELEMENT_REF_PATTERN),
323
+ action: z.enum(DISPLAY_ELEMENT_ACTIONS),
324
+ text: z.string().max(DISPLAY_INPUT_MAX_TEXT).optional(),
325
+ t: at
326
+ })
241
327
  ]);
242
- const vector2Schema = z.object({
243
- x: finiteSchema$1,
244
- y: finiteSchema$1
245
- }).strict();
246
- const scaleSchema = z.union([finiteSchema$1.nonnegative(), z.object({
247
- x: finiteSchema$1.nonnegative(),
248
- y: finiteSchema$1.nonnegative()
249
- }).strict()]);
250
- const BOARD_ANIMATION_CHANNELS = {
251
- "transform.translation": {
252
- targets: ["item"],
253
- value: vector2Schema,
254
- interpolations: ["linear", "step"],
255
- coordinateSpace: "world-offset",
256
- unit: "board"
257
- },
258
- "transform.rotation": {
259
- targets: ["item"],
260
- value: finiteSchema$1,
261
- interpolations: ["linear", "step"],
262
- unit: "radian"
263
- },
264
- "transform.scale": {
265
- targets: ["item"],
266
- value: scaleSchema,
267
- interpolations: ["linear", "step"],
268
- unit: "ratio"
269
- },
270
- "style.opacity": {
271
- targets: ["item"],
272
- value: finiteSchema$1.min(0).max(1),
273
- interpolations: ["linear", "step"],
274
- unit: "ratio"
275
- }
276
- };
277
- const BoardTrackKeyframeSchema = z.object({
278
- time: finiteSchema$1.nonnegative(),
279
- value: z.unknown(),
280
- easing: BoardEasingSchema.optional()
281
- }).strict();
282
- const BoardTrackSchema = z.object({
283
- id: idSchema$2,
284
- target: BoardAnimationTargetSchema,
285
- channel: z.string().min(1).max(160),
286
- channelVersion: z.number().int().positive().default(1),
287
- interpolation: z.enum(["linear", "step"]).default("linear"),
288
- fill: z.enum([
289
- "none",
290
- "backwards",
291
- "forwards",
292
- "both"
293
- ]).default("none"),
294
- keyframes: z.array(BoardTrackKeyframeSchema).min(1).max(1e5),
295
- metadata: jsonObjectSchema$2.default({})
296
- }).strict().superRefine((track, context) => {
297
- let previous = -1;
298
- for (const [index, keyframe] of track.keyframes.entries()) {
299
- if (keyframe.time <= previous) context.addIssue({
300
- code: "custom",
301
- message: "keyframe times must be strictly increasing",
302
- path: [
303
- "keyframes",
304
- index,
305
- "time"
306
- ]
307
- });
308
- previous = keyframe.time;
309
- }
310
- const channel = BOARD_ANIMATION_CHANNELS[track.channel];
311
- if (!channel) {
312
- if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+){2,}$/.test(track.channel)) context.addIssue({
313
- code: "custom",
314
- message: `unknown channel: ${track.channel}`,
315
- path: ["channel"]
316
- });
317
- return;
318
- }
319
- if (!channel.targets.includes(track.target.type)) context.addIssue({
320
- code: "custom",
321
- message: `${track.channel} cannot target ${track.target.type}`,
322
- path: ["target"]
323
- });
324
- if (!channel.interpolations.includes(track.interpolation)) context.addIssue({
325
- code: "custom",
326
- message: `${track.channel} does not support ${track.interpolation} interpolation`,
327
- path: ["interpolation"]
328
- });
329
- for (const [index, keyframe] of track.keyframes.entries()) {
330
- const parsed = channel.value.safeParse(keyframe.value);
331
- if (!parsed.success) context.addIssue({
332
- code: "custom",
333
- message: parsed.error.issues[0]?.message ?? `invalid value for ${track.channel}`,
334
- path: [
335
- "keyframes",
336
- index,
337
- "value"
338
- ]
339
- });
340
- }
341
- });
342
- const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
343
- const BoardProceduralClipSchema = z.object({
344
- id: idSchema$2,
345
- kind: extensionKindSchema,
346
- kindVersion: z.number().int().positive(),
347
- target: BoardAnimationTargetSchema,
348
- start: finiteSchema$1.nonnegative(),
349
- duration: finiteSchema$1.positive(),
350
- layer: z.enum([
351
- "behind",
352
- "content",
353
- "front",
354
- "screen"
355
- ]).default("content"),
356
- fill: z.enum([
357
- "none",
358
- "backwards",
359
- "forwards",
360
- "both"
361
- ]).default("none"),
362
- easing: BoardEasingSchema.default("linear"),
363
- params: jsonObjectSchema$2.default({}),
364
- assetRefs: z.array(z.object({
365
- type: z.enum(["space-file", "extension"]),
366
- ref: z.string().min(1).max(4096),
367
- digest: z.string().min(16).max(160).optional()
368
- }).strict()).default([]),
369
- seed: idSchema$2,
370
- metadata: jsonObjectSchema$2.default({})
371
- }).strict();
372
- const BoardTimelineMarkerSchema = z.object({
373
- id: idSchema$2,
374
- time: finiteSchema$1.nonnegative(),
375
- duration: finiteSchema$1.nonnegative().optional(),
376
- metadata: jsonObjectSchema$2.default({})
377
- }).strict();
378
- const BoardTimelineSchema = z.object({
379
- duration: finiteSchema$1.nonnegative(),
380
- tracks: z.array(BoardTrackSchema).max(5e4).default([]),
381
- clips: z.array(BoardProceduralClipSchema).max(5e4).default([]),
382
- markers: z.array(BoardTimelineMarkerSchema).max(1e4).default([])
383
- }).strict().superRefine((timeline, context) => {
384
- const ids = /* @__PURE__ */ new Set();
385
- const channels = /* @__PURE__ */ new Set();
386
- for (const [index, track] of timeline.tracks.entries()) {
387
- if (ids.has(track.id)) context.addIssue({
388
- code: "custom",
389
- message: `duplicate timeline id: ${track.id}`,
390
- path: [
391
- "tracks",
392
- index,
393
- "id"
394
- ]
395
- });
396
- ids.add(track.id);
397
- const targetId = track.target.type === "item" ? track.target.itemId : track.target.type === "effect" ? track.target.effectId : track.target.type;
398
- const key = `${track.target.type}:${targetId}:${track.channel}`;
399
- if (channels.has(key)) context.addIssue({
328
+ z.object({ events: z.array(displayInputEventSchema).min(1).max(512) }).superRefine(({ events }, ctx) => {
329
+ let last = 0;
330
+ events.forEach((event, index) => {
331
+ if (event.type === "scroll" && !event.dx && !event.dy) ctx.addIssue({
400
332
  code: "custom",
401
- message: `multiple tracks control ${key}`,
402
- path: [
403
- "tracks",
404
- index,
405
- "channel"
406
- ]
333
+ path: ["events", index],
334
+ message: "scroll needs dx or dy"
407
335
  });
408
- channels.add(key);
409
- for (const [keyframeIndex, keyframe] of track.keyframes.entries()) if (keyframe.time > timeline.duration) context.addIssue({
336
+ if (event.type === "element" && event.action === "setText" !== (event.text !== void 0)) ctx.addIssue({
410
337
  code: "custom",
411
- message: "keyframe exceeds timeline duration",
412
338
  path: [
413
- "tracks",
339
+ "events",
414
340
  index,
415
- "keyframes",
416
- keyframeIndex,
417
- "time"
418
- ]
341
+ "text"
342
+ ],
343
+ message: "text goes with setText only"
419
344
  });
420
- }
421
- for (const [index, clip] of timeline.clips.entries()) {
422
- if (ids.has(clip.id)) context.addIssue({
345
+ if (event.t === void 0) return;
346
+ if (event.t < last) ctx.addIssue({
423
347
  code: "custom",
424
- message: `duplicate timeline id: ${clip.id}`,
425
348
  path: [
426
- "clips",
349
+ "events",
427
350
  index,
428
- "id"
429
- ]
351
+ "t"
352
+ ],
353
+ message: "t must ascend"
430
354
  });
431
- ids.add(clip.id);
432
- if (clip.start + clip.duration > timeline.duration) context.addIssue({
433
- code: "custom",
434
- message: "clip exceeds timeline duration",
435
- path: [
436
- "clips",
437
- index,
438
- "duration"
439
- ]
440
- });
441
- }
442
- for (const [index, marker] of timeline.markers.entries()) {
443
- if (ids.has(marker.id)) context.addIssue({
444
- code: "custom",
445
- message: `duplicate timeline id: ${marker.id}`,
446
- path: [
447
- "markers",
448
- index,
449
- "id"
450
- ]
451
- });
452
- ids.add(marker.id);
453
- if (marker.time + (marker.duration ?? 0) > timeline.duration) context.addIssue({
454
- code: "custom",
455
- message: "marker exceeds timeline duration",
456
- path: ["markers", index]
457
- });
458
- }
459
- });
460
- const BoardCompositionPlaybackSchema = z.object({
461
- loop: z.boolean().default(false),
462
- endBehavior: z.enum(["hold", "reset"]).default("hold"),
463
- reducedMotion: z.discriminatedUnion("mode", [
464
- z.object({ mode: z.literal("base") }).strict(),
465
- z.object({
466
- mode: z.literal("time"),
467
- time: finiteSchema$1.nonnegative()
468
- }).strict(),
469
- z.object({
470
- mode: z.literal("marker"),
471
- markerId: idSchema$2
472
- }).strict()
473
- ]).default({ mode: "base" })
474
- }).strict();
475
- const compositionFields = {
476
- id: idSchema$2,
477
- name: z.string().min(1).max(255),
478
- timeline: BoardTimelineSchema,
479
- playback: BoardCompositionPlaybackSchema.default({
480
- loop: false,
481
- endBehavior: "hold",
482
- reducedMotion: { mode: "base" }
483
- }),
484
- metadata: jsonObjectSchema$2.default({})
485
- };
486
- function validateCompositionFallback(composition, context) {
487
- const fallback = composition.playback.reducedMotion;
488
- if (fallback.mode === "time" && fallback.time > composition.timeline.duration) context.addIssue({
489
- code: "custom",
490
- message: "reduced-motion time exceeds timeline duration",
491
- path: [
492
- "playback",
493
- "reducedMotion",
494
- "time"
495
- ]
496
- });
497
- if (fallback.mode === "marker" && !composition.timeline.markers.some((marker) => marker.id === fallback.markerId)) context.addIssue({
498
- code: "custom",
499
- message: `marker does not exist: ${fallback.markerId}`,
500
- path: [
501
- "playback",
502
- "reducedMotion",
503
- "markerId"
504
- ]
355
+ last = event.t;
505
356
  });
357
+ });
358
+ z.object({
359
+ format: z.enum(["jpeg", "png"]).optional(),
360
+ quality: z.number().int().min(1).max(100).optional(),
361
+ maxSize: z.number().int().min(1).max(DISPLAY_DIMENSION_MAX).optional()
362
+ });
363
+ z.object({
364
+ urls: z.union([z.string().min(1).max(512), z.array(z.string().min(1).max(512)).min(1).max(16)]),
365
+ username: z.string().max(512).optional(),
366
+ credential: z.string().max(512).optional()
367
+ });
368
+ z.object({
369
+ offer: z.string().min(1).max(65536),
370
+ control: z.boolean().optional()
371
+ });
372
+ const DISPLAY_CONTROL_CHANNEL = "control";
373
+ const DISPLAY_INPUT_CHANNEL = "input";
374
+ const DISPLAY_PING_INTERVAL_MS = 5e3;
375
+ function normalizeDisplayPoint(x, y, width, height) {
376
+ const unit = (value, extent) => Math.min(1, Math.max(0, extent > 1 ? value / (extent - 1) : 0));
377
+ return {
378
+ x: unit(x, width),
379
+ y: unit(y, height)
380
+ };
506
381
  }
507
- const BoardCompositionInputSchema = z.object(compositionFields).strict().superRefine(validateCompositionFallback);
508
- const BoardCompositionSchema = z.object({
509
- ...compositionFields,
510
- revision: z.number().int().nonnegative().default(0)
511
- }).strict().superRefine(validateCompositionFallback);
512
- /**
513
- * Accept inspect/get output as apply input while stripping server-owned
514
- * `revision`. Anything else that looks server-owned (or simply unknown) fails
515
- * with a pointer at the offending key, so a get → edit → apply round-trip that
516
- * accidentally carries runtime fields names the culprit instead of a bare
517
- * "Unrecognized key".
518
- */
519
- function parseBoardCompositionInput(value) {
520
- if (value && typeof value === "object" && !Array.isArray(value)) {
521
- const { revision: _revision, ...input } = value;
522
- const result = BoardCompositionInputSchema.safeParse(input);
523
- if (!result.success) {
524
- const issue = result.error.issues.find((item) => item.code === "unrecognized_keys");
525
- if (issue) throw new Error(`unknown field in composition input: ${issue.message}. Server-owned fields such as revision are stripped automatically; remove other fields not part of the composition schema`);
526
- throw result.error;
382
+ function splitDisplayText(text, max = DISPLAY_INPUT_MAX_TEXT) {
383
+ const pieces = [];
384
+ let piece = "";
385
+ for (const char of text) {
386
+ if (piece.length + char.length > max) {
387
+ pieces.push(piece);
388
+ piece = "";
527
389
  }
528
- return result.data;
390
+ piece += char;
529
391
  }
530
- return BoardCompositionInputSchema.parse(value);
392
+ if (piece) pieces.push(piece);
393
+ return pieces;
531
394
  }
532
- const BOARD_ANIMATION_CHANNEL_CAPABILITIES = Object.entries(BOARD_ANIMATION_CHANNELS).map(([id, definition]) => ({
533
- id,
534
- version: 1,
535
- targets: definition.targets,
536
- interpolations: definition.interpolations,
537
- ..."coordinateSpace" in definition ? { coordinateSpace: definition.coordinateSpace } : {},
538
- ..."unit" in definition ? { unit: definition.unit } : {},
539
- valueSchema: z.toJSONSchema(definition.value)
540
- }));
541
- //#endregion
542
- //#region ../protocol/dist/board-effect.js
543
- /**
544
- * Board effect schema, isolated so `board-authoring.ts` can reuse the exact
545
- * definition without a runtime cycle through `board.ts`. One definition, one
546
- * strictness level — semantic mutations and Board create must not drift apart.
547
- */
548
- const idSchema$1 = z.string().min(1).max(160);
549
- const extensionIdSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
550
- const jsonObjectSchema$1 = z.record(z.string(), z.unknown());
551
- const BoardAssetRefSchema = z.object({
552
- type: z.enum(["space-file", "extension"]),
553
- ref: z.string().min(1).max(4096),
554
- digest: z.string().min(16).max(160).optional()
555
- }).strict();
556
- const BoardEffectSchema = z.object({
557
- id: idSchema$1,
558
- boardId: z.string().uuid(),
559
- target: z.discriminatedUnion("type", [z.object({
560
- type: z.literal("item"),
561
- itemId: idSchema$1
562
- }).strict(), z.object({ type: z.literal("board") }).strict()]),
563
- kind: extensionIdSchema,
564
- kindVersion: z.number().int().positive().default(1),
565
- enabled: z.boolean().default(true),
566
- lifecycle: z.enum([
567
- "persistent",
568
- "when-visible",
569
- "manual",
570
- "on-enter"
571
- ]),
572
- timeOrigin: z.enum([
573
- "board",
574
- "visible",
575
- "activation"
576
- ]),
577
- layer: z.enum([
578
- "behind",
579
- "front",
580
- "screen"
581
- ]).default("front"),
582
- seed: z.string().min(1).max(160),
583
- params: jsonObjectSchema$1.default({}),
584
- assetRefs: z.array(BoardAssetRefSchema).default([]),
585
- metadata: jsonObjectSchema$1.default({}),
586
- revision: z.number().int().nonnegative()
587
- }).strict();
588
- /** The client-authored effect form: no server-owned `boardId`/`revision`. */
589
- const BoardEffectInputSchema = BoardEffectSchema.omit({
590
- boardId: true,
591
- revision: true
592
- });
593
- /** Accept a read projection in a write command while stripping server fields. */
594
- function parseBoardEffectInput(value) {
595
- if (value && typeof value === "object" && !Array.isArray(value)) {
596
- const { boardId: _boardId, revision: _revision, ...input } = value;
597
- return BoardEffectInputSchema.parse(input);
598
- }
599
- return BoardEffectInputSchema.parse(value);
600
- }
601
- //#endregion
602
- //#region ../protocol/dist/board-authoring.js
603
- const BOARD_AUTHORING_COLOR_IDS = [
604
- "brand",
605
- "neutral",
606
- "black",
607
- "white",
608
- "blue",
609
- "green",
610
- "amber",
611
- "violet",
612
- "rose"
613
- ];
614
- const BOARD_AUTHORING_GEO_KINDS = [
615
- "rectangle",
616
- "rounded",
617
- "ellipse",
618
- "diamond",
619
- "triangle"
620
- ];
621
- const idSchema = z.string().min(1).max(160);
622
- const jsonObjectSchema = z.record(z.string(), z.unknown());
623
- const finiteSchema = z.number().finite();
624
- const extensionTypeSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
625
- const BoardAuthoringPositionSchema = z.object({
626
- x: finiteSchema,
627
- y: finiteSchema
628
- }).strict();
629
- const BoardAuthoringSizeSchema = z.object({
630
- width: finiteSchema.positive(),
631
- height: finiteSchema.positive()
632
- }).strict();
633
- const BoardAuthoringPositionPatchSchema = z.object({
634
- x: finiteSchema.optional(),
635
- y: finiteSchema.optional()
636
- }).strict();
637
- const pointSchema = z.object({
638
- x: finiteSchema,
639
- y: finiteSchema,
640
- p: finiteSchema.min(0).max(1).default(.5)
641
- }).strict();
642
- const worldPointSchema = z.object({
643
- x: finiteSchema,
644
- y: finiteSchema
645
- }).strict();
646
- const cropSchema = z.object({
647
- x: finiteSchema.min(0).max(1),
648
- y: finiteSchema.min(0).max(1),
649
- w: finiteSchema.min(0).max(1),
650
- h: finiteSchema.min(0).max(1)
651
- }).strict();
652
- const BoardItemStyleSchema = z.object({
653
- color: z.enum(BOARD_AUTHORING_COLOR_IDS).optional(),
654
- strokeWidth: finiteSchema.min(1).max(64).optional(),
655
- fillOpacity: finiteSchema.min(0).max(1).optional()
656
- }).strict();
657
- const sourceSnapshotSchema = z.record(z.string(), z.unknown());
658
- function isSafeBoardSourcePath(value) {
659
- return value.length > 0 && value.length <= 4096 && !value.startsWith("/") && !value.startsWith("\\") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
660
- }
661
- const BoardItemSourceSchema = z.object({
662
- kind: z.literal("space-file"),
663
- path: z.string().refine(isSafeBoardSourcePath, "source path must be a safe relative Board file path"),
664
- snapshot: sourceSnapshotSchema.optional()
665
- }).strict();
666
- const sizedBaseFields = {
667
- id: idSchema,
668
- position: BoardAuthoringPositionSchema.default({
669
- x: 0,
670
- y: 0
671
- }),
672
- rotation: finiteSchema.default(0),
673
- parentId: idSchema.nullable().optional(),
674
- locked: z.boolean().optional(),
675
- metadata: jsonObjectSchema.optional(),
676
- size: BoardAuthoringSizeSchema.optional()
677
- };
678
- const styledBaseFields = {
679
- ...sizedBaseFields,
680
- style: BoardItemStyleSchema.optional()
681
- };
682
- const { position: _positionField, size: _sizeField, ...strokeBaseFields } = sizedBaseFields;
683
- const strokeStyledBaseFields = {
684
- ...strokeBaseFields,
685
- rotation: z.literal(0).default(0),
686
- style: BoardItemStyleSchema.optional()
687
- };
688
- const BoardTextAuthoringItemSchema = z.object({
689
- ...styledBaseFields,
690
- type: z.literal("text"),
691
- props: z.object({
692
- text: z.string().default(""),
693
- fontSize: finiteSchema.min(2).max(512).default(24)
694
- }).strict()
695
- }).strict();
696
- const BoardGeoAuthoringItemSchema = z.object({
697
- ...styledBaseFields,
698
- type: z.literal("geo"),
699
- props: z.object({
700
- shape: z.enum(BOARD_AUTHORING_GEO_KINDS).default("rectangle"),
701
- text: z.string().default("")
702
- }).strict()
703
- }).strict();
704
- const BoardDrawAuthoringItemSchema = z.object({
705
- ...strokeStyledBaseFields,
706
- type: z.literal("draw"),
707
- props: z.object({ points: z.array(pointSchema).min(1) }).strict()
708
- }).strict();
709
- const BoardArrowAuthoringItemSchema = z.object({
710
- ...strokeStyledBaseFields,
711
- type: z.literal("arrow"),
712
- props: z.object({
713
- start: worldPointSchema,
714
- end: worldPointSchema,
715
- bend: finiteSchema.min(-.85).max(.85).default(0),
716
- arrowStart: z.boolean().default(false),
717
- arrowEnd: z.boolean().default(true),
718
- label: z.string().default("")
719
- }).strict()
720
- }).strict();
721
- const BoardFrameAuthoringItemSchema = z.object({
722
- ...styledBaseFields,
723
- type: z.literal("frame"),
724
- props: z.object({ label: z.string().default("Frame") }).strict()
725
- }).strict();
726
- const fileBackedFields = {
727
- ...sizedBaseFields,
728
- style: z.object({}).strict().optional(),
729
- source: BoardItemSourceSchema
730
- };
731
- const builtinItemSchemas = [
732
- BoardTextAuthoringItemSchema,
733
- BoardGeoAuthoringItemSchema,
734
- BoardDrawAuthoringItemSchema,
735
- BoardArrowAuthoringItemSchema,
736
- BoardFrameAuthoringItemSchema,
737
- z.object({
738
- ...fileBackedFields,
739
- type: z.literal("image"),
740
- props: z.object({ crop: cropSchema.optional() }).strict()
741
- }).strict(),
742
- z.object({
743
- ...fileBackedFields,
744
- type: z.literal("video"),
745
- props: z.object({}).strict()
746
- }).strict(),
747
- z.object({
748
- ...fileBackedFields,
749
- type: z.literal("audio"),
750
- props: z.object({}).strict()
751
- }).strict(),
752
- z.object({
753
- ...fileBackedFields,
754
- type: z.literal("file"),
755
- props: z.object({}).strict()
756
- }).strict(),
757
- z.object({
758
- ...sizedBaseFields,
759
- type: z.literal("task"),
760
- props: z.object({
761
- taskRunId: z.string().min(1),
762
- snapshot: z.record(z.string(), z.unknown())
763
- }).strict(),
764
- style: z.object({}).strict().optional()
765
- }).strict()
766
- ];
767
- const BoardBuiltinAuthoringItemSchema = z.discriminatedUnion("type", builtinItemSchemas);
768
- const BoardExtensionAuthoringItemSchema = z.object({
769
- ...sizedBaseFields,
770
- type: extensionTypeSchema,
771
- kindVersion: z.number().int().positive(),
772
- props: jsonObjectSchema,
773
- style: jsonObjectSchema.optional(),
774
- source: z.object({
775
- kind: z.string().min(1).max(80),
776
- ref: z.string().min(1).max(4096),
777
- snapshot: jsonObjectSchema.optional()
778
- }).strict().optional()
779
- }).strict();
780
- const BoardAuthoringItemSchema = z.union([BoardBuiltinAuthoringItemSchema, BoardExtensionAuthoringItemSchema]);
781
- /** JSON Merge Patch semantics, constrained to the stable Item envelope. */
782
- const BoardItemPatchSchema = z.object({
783
- position: BoardAuthoringPositionPatchSchema.optional(),
784
- size: BoardAuthoringSizeSchema.nullable().optional(),
785
- rotation: finiteSchema.optional(),
786
- parentId: idSchema.nullable().optional(),
787
- locked: z.boolean().nullable().optional(),
788
- props: jsonObjectSchema.optional(),
789
- style: jsonObjectSchema.nullable().optional(),
790
- source: jsonObjectSchema.nullable().optional(),
791
- metadata: jsonObjectSchema.nullable().optional()
792
- }).strict().refine((patch) => Object.keys(patch).length > 0, "item patch is empty");
793
- const BoardConnectionInputSchema = BoardConnectionSchema;
794
- const stripServerFields = (value) => {
795
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
796
- const { boardId: _boardId, revision: _revision, ...input } = value;
797
- return input;
798
- };
799
- const BoardEffectCommandInputSchema = z.preprocess(stripServerFields, BoardEffectInputSchema);
800
- const BoardCompositionCommandInputSchema = z.preprocess(stripServerFields, BoardCompositionInputSchema);
801
- const BoardSemanticCommandSchema = z.discriminatedUnion("type", [
802
- z.object({
803
- type: z.literal("board.patch"),
804
- patch: z.object({
805
- title: z.string().min(1).max(255).optional(),
806
- metadata: jsonObjectSchema.nullable().optional(),
807
- metadataPatch: jsonObjectSchema.optional()
808
- }).strict().refine((patch) => Object.keys(patch).length > 0, "board patch is empty")
809
- }).strict(),
810
- z.object({
811
- type: z.literal("item.create"),
812
- item: BoardAuthoringItemSchema
813
- }).strict(),
814
- z.object({
815
- type: z.literal("item.patch"),
816
- itemId: idSchema,
817
- patch: BoardItemPatchSchema
818
- }).strict(),
819
- z.object({
820
- type: z.literal("item.replace"),
821
- itemId: idSchema,
822
- item: BoardAuthoringItemSchema
823
- }).strict(),
824
- z.object({
825
- type: z.literal("item.delete"),
826
- itemId: idSchema,
827
- cascade: z.boolean().default(false)
828
- }).strict(),
829
- z.object({
830
- type: z.literal("item.reorder"),
831
- itemId: idSchema,
832
- index: z.number().int().nonnegative()
833
- }).strict(),
834
- z.object({
835
- type: z.literal("connection.create"),
836
- connection: BoardConnectionInputSchema
837
- }).strict(),
838
- z.object({
839
- type: z.literal("connection.patch"),
840
- connectionId: idSchema,
841
- patch: BoardConnectionPatchSchema
842
- }).strict(),
843
- z.object({
844
- type: z.literal("connection.delete"),
845
- connectionId: idSchema
846
- }).strict(),
847
- z.object({
848
- type: z.literal("effect.apply"),
849
- effect: BoardEffectCommandInputSchema
850
- }).strict(),
851
- z.object({
852
- type: z.literal("effect.delete"),
853
- effectId: idSchema
854
- }).strict(),
855
- z.object({
856
- type: z.literal("composition.apply"),
857
- composition: BoardCompositionCommandInputSchema
858
- }).strict(),
859
- z.object({
860
- type: z.literal("composition.delete"),
861
- compositionId: idSchema
862
- }).strict()
863
- ]);
864
- const BoardSemanticMutationSchema = z.object({
865
- mutationId: idSchema,
866
- baseVersion: z.number().int().nonnegative(),
867
- clientId: idSchema.optional(),
868
- undoGroupId: idSchema.optional(),
869
- /** Validate (including server-side reference checks) without writing. */
870
- dryRun: z.boolean().default(false),
871
- commands: z.array(BoardSemanticCommandSchema).min(1).max(5e4)
872
- }).strict();
873
- z.object({
874
- include: z.array(z.enum([
875
- "items",
876
- "connections",
877
- "effects",
878
- "compositions",
879
- "playback"
880
- ])).optional(),
881
- itemIds: z.array(idSchema).max(5e4).optional(),
882
- connectionIds: z.array(idSchema).max(5e4).optional(),
883
- effectIds: z.array(idSchema).max(5e4).optional(),
884
- compositionIds: z.array(idSchema).max(5e4).optional(),
885
- viewport: z.object({
886
- x: finiteSchema,
887
- y: finiteSchema,
888
- width: finiteSchema.positive(),
889
- height: finiteSchema.positive()
890
- }).optional()
891
- }).strict();
892
395
  //#endregion
893
396
  //#region ../protocol/dist/space-hooks.js
894
397
  const SPACE_HOOK_WEBHOOK_SECRET_HEADER = "x-cohub-webhook-secret";
@@ -1472,14 +975,16 @@ var SearchApi = class {
1472
975
  for (const type of input.types ?? []) params.append("type", type);
1473
976
  if (input.spaceId) params.set("spaceId", input.spaceId);
1474
977
  if (input.labelRef) params.set("labelRef", input.labelRef);
1475
- if (input.groupTurns === false) params.set("groupTurns", "false");
1476
978
  return this.transport.request(`/api/search?${params.toString()}`, { fetch: customFetch });
1477
979
  }
1478
980
  /** Default (empty-query) command palette data: viewer-relative space signals. */
1479
981
  overview(input, customFetch) {
1480
982
  const params = new URLSearchParams();
1481
983
  if (input?.spaceLimit !== void 0) params.set("spaceLimit", String(input.spaceLimit));
1482
- if (input?.sessionLimit !== void 0) params.set("sessionLimit", String(input.sessionLimit));
984
+ for (const recent of input?.recentSpaces ?? []) {
985
+ params.append("recentSpaceId", recent.id);
986
+ params.append("recentSpaceAt", new Date(recent.timestamp).toISOString());
987
+ }
1483
988
  for (const spaceId of input?.recentSpaceIds ?? []) params.append("recentSpaceId", spaceId);
1484
989
  const query = params.toString();
1485
990
  return this.transport.request(`/api/palette/overview${query ? `?${query}` : ""}`, { fetch: customFetch });
@@ -1546,6 +1051,354 @@ function ensureRealtimeConnected(websocketClient) {
1546
1051
  });
1547
1052
  }
1548
1053
  //#endregion
1054
+ //#region src/display-stats.ts
1055
+ const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1056
+ function perFrame(seconds, previousSeconds, frames, previousFrames) {
1057
+ const count = frames - previousFrames;
1058
+ return count > 0 ? Math.round((seconds - previousSeconds) * 1e3 / count) : null;
1059
+ }
1060
+ function readDisplayStats(report, previous, fallbackRttMs = null) {
1061
+ const stats = {
1062
+ path: null,
1063
+ relayProtocol: null,
1064
+ rttMs: fallbackRttMs,
1065
+ fps: null,
1066
+ bitrate: null,
1067
+ width: null,
1068
+ height: null,
1069
+ jitterBufferMs: null,
1070
+ decodeMs: null,
1071
+ packetsLost: null,
1072
+ freezes: null
1073
+ };
1074
+ let counters = null;
1075
+ report.forEach((entry) => {
1076
+ if (entry.type === "candidate-pair" && entry.nominated && entry.state === "succeeded") {
1077
+ const local = report.get(String(entry.localCandidateId));
1078
+ const remote = report.get(String(entry.remoteCandidateId));
1079
+ stats.path = local?.candidateType === "relay" || remote?.candidateType === "relay" ? "relay" : "direct";
1080
+ if (local?.candidateType === "relay" && typeof local.relayProtocol === "string") stats.relayProtocol = local.relayProtocol;
1081
+ const rtt = num(entry.currentRoundTripTime);
1082
+ if (rtt !== null) stats.rttMs = Math.round(rtt * 1e3);
1083
+ }
1084
+ if (entry.type === "inbound-rtp" && entry.kind === "video") {
1085
+ stats.fps = num(entry.framesPerSecond);
1086
+ stats.width = num(entry.frameWidth);
1087
+ stats.height = num(entry.frameHeight);
1088
+ stats.packetsLost = num(entry.packetsLost);
1089
+ stats.freezes = num(entry.freezeCount);
1090
+ counters = {
1091
+ at: num(entry.timestamp) ?? 0,
1092
+ bytes: num(entry.bytesReceived) ?? 0,
1093
+ jitterBufferDelay: num(entry.jitterBufferDelay) ?? 0,
1094
+ jitterBufferEmitted: num(entry.jitterBufferEmittedCount) ?? 0,
1095
+ decodeTime: num(entry.totalDecodeTime) ?? 0,
1096
+ decoded: num(entry.framesDecoded) ?? 0
1097
+ };
1098
+ if (previous && counters.at > previous.at) {
1099
+ stats.bitrate = Math.round((counters.bytes - previous.bytes) * 8 * 1e3 / (counters.at - previous.at));
1100
+ stats.jitterBufferMs = perFrame(counters.jitterBufferDelay, previous.jitterBufferDelay, counters.jitterBufferEmitted, previous.jitterBufferEmitted);
1101
+ stats.decodeMs = perFrame(counters.decodeTime, previous.decodeTime, counters.decoded, previous.decoded);
1102
+ }
1103
+ }
1104
+ });
1105
+ return {
1106
+ stats,
1107
+ counters
1108
+ };
1109
+ }
1110
+ //#endregion
1111
+ //#region src/display-session.ts
1112
+ const DEFAULT_GATHER_TIMEOUT_MS = 2e3;
1113
+ const DEFAULT_CONNECT_TIMEOUT_MS = 2e4;
1114
+ const INPUT_BACKLOG_BYTES = 65536;
1115
+ var DisplayConnection = class DisplayConnection {
1116
+ api;
1117
+ displayId;
1118
+ control;
1119
+ pc;
1120
+ stream = new MediaStream();
1121
+ currentState = "connecting";
1122
+ currentDisplay = null;
1123
+ reason = null;
1124
+ sessionId = null;
1125
+ listeners = /* @__PURE__ */ new Map();
1126
+ controlChannel;
1127
+ inputChannel;
1128
+ pingTimer = null;
1129
+ connectTimer = null;
1130
+ pingId = 0;
1131
+ lastPingAt = 0;
1132
+ rtt = null;
1133
+ counters = null;
1134
+ isPaused = false;
1135
+ constructor(api, displayId, control, pc) {
1136
+ this.api = api;
1137
+ this.displayId = displayId;
1138
+ this.control = control;
1139
+ this.pc = pc;
1140
+ pc.addTransceiver("video", { direction: "recvonly" });
1141
+ pc.ontrack = (event) => {
1142
+ const receiver = event.receiver;
1143
+ if ("jitterBufferTarget" in receiver) receiver.jitterBufferTarget = 0;
1144
+ this.stream.addTrack(event.track);
1145
+ };
1146
+ pc.onconnectionstatechange = () => {
1147
+ if (pc.connectionState === "connected") this.setState("connected");
1148
+ else if (pc.connectionState === "failed") this.close("failed");
1149
+ };
1150
+ this.controlChannel = pc.createDataChannel(DISPLAY_CONTROL_CHANNEL);
1151
+ this.controlChannel.onmessage = (event) => this.onControl(event.data);
1152
+ this.controlChannel.onopen = () => {
1153
+ if (this.isPaused) this.sendControl({ type: "pause" });
1154
+ this.startPings();
1155
+ };
1156
+ this.controlChannel.onclose = () => this.close(this.reason ?? "closed");
1157
+ this.inputChannel = control ? pc.createDataChannel(DISPLAY_INPUT_CHANNEL) : null;
1158
+ }
1159
+ static async open(api, displayId, options = {}) {
1160
+ const { iceServers } = await api.iceServers({ signal: options.signal });
1161
+ const pc = new RTCPeerConnection({
1162
+ iceServers,
1163
+ bundlePolicy: "max-bundle"
1164
+ });
1165
+ const connection = new DisplayConnection(api, displayId, options.control ?? true, pc);
1166
+ try {
1167
+ await pc.setLocalDescription(await pc.createOffer());
1168
+ await gathered(pc, options.gatherTimeoutMs ?? DEFAULT_GATHER_TIMEOUT_MS, options.signal);
1169
+ const offer = pc.localDescription?.sdp;
1170
+ if (!offer) throw new Error("The browser produced no offer");
1171
+ const session = await api.openSession(displayId, {
1172
+ offer,
1173
+ control: connection.control
1174
+ }, { signal: options.signal });
1175
+ connection.sessionId = session.sessionId;
1176
+ await pc.setRemoteDescription({
1177
+ type: "answer",
1178
+ sdp: session.answer
1179
+ });
1180
+ connection.connectTimer = setTimeout(() => connection.close("connect_timeout"), options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS);
1181
+ return connection;
1182
+ } catch (error) {
1183
+ connection.close("failed");
1184
+ throw error;
1185
+ }
1186
+ }
1187
+ get state() {
1188
+ return this.currentState;
1189
+ }
1190
+ get display() {
1191
+ return this.currentDisplay;
1192
+ }
1193
+ get closeReason() {
1194
+ return this.currentState === "closed" ? this.reason : null;
1195
+ }
1196
+ on(event, listener) {
1197
+ let set = this.listeners.get(event);
1198
+ if (!set) {
1199
+ set = /* @__PURE__ */ new Set();
1200
+ this.listeners.set(event, set);
1201
+ }
1202
+ set.add(listener);
1203
+ return () => set.delete(listener);
1204
+ }
1205
+ emit(event, value) {
1206
+ for (const listener of this.listeners.get(event) ?? []) listener(value);
1207
+ }
1208
+ setState(state) {
1209
+ if (this.currentState === state || this.currentState === "closed") return;
1210
+ this.currentState = state;
1211
+ if (state === "connected" && this.connectTimer) {
1212
+ clearTimeout(this.connectTimer);
1213
+ this.connectTimer = null;
1214
+ }
1215
+ this.emit("state", state);
1216
+ }
1217
+ sendInput(events) {
1218
+ const channel = this.inputChannel;
1219
+ if (channel?.readyState !== "open" || events.length === 0) return false;
1220
+ if (channel.bufferedAmount > INPUT_BACKLOG_BYTES && events.every((event) => event.type === "pointer" && event.action === "move")) return false;
1221
+ channel.send(JSON.stringify({ events }));
1222
+ return true;
1223
+ }
1224
+ requestKeyframe() {
1225
+ this.sendControl({ type: "keyframe" });
1226
+ }
1227
+ get paused() {
1228
+ return this.isPaused;
1229
+ }
1230
+ pause() {
1231
+ if (this.isPaused) return;
1232
+ this.isPaused = true;
1233
+ this.sendControl({ type: "pause" });
1234
+ }
1235
+ resume() {
1236
+ if (!this.isPaused) return;
1237
+ this.isPaused = false;
1238
+ this.counters = null;
1239
+ this.sendControl({ type: "resume" });
1240
+ }
1241
+ async stats() {
1242
+ const { stats, counters } = readDisplayStats(await this.pc.getStats(), this.counters, this.rtt);
1243
+ this.counters = counters;
1244
+ return stats;
1245
+ }
1246
+ close(reason = "closed") {
1247
+ if (this.currentState === "closed") return;
1248
+ this.reason = reason;
1249
+ if (this.pingTimer) clearInterval(this.pingTimer);
1250
+ if (this.connectTimer) clearTimeout(this.connectTimer);
1251
+ if (this.sessionId) this.api.closeSession(this.displayId, this.sessionId).catch(() => void 0);
1252
+ this.sessionId = null;
1253
+ for (const track of this.stream.getTracks()) track.stop();
1254
+ this.pc.close();
1255
+ this.currentState = "closed";
1256
+ this.emit("state", "closed");
1257
+ }
1258
+ startPings() {
1259
+ const ping = () => {
1260
+ this.pingId += 1;
1261
+ this.lastPingAt = performance.now();
1262
+ this.sendControl({
1263
+ type: "ping",
1264
+ id: this.pingId
1265
+ });
1266
+ };
1267
+ ping();
1268
+ this.pingTimer = setInterval(ping, DISPLAY_PING_INTERVAL_MS);
1269
+ }
1270
+ sendControl(message) {
1271
+ if (this.controlChannel.readyState === "open") this.controlChannel.send(JSON.stringify(message));
1272
+ }
1273
+ onControl(data) {
1274
+ if (typeof data !== "string") return;
1275
+ let message;
1276
+ try {
1277
+ message = JSON.parse(data);
1278
+ } catch {
1279
+ return;
1280
+ }
1281
+ switch (message.type) {
1282
+ case "ready":
1283
+ case "display":
1284
+ this.currentDisplay = message.display;
1285
+ this.emit("display", message.display);
1286
+ break;
1287
+ case "pong":
1288
+ if (message.id === this.pingId) this.rtt = Math.round(performance.now() - this.lastPingAt);
1289
+ break;
1290
+ case "error":
1291
+ this.emit("error", {
1292
+ code: message.code,
1293
+ message: message.message
1294
+ });
1295
+ break;
1296
+ case "closed":
1297
+ this.sessionId = null;
1298
+ this.close(message.reason);
1299
+ }
1300
+ }
1301
+ };
1302
+ function gathered(pc, timeoutMs, signal) {
1303
+ if (pc.iceGatheringState === "complete") return Promise.resolve();
1304
+ return new Promise((resolve, reject) => {
1305
+ const done = () => {
1306
+ clearTimeout(timer);
1307
+ pc.removeEventListener("icegatheringstatechange", check);
1308
+ signal?.removeEventListener("abort", abort);
1309
+ resolve();
1310
+ };
1311
+ const check = () => {
1312
+ if (pc.iceGatheringState === "complete") done();
1313
+ };
1314
+ const abort = () => {
1315
+ clearTimeout(timer);
1316
+ pc.removeEventListener("icegatheringstatechange", check);
1317
+ reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
1318
+ };
1319
+ const timer = setTimeout(done, timeoutMs);
1320
+ pc.addEventListener("icegatheringstatechange", check);
1321
+ signal?.addEventListener("abort", abort, { once: true });
1322
+ });
1323
+ }
1324
+ function connectDisplay(api, displayId, options) {
1325
+ return DisplayConnection.open(api, displayId, options);
1326
+ }
1327
+ //#endregion
1328
+ //#region src/apis/displays.ts
1329
+ const ICE_REFRESH_MARGIN_MS = 3e5;
1330
+ var SpaceDisplaysApi = class {
1331
+ transport;
1332
+ spaceId;
1333
+ ice = null;
1334
+ constructor(transport, spaceId) {
1335
+ this.transport = transport;
1336
+ this.spaceId = spaceId;
1337
+ }
1338
+ path(displayId, suffix = "") {
1339
+ return `/api/spaces/${this.spaceId}/displays/${encodeURIComponent(displayId)}${suffix}`;
1340
+ }
1341
+ list(options = {}) {
1342
+ return this.transport.request(`/api/spaces/${this.spaceId}/displays`, { signal: options.signal });
1343
+ }
1344
+ startVirtual(params = {}) {
1345
+ return this.transport.request(`/api/spaces/${this.spaceId}/displays/virtual`, {
1346
+ method: "POST",
1347
+ headers: { "Content-Type": "application/json" },
1348
+ body: JSON.stringify(params)
1349
+ });
1350
+ }
1351
+ stopVirtual() {
1352
+ return this.transport.request(`/api/spaces/${this.spaceId}/displays/virtual`, { method: "DELETE" });
1353
+ }
1354
+ tree(displayId, params = {}, options = {}) {
1355
+ const query = new URLSearchParams();
1356
+ if (params.maxElements) query.set("maxElements", String(params.maxElements));
1357
+ if (params.actionable) query.set("actionable", "true");
1358
+ const search = query.size ? `?${query}` : "";
1359
+ return this.transport.request(this.path(displayId, `/tree${search}`), { signal: options.signal });
1360
+ }
1361
+ capture(displayId, params = {}, options = {}) {
1362
+ const query = new URLSearchParams();
1363
+ if (params.format) query.set("format", params.format);
1364
+ if (params.quality) query.set("quality", String(params.quality));
1365
+ if (params.maxSize) query.set("maxSize", String(params.maxSize));
1366
+ const search = query.size ? `?${query}` : "";
1367
+ return this.transport.request(this.path(displayId, `/capture${search}`), { signal: options.signal });
1368
+ }
1369
+ input(displayId, events, options = {}) {
1370
+ return this.transport.request(this.path(displayId, "/input"), {
1371
+ method: "POST",
1372
+ headers: { "Content-Type": "application/json" },
1373
+ body: JSON.stringify({ events }),
1374
+ signal: options.signal
1375
+ });
1376
+ }
1377
+ async iceServers(options = {}) {
1378
+ if (this.ice && this.ice.refreshAt > Date.now()) return this.ice.value;
1379
+ const value = await this.transport.request(`/api/spaces/${this.spaceId}/rtc/ice-servers`, { signal: options.signal });
1380
+ this.ice = {
1381
+ value,
1382
+ refreshAt: Date.parse(value.expiresAt) - ICE_REFRESH_MARGIN_MS
1383
+ };
1384
+ return value;
1385
+ }
1386
+ connect(displayId, options) {
1387
+ return connectDisplay(this, displayId, options);
1388
+ }
1389
+ openSession(displayId, body, options = {}) {
1390
+ return this.transport.request(this.path(displayId, "/sessions"), {
1391
+ method: "POST",
1392
+ headers: { "Content-Type": "application/json" },
1393
+ body: JSON.stringify(body),
1394
+ signal: options.signal
1395
+ });
1396
+ }
1397
+ closeSession(displayId, sessionId) {
1398
+ return this.transport.request(this.path(displayId, `/sessions/${sessionId}`), { method: "DELETE" });
1399
+ }
1400
+ };
1401
+ //#endregion
1549
1402
  //#region src/session-patch-reducer.ts
1550
1403
  const blockSubPathPattern = /^\/message\/content\/blocks\/(\d+)\/(.+)$/;
1551
1404
  const blockPathPattern = /^\/message\/content\/blocks\/(\d+)$/;
@@ -2165,6 +2018,9 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2165
2018
  messageOrdinal = null;
2166
2019
  intermediateMessages = [];
2167
2020
  patchState = null;
2021
+ recovery = null;
2022
+ resyncedMessageKey = null;
2023
+ disposed = false;
2168
2024
  constructor(websocketClient, spaceId, sessionId, fetchStreamSnapshot) {
2169
2025
  this.websocketClient = websocketClient;
2170
2026
  this.spaceId = spaceId;
@@ -2175,48 +2031,80 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2175
2031
  if (!this.websocketClient) throw new Error("realtime transport is not configured for this client");
2176
2032
  ensureRealtimeConnected(this.websocketClient);
2177
2033
  const stream = new SessionGenerationStreamClient(this.websocketClient, this.spaceId, this.sessionId, this.fetchStreamSnapshot);
2178
- const shouldRecover = options.recover === true || options.initialSnapshot !== void 0;
2179
- let recovering = shouldRecover;
2180
- let disposed = false;
2181
- let recoveryAborted = false;
2182
- let bufferedEvents = [];
2183
- let bufferedEventsReplayed = false;
2184
2034
  const releaseRoom = this.websocketClient.retainRooms([getRealtimeSpaceRoom(this.spaceId)]);
2185
2035
  const unsubscribe = this.websocketClient.on("event", (event) => {
2186
2036
  if (event.spaceId !== this.spaceId || event.sessionId !== this.sessionId) return;
2187
- if (recovering && isGenerationRealtimeEvent(event)) {
2188
- bufferedEvents.push(event);
2189
- if (bufferedEvents.length > SNAPSHOT_RECOVERY_MAX_BUFFERED_EVENTS) {
2190
- recoveryAborted = true;
2191
- recovering = false;
2192
- stream.replayBufferedEvents(bufferedEvents, handlers);
2193
- bufferedEvents = [];
2194
- bufferedEventsReplayed = true;
2195
- }
2196
- return;
2197
- }
2198
- stream.handleEvent(event, handlers);
2199
- });
2200
- if (shouldRecover) stream.recoverFromSnapshot(handlers, options, () => !recoveryAborted && !disposed).finally(() => {
2201
- if (disposed || bufferedEventsReplayed) return;
2202
- recovering = false;
2203
- stream.replayBufferedEvents(bufferedEvents, handlers);
2204
- bufferedEventsReplayed = true;
2037
+ stream.receive(event, handlers);
2205
2038
  });
2039
+ const { initialSnapshot } = options;
2040
+ if (initialSnapshot !== void 0) stream.recover(handlers, async () => initialSnapshot, options);
2041
+ else if (options.recover === true) stream.recover(handlers, () => stream.loadSnapshot(), options);
2206
2042
  return () => {
2207
- disposed = true;
2043
+ stream.dispose();
2208
2044
  unsubscribe();
2209
2045
  releaseRoom();
2210
2046
  };
2211
2047
  }
2212
- async recoverFromSnapshot(handlers, options, shouldContinue) {
2213
- const snapshot = options.initialSnapshot !== void 0 ? options.initialSnapshot : options.recover === true && this.fetchStreamSnapshot ? (await withTimeout(this.fetchStreamSnapshot(), SNAPSHOT_RECOVERY_TIMEOUT_MS).catch((error) => {
2048
+ receive(event, handlers) {
2049
+ if (this.disposed) return;
2050
+ const recovery = this.recovery;
2051
+ if (recovery && isGenerationRealtimeEvent(event)) {
2052
+ recovery.events.push(event);
2053
+ if (recovery.events.length > SNAPSHOT_RECOVERY_MAX_BUFFERED_EVENTS) this.finishRecovery(recovery, handlers);
2054
+ return;
2055
+ }
2056
+ this.handleEvent(event, handlers);
2057
+ }
2058
+ dispose() {
2059
+ this.disposed = true;
2060
+ this.recovery = null;
2061
+ }
2062
+ async loadSnapshot() {
2063
+ if (!this.fetchStreamSnapshot) return null;
2064
+ try {
2065
+ return (await withTimeout(this.fetchStreamSnapshot(), SNAPSHOT_RECOVERY_TIMEOUT_MS)).snapshot;
2066
+ } catch (error) {
2214
2067
  console.warn("[SessionGenerationStreamClient] Failed to recover stream snapshot:", error);
2215
- return { snapshot: null };
2216
- })).snapshot : null;
2217
- if (!shouldContinue()) return;
2218
- if (!snapshot) return;
2219
- this.seedFromSnapshot(snapshot, handlers, options);
2068
+ return null;
2069
+ }
2070
+ }
2071
+ async recover(handlers, load, options, resync) {
2072
+ const recovery = { events: resync ? [resync.event] : [] };
2073
+ this.recovery = recovery;
2074
+ const snapshot = await load().catch(() => null);
2075
+ if (this.recovery !== recovery) return;
2076
+ const expectedTurnId = resync?.turnId;
2077
+ if (snapshot && (!expectedTurnId || !snapshot.turnId || snapshot.turnId === expectedTurnId)) this.seedFromSnapshot(snapshot, handlers, options);
2078
+ this.finishRecovery(recovery, handlers);
2079
+ }
2080
+ finishRecovery(recovery, handlers) {
2081
+ if (this.recovery !== recovery) return;
2082
+ this.recovery = null;
2083
+ this.replayBufferedEvents(recovery.events, handlers);
2084
+ }
2085
+ shouldResync(turnId, messageId) {
2086
+ if (!this.fetchStreamSnapshot || this.recovery) return false;
2087
+ const key = `${turnId ?? ""}:${messageId ?? ""}`;
2088
+ if (key === this.resyncedMessageKey) return false;
2089
+ this.resyncedMessageKey = key;
2090
+ return true;
2091
+ }
2092
+ liveState() {
2093
+ return this.reducer.get({
2094
+ spaceId: this.spaceId,
2095
+ sessionId: this.sessionId
2096
+ });
2097
+ }
2098
+ isForeignTurn(turnId) {
2099
+ if (!turnId) return false;
2100
+ const live = this.liveState();
2101
+ return (live.status === "streaming" || live.status === "pending") && live.turnId != null && live.turnId !== turnId;
2102
+ }
2103
+ isLiveMessage(message) {
2104
+ if (this.messageId == null && this.messageOrdinal == null) return true;
2105
+ const meta = message.meta ?? {};
2106
+ if (typeof meta.messageOrdinal === "number" && this.messageOrdinal != null) return meta.messageOrdinal === this.messageOrdinal;
2107
+ return typeof meta.streamMessageId === "string" && meta.streamMessageId === this.messageId;
2220
2108
  }
2221
2109
  seedFromSnapshot(snapshot, handlers, options) {
2222
2110
  if (snapshot.spaceId !== this.spaceId || snapshot.sessionId !== this.sessionId) return false;
@@ -2255,7 +2143,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2255
2143
  const sameMessage = sameTurn && Boolean(this.messageId && (identity.messageId && identity.messageId === this.messageId || identity.messageId == null && identity.messageOrdinal != null && identity.messageOrdinal === this.messageOrdinal));
2256
2144
  const olderMessageInSameTurn = sameTurn && identity.messageOrdinal != null && this.messageOrdinal != null && identity.messageOrdinal < this.messageOrdinal;
2257
2145
  if ((sameMessage || olderMessageInSameTurn) && this.patchState && seq != null && seq <= this.patchState.patchSeq) continue;
2258
- this.handleEvent(event, handlers);
2146
+ this.receive(event, handlers);
2259
2147
  }
2260
2148
  }
2261
2149
  emit(handlers, event) {
@@ -2377,36 +2265,41 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2377
2265
  anchorUserMessageId
2378
2266
  };
2379
2267
  const result = this.reducer.applyPatch(input);
2268
+ if (!result.applied && result.reason === "version_mismatch" && this.shouldResync(turnId, messageId)) {
2269
+ this.recover(handlers, () => this.loadSnapshot(), void 0, {
2270
+ turnId,
2271
+ event
2272
+ });
2273
+ return;
2274
+ }
2380
2275
  this.handleAppliedState(handlers, "patch", result, event, messageId, messageOrdinal);
2381
2276
  }
2382
2277
  handlePersisted(event, handlers) {
2383
2278
  const message = event.payload.message;
2384
2279
  if (!isRecord(message)) return;
2385
2280
  const commit = parseAssistantMessageCommit(message);
2386
- if (commit.kind === "intermediate") {
2387
- const intermediate = messageRecordToIntermediate(commit.message);
2388
- if (intermediate) this.addIntermediateMessage(intermediate);
2389
- this.reducer.reset({
2390
- spaceId: this.spaceId,
2391
- sessionId: this.sessionId
2392
- });
2393
- this.resetCurrentMessage();
2394
- }
2395
- if (commit.kind === "final") {
2396
- this.reducer.complete({
2397
- spaceId: this.spaceId,
2398
- sessionId: this.sessionId,
2399
- turnId: getTurnIdFromMessage(commit.message)
2400
- });
2401
- this.resetCurrentMessage();
2402
- }
2403
- if (commit.kind === "error") {
2404
- this.reducer.fail({
2405
- spaceId: this.spaceId,
2406
- sessionId: this.sessionId,
2407
- turnId: getTurnIdFromMessage(commit.message)
2408
- });
2409
- this.resetCurrentMessage();
2281
+ const turnId = getTurnIdFromMessage(commit.message);
2282
+ const key = {
2283
+ spaceId: this.spaceId,
2284
+ sessionId: this.sessionId,
2285
+ turnId
2286
+ };
2287
+ if (commit.kind !== "ignored" && !this.isForeignTurn(turnId)) {
2288
+ if (commit.kind === "intermediate") {
2289
+ const intermediate = messageRecordToIntermediate(commit.message);
2290
+ if (intermediate) this.addIntermediateMessage(intermediate);
2291
+ if (this.isLiveMessage(commit.message)) {
2292
+ this.reducer.start({
2293
+ ...key,
2294
+ turnId: turnId ?? this.liveState().turnId
2295
+ });
2296
+ this.resetCurrentMessage();
2297
+ }
2298
+ } else {
2299
+ if (commit.kind === "final") this.reducer.complete(key);
2300
+ else this.reducer.fail(key);
2301
+ this.resetCurrentMessage();
2302
+ }
2410
2303
  }
2411
2304
  this.emit(handlers, {
2412
2305
  type: "commit",
@@ -2418,17 +2311,16 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2418
2311
  const turn = event.payload.turn;
2419
2312
  if (!isRecord(turn)) return;
2420
2313
  const typedTurn = turn;
2421
- if (typedTurn.status === "interrupted") this.reducer.interrupt({
2422
- spaceId: this.spaceId,
2423
- sessionId: this.sessionId,
2424
- turnId: typedTurn.id
2425
- });
2426
- else this.reducer.complete({
2427
- spaceId: this.spaceId,
2428
- sessionId: this.sessionId,
2429
- turnId: typedTurn.id
2430
- });
2431
- this.resetCurrentMessage();
2314
+ if (!this.isForeignTurn(typedTurn.id)) {
2315
+ const key = {
2316
+ spaceId: this.spaceId,
2317
+ sessionId: this.sessionId,
2318
+ turnId: typedTurn.id
2319
+ };
2320
+ if (typedTurn.status === "interrupted") this.reducer.interrupt(key);
2321
+ else this.reducer.complete(key);
2322
+ this.resetCurrentMessage();
2323
+ }
2432
2324
  this.emit(handlers, {
2433
2325
  type: "finalized",
2434
2326
  turn: typedTurn,
@@ -2635,17 +2527,22 @@ var SpacesApi = class {
2635
2527
  constructor(transport) {
2636
2528
  this.transport = transport;
2637
2529
  }
2638
- list(customFetch) {
2639
- return this.transport.request("/api/spaces", {
2640
- method: "GET",
2641
- fetch: customFetch
2642
- });
2530
+ list(optionsOrFetch = {}, customFetch) {
2531
+ const options = typeof optionsOrFetch === "function" ? {} : optionsOrFetch;
2532
+ const fetch = typeof optionsOrFetch === "function" ? optionsOrFetch : customFetch;
2533
+ const params = new URLSearchParams();
2534
+ params.set("filter", options.filter ?? "recent");
2535
+ if (options.limit) params.set("limit", String(options.limit));
2536
+ if (options.cursor) params.set("cursor", options.cursor);
2537
+ if (options.query) params.set("q", options.query);
2538
+ if (options.name) params.set("name", options.name);
2539
+ for (const recent of options.recentSpaces ?? []) {
2540
+ params.append("recentSpaceId", recent.id);
2541
+ params.append("recentSpaceAt", new Date(recent.timestamp).toISOString());
2542
+ }
2543
+ return this.transport.request(`/api/spaces?${params}`, { fetch });
2643
2544
  }
2644
- /**
2645
- * Resolve the user's default space (owned/member home, else most recent).
2646
- * When the account has no accessible space, the API creates a blank Home
2647
- * space (`slug=home`) and returns it.
2648
- */
2545
+ /** Landing Space within the user's own Spaces; use `ensureHome()` for a write target. */
2649
2546
  getDefault(customFetch) {
2650
2547
  return this.transport.request("/api/spaces/default", {
2651
2548
  method: "GET",
@@ -2658,6 +2555,16 @@ var SpacesApi = class {
2658
2555
  getBySlug(username, slug, customFetch) {
2659
2556
  return this.transport.request(`/api/spaces/by-slug/${encodeURIComponent(username)}/${encodeURIComponent(slug)}`, { fetch: customFetch });
2660
2557
  }
2558
+ getOwnedBySlug(slug, customFetch) {
2559
+ return this.transport.request(`/api/me/spaces/by-slug/${encodeURIComponent(slug)}`, { fetch: customFetch });
2560
+ }
2561
+ /** The account's own Home Space, created on first use. */
2562
+ ensureHome(customFetch) {
2563
+ return this.transport.request("/api/me/spaces/home", {
2564
+ method: "POST",
2565
+ fetch: customFetch
2566
+ });
2567
+ }
2661
2568
  create(input, headers) {
2662
2569
  return this.transport.request("/api/spaces", {
2663
2570
  method: "POST",
@@ -2758,7 +2665,7 @@ var SpaceFilesApi = class {
2758
2665
  }
2759
2666
  read(path, customFetch, signal) {
2760
2667
  const params = new URLSearchParams({ path });
2761
- return this.transport.request(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, {
2668
+ return this.transport.requestRevalidated(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, {
2762
2669
  fetch: customFetch,
2763
2670
  signal
2764
2671
  });
@@ -2821,15 +2728,33 @@ var SpaceFilesApi = class {
2821
2728
  fetch: customFetch
2822
2729
  });
2823
2730
  }
2824
- async download(path, customFetch) {
2731
+ async open(path, options = {}) {
2825
2732
  const params = new URLSearchParams({ path });
2826
- const raw = await this.transport.raw(`/api/spaces/${this.spaceId}/fs/download?${params.toString()}`, { fetch: customFetch });
2827
- if (raw.response.status === 202) throw new HttpError("File is being prepared. Please retry shortly.", 202, await raw.json().catch(() => null));
2828
- const blob = await raw.blob();
2733
+ const deadlineAt = Date.now() + Math.max(0, options.timeoutMs ?? 6e4);
2734
+ while (true) {
2735
+ const raw = await this.transport.raw(`/api/spaces/${this.spaceId}/fs/download?${params.toString()}`, {
2736
+ fetch: options.fetch,
2737
+ signal: options.signal
2738
+ });
2739
+ const { response } = raw;
2740
+ if (response.status !== 202) return {
2741
+ response,
2742
+ filename: getFilenameFromContentDisposition(response.headers.get("content-disposition")) ?? path.split("/").pop() ?? "download",
2743
+ mimeType: response.headers.get("content-type") ?? "application/octet-stream"
2744
+ };
2745
+ const body = await raw.json().catch(() => null);
2746
+ const remainingMs = deadlineAt - Date.now();
2747
+ if (remainingMs <= 0) throw new HttpError("File is being prepared. Please retry shortly.", 202, body);
2748
+ const retryAfterMs = typeof body?.retryAfterMs === "number" ? body.retryAfterMs : 2e3;
2749
+ await waitForSpaceFileRetry(Math.min(Math.max(250, Math.min(retryAfterMs, 2e3)), remainingMs), options.signal);
2750
+ }
2751
+ }
2752
+ async download(path, customFetch) {
2753
+ const { response, filename, mimeType } = await this.open(path, { fetch: customFetch });
2829
2754
  return {
2830
- blob,
2831
- filename: getFilenameFromContentDisposition(raw.response.headers.get("content-disposition")) ?? path.split("/").pop() ?? "download",
2832
- mimeType: raw.response.headers.get("content-type") ?? blob.type ?? "application/octet-stream"
2755
+ blob: await response.blob(),
2756
+ filename,
2757
+ mimeType
2833
2758
  };
2834
2759
  }
2835
2760
  write(input) {
@@ -2839,6 +2764,31 @@ var SpaceFilesApi = class {
2839
2764
  body: JSON.stringify(input)
2840
2765
  });
2841
2766
  }
2767
+ copy(input) {
2768
+ return this.transport.request(`/api/spaces/${this.spaceId}/fs/copy`, {
2769
+ method: "POST",
2770
+ headers: { "Content-Type": "application/json" },
2771
+ body: JSON.stringify(input)
2772
+ });
2773
+ }
2774
+ getCopy(copyId, options = {}) {
2775
+ const params = new URLSearchParams();
2776
+ if (options.waitMs) params.set("waitMs", String(options.waitMs));
2777
+ const query = params.toString();
2778
+ return this.transport.request(`/api/spaces/${this.spaceId}/fs/copies/${encodeURIComponent(copyId)}${query ? `?${query}` : ""}`, { signal: options.signal });
2779
+ }
2780
+ async waitForCopy(copy, options = {}) {
2781
+ let current = copy;
2782
+ while (current.status !== "completed" || !current.result) {
2783
+ if (current.progress) options.onProgress?.(current.progress);
2784
+ options.signal?.throwIfAborted();
2785
+ current = await this.getCopy(current.copyId, {
2786
+ waitMs: options.pollMs ?? 5e3,
2787
+ signal: options.signal
2788
+ });
2789
+ }
2790
+ return current.result;
2791
+ }
2842
2792
  createDir(path, mutationId) {
2843
2793
  return this.transport.request(`/api/spaces/${this.spaceId}/fs/dir`, {
2844
2794
  method: "POST",
@@ -3115,6 +3065,11 @@ var SessionClient = class {
3115
3065
  get(customFetch) {
3116
3066
  return this.transport.request(`/api/sessions/${this.id}`, { fetch: customFetch });
3117
3067
  }
3068
+ async stats(options = {}) {
3069
+ const stats = readSessionStats({ stats: (await this.transport.request(`/api/sessions/${this.id}/stats`, options)).stats });
3070
+ if (!stats) throw new Error("Invalid session statistics");
3071
+ return { stats };
3072
+ }
3118
3073
  /** Space files changed by this session's agent write/edit tool calls. */
3119
3074
  files(options = {}) {
3120
3075
  const query = options.limit ? `?limit=${options.limit}` : "";
@@ -3699,34 +3654,111 @@ var BoardRealtimeClient = class {
3699
3654
  var BoardClient = class {
3700
3655
  spaceId;
3701
3656
  id;
3657
+ transport;
3702
3658
  websocketClient;
3703
3659
  realtime;
3704
- boards;
3705
3660
  constructor(spaceId, id, transport, websocketClient) {
3706
3661
  this.spaceId = spaceId;
3707
3662
  this.id = id;
3663
+ this.transport = transport;
3708
3664
  this.websocketClient = websocketClient;
3709
- this.boards = new SpaceBoardsApi(transport, spaceId, websocketClient);
3710
3665
  this.realtime = new BoardRealtimeClient(websocketClient, spaceId, id);
3711
3666
  }
3712
- capabilities(customFetch) {
3713
- return this.boards.capabilities(this.id, customFetch);
3667
+ get path() {
3668
+ return `/api/spaces/${this.spaceId}/boards/${this.id}`;
3714
3669
  }
3715
- summary(customFetch) {
3716
- return this.boards.summary(this.id, customFetch);
3670
+ /** Read the Board, or part of it. Every value is complete, defaults included. */
3671
+ get(input = {}, customFetch) {
3672
+ const params = new URLSearchParams();
3673
+ if (input.only?.length) params.set("only", input.only.join(","));
3674
+ if (input.rect) params.set("rect", [
3675
+ input.rect.x,
3676
+ input.rect.y,
3677
+ input.rect.width,
3678
+ input.rect.height
3679
+ ].join(","));
3680
+ if (input.within) params.set("within", input.within);
3681
+ if (input.items?.length) params.set("items", input.items.join(","));
3682
+ if (input.animations?.length) params.set("animations", input.animations.join(","));
3683
+ if (input.limit !== void 0) params.set("limit", String(input.limit));
3684
+ if (input.cursor) params.set("cursor", input.cursor);
3685
+ const query = params.toString();
3686
+ return this.transport.request(`${this.path}${query ? `?${query}` : ""}`, { fetch: customFetch });
3717
3687
  }
3718
- authoring(input = {}, customFetch) {
3719
- return this.boards.authoring(this.id, input, customFetch);
3688
+ /** Merge a patch into the Board: fields merge, `null` deletes. */
3689
+ apply(patch, options = {}) {
3690
+ return this.transport.request(`${this.path}/apply`, {
3691
+ method: "POST",
3692
+ headers: { "Content-Type": "application/json" },
3693
+ body: JSON.stringify({
3694
+ ...options,
3695
+ patch,
3696
+ mutationId: options.mutationId ?? randomBoardId()
3697
+ })
3698
+ });
3720
3699
  }
3721
- /** Read-only transaction log, newest first; the first page carries the current rows. */
3722
- transactions(input = {}, customFetch) {
3723
- return this.boards.transactions(this.id, input, customFetch);
3700
+ /** Transactions newest first, each with the before/after state of what it changed. */
3701
+ history(input = {}, customFetch) {
3702
+ const params = new URLSearchParams();
3703
+ if (input.before !== void 0) params.set("before", String(input.before));
3704
+ if (input.limit !== void 0) params.set("limit", String(input.limit));
3705
+ const query = params.toString();
3706
+ return this.transport.request(`${this.path}/history${query ? `?${query}` : ""}`, { fetch: customFetch });
3724
3707
  }
3725
- mutateSemantic(input) {
3726
- return this.boards.mutateSemantic(this.id, {
3727
- ...input,
3728
- mutationId: input.mutationId ?? randomBoardId(),
3729
- dryRun: input.dryRun ?? false
3708
+ /** Return the Board to an earlier version as a new write. */
3709
+ restore(version) {
3710
+ return this.transport.request(`${this.path}/restore`, {
3711
+ method: "POST",
3712
+ headers: { "Content-Type": "application/json" },
3713
+ body: JSON.stringify({ version })
3714
+ });
3715
+ }
3716
+ playback(command) {
3717
+ return this.transport.request(`${this.path}/playback`, {
3718
+ method: "POST",
3719
+ headers: { "Content-Type": "application/json" },
3720
+ body: JSON.stringify(command)
3721
+ }).then((response) => response.playback);
3722
+ }
3723
+ /** Shared playback: every viewer follows the same clock. */
3724
+ play(animationId, options = {}) {
3725
+ return this.playback({
3726
+ commandId: randomBoardId(),
3727
+ type: "play",
3728
+ animationId,
3729
+ ...options
3730
+ });
3731
+ }
3732
+ pause() {
3733
+ return this.playback({
3734
+ commandId: randomBoardId(),
3735
+ type: "pause"
3736
+ });
3737
+ }
3738
+ resume() {
3739
+ return this.playback({
3740
+ commandId: randomBoardId(),
3741
+ type: "resume"
3742
+ });
3743
+ }
3744
+ seek(position) {
3745
+ return this.playback({
3746
+ commandId: randomBoardId(),
3747
+ type: "seek",
3748
+ position
3749
+ });
3750
+ }
3751
+ /** Continue past the marker a presentation is holding at. */
3752
+ next() {
3753
+ return this.playback({
3754
+ commandId: randomBoardId(),
3755
+ type: "next"
3756
+ });
3757
+ }
3758
+ stop() {
3759
+ return this.playback({
3760
+ commandId: randomBoardId(),
3761
+ type: "stop"
3730
3762
  });
3731
3763
  }
3732
3764
  updateAwareness(seq, update) {
@@ -3738,18 +3770,6 @@ var BoardClient = class {
3738
3770
  update
3739
3771
  });
3740
3772
  }
3741
- play(command) {
3742
- return this.boards.play(this.id, command);
3743
- }
3744
- pause(command) {
3745
- return this.boards.pause(this.id, command);
3746
- }
3747
- seek(command) {
3748
- return this.boards.seek(this.id, command);
3749
- }
3750
- stop(command) {
3751
- return this.boards.stop(this.id, command);
3752
- }
3753
3773
  subscribe(handlers) {
3754
3774
  return this.realtime.subscribe(handlers);
3755
3775
  }
@@ -3771,66 +3791,14 @@ var SpaceBoardsApi = class {
3771
3791
  byId(boardId) {
3772
3792
  return new BoardClient(this.spaceId, boardId, this.transport, this.websocketClient);
3773
3793
  }
3774
- async create(input) {
3775
- for (const item of input.items ?? []) BoardAuthoringItemSchema.parse(item);
3794
+ /** Create a `.board` file and its Board, optionally with an initial document. */
3795
+ create(input) {
3776
3796
  return this.transport.request(`/api/spaces/${this.spaceId}/boards`, {
3777
3797
  method: "POST",
3778
3798
  headers: { "Content-Type": "application/json" },
3779
3799
  body: JSON.stringify(input)
3780
3800
  });
3781
3801
  }
3782
- authoring(boardId, input = {}, customFetch) {
3783
- if (input.include?.length === 0) throw new Error("Board authoring include must not be empty.");
3784
- const params = new URLSearchParams();
3785
- for (const section of input.include ?? []) params.append("include", section);
3786
- if (input.itemIds?.length) params.set("itemIds", input.itemIds.join(","));
3787
- if (input.connectionIds?.length) params.set("connectionIds", input.connectionIds.join(","));
3788
- if (input.effectIds?.length) params.set("effectIds", input.effectIds.join(","));
3789
- if (input.compositionIds?.length) params.set("compositionIds", input.compositionIds.join(","));
3790
- if (input.viewport) params.set("viewport", JSON.stringify(input.viewport));
3791
- const query = params.toString();
3792
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/authoring${query ? `?${query}` : ""}`, { fetch: customFetch });
3793
- }
3794
- mutateSemantic(boardId, mutation) {
3795
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/mutations`, {
3796
- method: "POST",
3797
- headers: { "Content-Type": "application/json" },
3798
- body: JSON.stringify(mutation)
3799
- });
3800
- }
3801
- summary(boardId, customFetch) {
3802
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/summary`, { fetch: customFetch });
3803
- }
3804
- capabilities(boardId, customFetch) {
3805
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/capabilities`, { fetch: customFetch });
3806
- }
3807
- transactions(boardId, input = {}, customFetch) {
3808
- const params = new URLSearchParams();
3809
- if (input.before !== void 0) params.set("before", String(input.before));
3810
- if (input.limit !== void 0) params.set("limit", String(input.limit));
3811
- if (input.snapshot !== void 0) params.set("snapshot", String(input.snapshot));
3812
- const query = params.toString();
3813
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/transactions${query ? `?${query}` : ""}`, { fetch: customFetch });
3814
- }
3815
- playback(boardId, command) {
3816
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/playback`, {
3817
- method: "POST",
3818
- headers: { "Content-Type": "application/json" },
3819
- body: JSON.stringify(command)
3820
- });
3821
- }
3822
- play(boardId, command) {
3823
- return this.playback(boardId, command);
3824
- }
3825
- pause(boardId, command) {
3826
- return this.playback(boardId, command);
3827
- }
3828
- seek(boardId, command) {
3829
- return this.playback(boardId, command);
3830
- }
3831
- stop(boardId, command) {
3832
- return this.playback(boardId, command);
3833
- }
3834
3802
  };
3835
3803
  var SpaceCheckpointFilesApi = class {
3836
3804
  transport;
@@ -3970,6 +3938,7 @@ var SpaceClient = class {
3970
3938
  webhooks;
3971
3939
  env;
3972
3940
  sandbox;
3941
+ displays;
3973
3942
  invitations;
3974
3943
  labels;
3975
3944
  boards;
@@ -3993,6 +3962,7 @@ var SpaceClient = class {
3993
3962
  this.webhooks = new SpaceWebhooksApi(transport, id);
3994
3963
  this.env = new SpaceEnvApi(transport, id);
3995
3964
  this.sandbox = new SpaceSandboxApi(transport, id);
3965
+ this.displays = new SpaceDisplaysApi(transport, id);
3996
3966
  this.invitations = new SpaceInvitationsApi(transport, id);
3997
3967
  this.labels = new SpaceLabelsApi(transport, id);
3998
3968
  this.boards = new SpaceBoardsApi(transport, id, websocketClient);
@@ -4398,6 +4368,7 @@ var UserApi = class {
4398
4368
  if (options?.limit !== void 0) params.set("limit", String(options.limit));
4399
4369
  if (options?.cursor) params.set("cursor", options.cursor);
4400
4370
  if (options?.source?.length) params.set("source", options.source.join(","));
4371
+ if (options?.includeForks) params.set("includeForks", "1");
4401
4372
  const query = params.toString();
4402
4373
  return this.transport.request(`/api/me/sessions${query ? `?${query}` : ""}`, { fetch });
4403
4374
  }
@@ -4434,6 +4405,53 @@ var UserLabelsApi = class {
4434
4405
  constructor(transport) {
4435
4406
  this.transport = transport;
4436
4407
  }
4408
+ list() {
4409
+ return this.transport.request("/api/me/labels");
4410
+ }
4411
+ create(labelRef) {
4412
+ return this.transport.request("/api/me/labels", {
4413
+ method: "POST",
4414
+ headers: { "Content-Type": "application/json" },
4415
+ body: JSON.stringify({ labelRef })
4416
+ });
4417
+ }
4418
+ resolve(labelRefs) {
4419
+ return this.transport.request("/api/me/labels/resolve", {
4420
+ method: "POST",
4421
+ headers: { "Content-Type": "application/json" },
4422
+ body: JSON.stringify({ labelRefs })
4423
+ });
4424
+ }
4425
+ update(labelRef, input) {
4426
+ return this.transport.request("/api/me/labels/by-ref", {
4427
+ method: "PATCH",
4428
+ headers: { "Content-Type": "application/json" },
4429
+ body: JSON.stringify({
4430
+ labelRef,
4431
+ ...input
4432
+ })
4433
+ });
4434
+ }
4435
+ delete(labelRef) {
4436
+ return this.transport.request(`/api/me/labels/by-ref?${new URLSearchParams({ ref: labelRef })}`, { method: "DELETE" });
4437
+ }
4438
+ reorder(labelRefs) {
4439
+ return this.transport.request("/api/me/labels/reorder", {
4440
+ method: "POST",
4441
+ headers: { "Content-Type": "application/json" },
4442
+ body: JSON.stringify({ labelRefs })
4443
+ });
4444
+ }
4445
+ patchResources(resourceRefs, input) {
4446
+ return this.transport.request("/api/me/resources/space/labels/batch", {
4447
+ method: "PATCH",
4448
+ headers: { "Content-Type": "application/json" },
4449
+ body: JSON.stringify({
4450
+ resourceRefs,
4451
+ ...input
4452
+ })
4453
+ });
4454
+ }
4437
4455
  getResourceLabels(resourceType, resourceRef) {
4438
4456
  const params = new URLSearchParams({ resourceRef });
4439
4457
  return this.transport.request(`/api/me/resources/${resourceType}/labels?${params.toString()}`);
@@ -4789,4 +4807,4 @@ var CohubHttpClient = class {
4789
4807
  };
4790
4808
  const createHttpClient = (options) => new CohubHttpClient(options);
4791
4809
  //#endregion
4792
- export { BoardItemPatchSchema as $, SkillsApi as A, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES as B, SessionPatchReducer as C, ReferencesApi as D, SessionAccessApi as E, ChannelsApi as F, defaultDesktopCommandTimeoutMs as G, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS as H, DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS as I, parseDesktopCommand as J, isDesktopCallMethod as K, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS as L, ModelsApi as M, GenerationsApi as N, SearchApi as O, CronJobsApi as P, BoardAuthoringItemSchema as Q, DESKTOP_COMMAND_MAX_TIMEOUT_MS as R, parseAssistantMessageCommit as S, ensureRealtimeConnected as T, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS as U, DESKTOP_COMMAND_PENDING_TTL_SECONDS as V, DESKTOP_COMMAND_VERSION as W, resolveOpenSurface as X, parseDesktopCommandId as Y, NAVIGATION_ERROR_MESSAGE_MAX_LENGTH as Z, PublicInviteApi as _, DEFAULT_BOARD_RENDER_LIMITS as _t, DesktopCommandsApi as a, BOARD_ANIMATION_CHANNELS as at, SessionGenerationStreamClient as b, ReferralsApi as c, BoardCompositionSchema as ct, TasksApi as d, parseBoardCompositionInput as dt, BoardSemanticCommandSchema as et, BoardClient as f, BoardConnectionSchema as ft, SpacesApi as g, BOARD_BUILTIN_EFFECT_KINDS as gt, SpaceWebhooksApi as h, BOARD_BUILTIN_CLIP_KINDS as ht, WorkCommerceApi as i, parseBoardEffectInput as it, PromptsApi as j, PublicAssetsApi as k, UsersApi as l, BoardProceduralClipSchema as lt, SpacePublicFilesApi as m, BOARD_BUILTIN_CAPABILITIES as mt, createHttpClient as n, BoardEffectInputSchema as nt, UiCommandsApi as o, BOARD_ANIMATION_CHANNEL_CAPABILITIES as ot, SpaceClient as p, BOARD_ARROW_STROKE_SIZE as pt, isTerminalDesktopCommandStatus as q, AppCommerceApi as r, BoardEffectSchema as rt, AppsApi as s, BoardCompositionInputSchema as st, CohubHttpClient as t, BoardSemanticMutationSchema as tt, UserApi as u, BoardTrackSchema as ut, buildSpaceInvitePath as v, createSessionPatchReducer as w, createSessionGenerationStreamClient as x, buildSpacePath as y, DESKTOP_COMMAND_OPEN_TIMEOUT_MS as z };
4810
+ export { resolveOpenSurface as $, ReferencesApi as A, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS as B, SessionPatchReducer as C, connectDisplay as D, DisplayConnection as E, ModelsApi as F, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS as G, DESKTOP_COMMAND_OPEN_TIMEOUT_MS as H, GenerationsApi as I, defaultDesktopCommandTimeoutMs as J, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS as K, CronJobsApi as L, PublicAssetsApi as M, SkillsApi as N, ensureRealtimeConnected as O, PromptsApi as P, parseDesktopCommandId as Q, ChannelsApi as R, parseAssistantMessageCommit as S, SpaceDisplaysApi as T, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES as U, DESKTOP_COMMAND_MAX_TIMEOUT_MS as V, DESKTOP_COMMAND_PENDING_TTL_SECONDS as W, isTerminalDesktopCommandStatus as X, isDesktopCallMethod as Y, parseDesktopCommand as Z, PublicInviteApi as _, DesktopCommandsApi as a, normalizeDisplayPoint as at, SessionGenerationStreamClient as b, ReferralsApi as c, TasksApi as d, NAVIGATION_ERROR_MESSAGE_MAX_LENGTH as et, BoardClient as f, SpacesApi as g, SpaceWebhooksApi as h, WorkCommerceApi as i, DISPLAY_SYSTEM_ACTIONS as it, SearchApi as j, SessionAccessApi as k, UsersApi as l, SpacePublicFilesApi as m, createHttpClient as n, DISPLAY_INPUT_MAX_SCHEDULE_MS as nt, UiCommandsApi as o, splitDisplayText as ot, SpaceClient as p, DESKTOP_COMMAND_VERSION as q, AppCommerceApi as r, DISPLAY_INPUT_MAX_TEXT as rt, AppsApi as s, CohubHttpClient as t, DISPLAY_ELEMENT_REF_PATTERN as tt, UserApi as u, buildSpaceInvitePath as v, createSessionPatchReducer as w, createSessionGenerationStreamClient as x, buildSpacePath as y, DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS as z };