@neta-art/cohub 8.23.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 +7 -27
  30. package/dist/board/headless/index.js +20 -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 +2296 -3864
  91. package/dist/chunks/host-bridge.d.ts +253 -0
  92. package/dist/chunks/http.d.ts +238 -58
  93. package/dist/chunks/http.js +1061 -1034
  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 +76 -193
  101. package/dist/index.js +2037 -1191
  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";
@@ -912,6 +415,9 @@ const DESKTOP_COMMAND_VERSION = 1;
912
415
  const DESKTOP_COMMAND_PAYLOAD_MAX_BYTES = 32768;
913
416
  const DESKTOP_COMMAND_MAX_BYTES = 40960;
914
417
  const DESKTOP_COMMAND_LAUNCH_MAX_LENGTH = NAVIGATION_LAUNCH_MAX_LENGTH;
418
+ /** A command nobody accepts within this window settles as `no_active_client`. */
419
+ const DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS = 1e4;
420
+ const DESKTOP_COMMAND_OPEN_TIMEOUT_MS = 3e4;
915
421
  const DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS = 6e5;
916
422
  const DESKTOP_COMMAND_MAX_TIMEOUT_MS = 432e5;
917
423
  const DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS = 600;
@@ -935,6 +441,7 @@ const isTerminalDesktopCommandStatus = (status) => DESKTOP_COMMAND_TERMINAL_STAT
935
441
  function resolveOpenSurface(requested, declared) {
936
442
  return (requested ?? declared) === "overlay" ? "overlay" : void 0;
937
443
  }
444
+ const defaultDesktopCommandTimeoutMs = (command) => command.call ? DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS : DESKTOP_COMMAND_OPEN_TIMEOUT_MS;
938
445
  const METHOD_RE = new RegExp(`^[A-Za-z][A-Za-z0-9_.:-]{0,63}$`);
939
446
  const isDesktopCallMethod = (value) => typeof value === "string" && METHOD_RE.test(value);
940
447
  const DESKTOP_COMMAND_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
@@ -1468,14 +975,16 @@ var SearchApi = class {
1468
975
  for (const type of input.types ?? []) params.append("type", type);
1469
976
  if (input.spaceId) params.set("spaceId", input.spaceId);
1470
977
  if (input.labelRef) params.set("labelRef", input.labelRef);
1471
- if (input.groupTurns === false) params.set("groupTurns", "false");
1472
978
  return this.transport.request(`/api/search?${params.toString()}`, { fetch: customFetch });
1473
979
  }
1474
980
  /** Default (empty-query) command palette data: viewer-relative space signals. */
1475
981
  overview(input, customFetch) {
1476
982
  const params = new URLSearchParams();
1477
983
  if (input?.spaceLimit !== void 0) params.set("spaceLimit", String(input.spaceLimit));
1478
- 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
+ }
1479
988
  for (const spaceId of input?.recentSpaceIds ?? []) params.append("recentSpaceId", spaceId);
1480
989
  const query = params.toString();
1481
990
  return this.transport.request(`/api/palette/overview${query ? `?${query}` : ""}`, { fetch: customFetch });
@@ -1542,6 +1051,354 @@ function ensureRealtimeConnected(websocketClient) {
1542
1051
  });
1543
1052
  }
1544
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
1545
1402
  //#region src/session-patch-reducer.ts
1546
1403
  const blockSubPathPattern = /^\/message\/content\/blocks\/(\d+)\/(.+)$/;
1547
1404
  const blockPathPattern = /^\/message\/content\/blocks\/(\d+)$/;
@@ -2161,6 +2018,9 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2161
2018
  messageOrdinal = null;
2162
2019
  intermediateMessages = [];
2163
2020
  patchState = null;
2021
+ recovery = null;
2022
+ resyncedMessageKey = null;
2023
+ disposed = false;
2164
2024
  constructor(websocketClient, spaceId, sessionId, fetchStreamSnapshot) {
2165
2025
  this.websocketClient = websocketClient;
2166
2026
  this.spaceId = spaceId;
@@ -2171,48 +2031,80 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2171
2031
  if (!this.websocketClient) throw new Error("realtime transport is not configured for this client");
2172
2032
  ensureRealtimeConnected(this.websocketClient);
2173
2033
  const stream = new SessionGenerationStreamClient(this.websocketClient, this.spaceId, this.sessionId, this.fetchStreamSnapshot);
2174
- const shouldRecover = options.recover === true || options.initialSnapshot !== void 0;
2175
- let recovering = shouldRecover;
2176
- let disposed = false;
2177
- let recoveryAborted = false;
2178
- let bufferedEvents = [];
2179
- let bufferedEventsReplayed = false;
2180
2034
  const releaseRoom = this.websocketClient.retainRooms([getRealtimeSpaceRoom(this.spaceId)]);
2181
2035
  const unsubscribe = this.websocketClient.on("event", (event) => {
2182
2036
  if (event.spaceId !== this.spaceId || event.sessionId !== this.sessionId) return;
2183
- if (recovering && isGenerationRealtimeEvent(event)) {
2184
- bufferedEvents.push(event);
2185
- if (bufferedEvents.length > SNAPSHOT_RECOVERY_MAX_BUFFERED_EVENTS) {
2186
- recoveryAborted = true;
2187
- recovering = false;
2188
- stream.replayBufferedEvents(bufferedEvents, handlers);
2189
- bufferedEvents = [];
2190
- bufferedEventsReplayed = true;
2191
- }
2192
- return;
2193
- }
2194
- stream.handleEvent(event, handlers);
2195
- });
2196
- if (shouldRecover) stream.recoverFromSnapshot(handlers, options, () => !recoveryAborted && !disposed).finally(() => {
2197
- if (disposed || bufferedEventsReplayed) return;
2198
- recovering = false;
2199
- stream.replayBufferedEvents(bufferedEvents, handlers);
2200
- bufferedEventsReplayed = true;
2037
+ stream.receive(event, handlers);
2201
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);
2202
2042
  return () => {
2203
- disposed = true;
2043
+ stream.dispose();
2204
2044
  unsubscribe();
2205
2045
  releaseRoom();
2206
2046
  };
2207
2047
  }
2208
- async recoverFromSnapshot(handlers, options, shouldContinue) {
2209
- 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) {
2210
2067
  console.warn("[SessionGenerationStreamClient] Failed to recover stream snapshot:", error);
2211
- return { snapshot: null };
2212
- })).snapshot : null;
2213
- if (!shouldContinue()) return;
2214
- if (!snapshot) return;
2215
- 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;
2216
2108
  }
2217
2109
  seedFromSnapshot(snapshot, handlers, options) {
2218
2110
  if (snapshot.spaceId !== this.spaceId || snapshot.sessionId !== this.sessionId) return false;
@@ -2251,7 +2143,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2251
2143
  const sameMessage = sameTurn && Boolean(this.messageId && (identity.messageId && identity.messageId === this.messageId || identity.messageId == null && identity.messageOrdinal != null && identity.messageOrdinal === this.messageOrdinal));
2252
2144
  const olderMessageInSameTurn = sameTurn && identity.messageOrdinal != null && this.messageOrdinal != null && identity.messageOrdinal < this.messageOrdinal;
2253
2145
  if ((sameMessage || olderMessageInSameTurn) && this.patchState && seq != null && seq <= this.patchState.patchSeq) continue;
2254
- this.handleEvent(event, handlers);
2146
+ this.receive(event, handlers);
2255
2147
  }
2256
2148
  }
2257
2149
  emit(handlers, event) {
@@ -2373,36 +2265,41 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2373
2265
  anchorUserMessageId
2374
2266
  };
2375
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
+ }
2376
2275
  this.handleAppliedState(handlers, "patch", result, event, messageId, messageOrdinal);
2377
2276
  }
2378
2277
  handlePersisted(event, handlers) {
2379
2278
  const message = event.payload.message;
2380
2279
  if (!isRecord(message)) return;
2381
2280
  const commit = parseAssistantMessageCommit(message);
2382
- if (commit.kind === "intermediate") {
2383
- const intermediate = messageRecordToIntermediate(commit.message);
2384
- if (intermediate) this.addIntermediateMessage(intermediate);
2385
- this.reducer.reset({
2386
- spaceId: this.spaceId,
2387
- sessionId: this.sessionId
2388
- });
2389
- this.resetCurrentMessage();
2390
- }
2391
- if (commit.kind === "final") {
2392
- this.reducer.complete({
2393
- spaceId: this.spaceId,
2394
- sessionId: this.sessionId,
2395
- turnId: getTurnIdFromMessage(commit.message)
2396
- });
2397
- this.resetCurrentMessage();
2398
- }
2399
- if (commit.kind === "error") {
2400
- this.reducer.fail({
2401
- spaceId: this.spaceId,
2402
- sessionId: this.sessionId,
2403
- turnId: getTurnIdFromMessage(commit.message)
2404
- });
2405
- 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
+ }
2406
2303
  }
2407
2304
  this.emit(handlers, {
2408
2305
  type: "commit",
@@ -2414,17 +2311,16 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
2414
2311
  const turn = event.payload.turn;
2415
2312
  if (!isRecord(turn)) return;
2416
2313
  const typedTurn = turn;
2417
- if (typedTurn.status === "interrupted") this.reducer.interrupt({
2418
- spaceId: this.spaceId,
2419
- sessionId: this.sessionId,
2420
- turnId: typedTurn.id
2421
- });
2422
- else this.reducer.complete({
2423
- spaceId: this.spaceId,
2424
- sessionId: this.sessionId,
2425
- turnId: typedTurn.id
2426
- });
2427
- 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
+ }
2428
2324
  this.emit(handlers, {
2429
2325
  type: "finalized",
2430
2326
  turn: typedTurn,
@@ -2631,17 +2527,22 @@ var SpacesApi = class {
2631
2527
  constructor(transport) {
2632
2528
  this.transport = transport;
2633
2529
  }
2634
- list(customFetch) {
2635
- return this.transport.request("/api/spaces", {
2636
- method: "GET",
2637
- fetch: customFetch
2638
- });
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 });
2639
2544
  }
2640
- /**
2641
- * Resolve the user's default space (owned/member home, else most recent).
2642
- * When the account has no accessible space, the API creates a blank Home
2643
- * space (`slug=home`) and returns it.
2644
- */
2545
+ /** Landing Space within the user's own Spaces; use `ensureHome()` for a write target. */
2645
2546
  getDefault(customFetch) {
2646
2547
  return this.transport.request("/api/spaces/default", {
2647
2548
  method: "GET",
@@ -2654,6 +2555,16 @@ var SpacesApi = class {
2654
2555
  getBySlug(username, slug, customFetch) {
2655
2556
  return this.transport.request(`/api/spaces/by-slug/${encodeURIComponent(username)}/${encodeURIComponent(slug)}`, { fetch: customFetch });
2656
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
+ }
2657
2568
  create(input, headers) {
2658
2569
  return this.transport.request("/api/spaces", {
2659
2570
  method: "POST",
@@ -2754,7 +2665,7 @@ var SpaceFilesApi = class {
2754
2665
  }
2755
2666
  read(path, customFetch, signal) {
2756
2667
  const params = new URLSearchParams({ path });
2757
- 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()}`, {
2758
2669
  fetch: customFetch,
2759
2670
  signal
2760
2671
  });
@@ -2817,15 +2728,33 @@ var SpaceFilesApi = class {
2817
2728
  fetch: customFetch
2818
2729
  });
2819
2730
  }
2820
- async download(path, customFetch) {
2731
+ async open(path, options = {}) {
2821
2732
  const params = new URLSearchParams({ path });
2822
- const raw = await this.transport.raw(`/api/spaces/${this.spaceId}/fs/download?${params.toString()}`, { fetch: customFetch });
2823
- if (raw.response.status === 202) throw new HttpError("File is being prepared. Please retry shortly.", 202, await raw.json().catch(() => null));
2824
- 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 });
2825
2754
  return {
2826
- blob,
2827
- filename: getFilenameFromContentDisposition(raw.response.headers.get("content-disposition")) ?? path.split("/").pop() ?? "download",
2828
- mimeType: raw.response.headers.get("content-type") ?? blob.type ?? "application/octet-stream"
2755
+ blob: await response.blob(),
2756
+ filename,
2757
+ mimeType
2829
2758
  };
2830
2759
  }
2831
2760
  write(input) {
@@ -2835,6 +2764,31 @@ var SpaceFilesApi = class {
2835
2764
  body: JSON.stringify(input)
2836
2765
  });
2837
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
+ }
2838
2792
  createDir(path, mutationId) {
2839
2793
  return this.transport.request(`/api/spaces/${this.spaceId}/fs/dir`, {
2840
2794
  method: "POST",
@@ -3111,6 +3065,11 @@ var SessionClient = class {
3111
3065
  get(customFetch) {
3112
3066
  return this.transport.request(`/api/sessions/${this.id}`, { fetch: customFetch });
3113
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
+ }
3114
3073
  /** Space files changed by this session's agent write/edit tool calls. */
3115
3074
  files(options = {}) {
3116
3075
  const query = options.limit ? `?limit=${options.limit}` : "";
@@ -3695,34 +3654,111 @@ var BoardRealtimeClient = class {
3695
3654
  var BoardClient = class {
3696
3655
  spaceId;
3697
3656
  id;
3657
+ transport;
3698
3658
  websocketClient;
3699
3659
  realtime;
3700
- boards;
3701
3660
  constructor(spaceId, id, transport, websocketClient) {
3702
3661
  this.spaceId = spaceId;
3703
3662
  this.id = id;
3663
+ this.transport = transport;
3704
3664
  this.websocketClient = websocketClient;
3705
- this.boards = new SpaceBoardsApi(transport, spaceId, websocketClient);
3706
3665
  this.realtime = new BoardRealtimeClient(websocketClient, spaceId, id);
3707
3666
  }
3708
- capabilities(customFetch) {
3709
- return this.boards.capabilities(this.id, customFetch);
3667
+ get path() {
3668
+ return `/api/spaces/${this.spaceId}/boards/${this.id}`;
3710
3669
  }
3711
- summary(customFetch) {
3712
- 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 });
3713
3687
  }
3714
- authoring(input = {}, customFetch) {
3715
- 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
+ });
3716
3699
  }
3717
- /** Read-only transaction log, newest first; the first page carries the current rows. */
3718
- transactions(input = {}, customFetch) {
3719
- 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 });
3720
3707
  }
3721
- mutateSemantic(input) {
3722
- return this.boards.mutateSemantic(this.id, {
3723
- ...input,
3724
- mutationId: input.mutationId ?? randomBoardId(),
3725
- 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"
3726
3762
  });
3727
3763
  }
3728
3764
  updateAwareness(seq, update) {
@@ -3734,18 +3770,6 @@ var BoardClient = class {
3734
3770
  update
3735
3771
  });
3736
3772
  }
3737
- play(command) {
3738
- return this.boards.play(this.id, command);
3739
- }
3740
- pause(command) {
3741
- return this.boards.pause(this.id, command);
3742
- }
3743
- seek(command) {
3744
- return this.boards.seek(this.id, command);
3745
- }
3746
- stop(command) {
3747
- return this.boards.stop(this.id, command);
3748
- }
3749
3773
  subscribe(handlers) {
3750
3774
  return this.realtime.subscribe(handlers);
3751
3775
  }
@@ -3767,66 +3791,14 @@ var SpaceBoardsApi = class {
3767
3791
  byId(boardId) {
3768
3792
  return new BoardClient(this.spaceId, boardId, this.transport, this.websocketClient);
3769
3793
  }
3770
- async create(input) {
3771
- 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) {
3772
3796
  return this.transport.request(`/api/spaces/${this.spaceId}/boards`, {
3773
3797
  method: "POST",
3774
3798
  headers: { "Content-Type": "application/json" },
3775
3799
  body: JSON.stringify(input)
3776
3800
  });
3777
3801
  }
3778
- authoring(boardId, input = {}, customFetch) {
3779
- if (input.include?.length === 0) throw new Error("Board authoring include must not be empty.");
3780
- const params = new URLSearchParams();
3781
- for (const section of input.include ?? []) params.append("include", section);
3782
- if (input.itemIds?.length) params.set("itemIds", input.itemIds.join(","));
3783
- if (input.connectionIds?.length) params.set("connectionIds", input.connectionIds.join(","));
3784
- if (input.effectIds?.length) params.set("effectIds", input.effectIds.join(","));
3785
- if (input.compositionIds?.length) params.set("compositionIds", input.compositionIds.join(","));
3786
- if (input.viewport) params.set("viewport", JSON.stringify(input.viewport));
3787
- const query = params.toString();
3788
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/authoring${query ? `?${query}` : ""}`, { fetch: customFetch });
3789
- }
3790
- mutateSemantic(boardId, mutation) {
3791
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/mutations`, {
3792
- method: "POST",
3793
- headers: { "Content-Type": "application/json" },
3794
- body: JSON.stringify(mutation)
3795
- });
3796
- }
3797
- summary(boardId, customFetch) {
3798
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/summary`, { fetch: customFetch });
3799
- }
3800
- capabilities(boardId, customFetch) {
3801
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/capabilities`, { fetch: customFetch });
3802
- }
3803
- transactions(boardId, input = {}, customFetch) {
3804
- const params = new URLSearchParams();
3805
- if (input.before !== void 0) params.set("before", String(input.before));
3806
- if (input.limit !== void 0) params.set("limit", String(input.limit));
3807
- if (input.snapshot !== void 0) params.set("snapshot", String(input.snapshot));
3808
- const query = params.toString();
3809
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/transactions${query ? `?${query}` : ""}`, { fetch: customFetch });
3810
- }
3811
- playback(boardId, command) {
3812
- return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/playback`, {
3813
- method: "POST",
3814
- headers: { "Content-Type": "application/json" },
3815
- body: JSON.stringify(command)
3816
- });
3817
- }
3818
- play(boardId, command) {
3819
- return this.playback(boardId, command);
3820
- }
3821
- pause(boardId, command) {
3822
- return this.playback(boardId, command);
3823
- }
3824
- seek(boardId, command) {
3825
- return this.playback(boardId, command);
3826
- }
3827
- stop(boardId, command) {
3828
- return this.playback(boardId, command);
3829
- }
3830
3802
  };
3831
3803
  var SpaceCheckpointFilesApi = class {
3832
3804
  transport;
@@ -3966,6 +3938,7 @@ var SpaceClient = class {
3966
3938
  webhooks;
3967
3939
  env;
3968
3940
  sandbox;
3941
+ displays;
3969
3942
  invitations;
3970
3943
  labels;
3971
3944
  boards;
@@ -3989,6 +3962,7 @@ var SpaceClient = class {
3989
3962
  this.webhooks = new SpaceWebhooksApi(transport, id);
3990
3963
  this.env = new SpaceEnvApi(transport, id);
3991
3964
  this.sandbox = new SpaceSandboxApi(transport, id);
3965
+ this.displays = new SpaceDisplaysApi(transport, id);
3992
3966
  this.invitations = new SpaceInvitationsApi(transport, id);
3993
3967
  this.labels = new SpaceLabelsApi(transport, id);
3994
3968
  this.boards = new SpaceBoardsApi(transport, id, websocketClient);
@@ -4394,6 +4368,7 @@ var UserApi = class {
4394
4368
  if (options?.limit !== void 0) params.set("limit", String(options.limit));
4395
4369
  if (options?.cursor) params.set("cursor", options.cursor);
4396
4370
  if (options?.source?.length) params.set("source", options.source.join(","));
4371
+ if (options?.includeForks) params.set("includeForks", "1");
4397
4372
  const query = params.toString();
4398
4373
  return this.transport.request(`/api/me/sessions${query ? `?${query}` : ""}`, { fetch });
4399
4374
  }
@@ -4430,6 +4405,53 @@ var UserLabelsApi = class {
4430
4405
  constructor(transport) {
4431
4406
  this.transport = transport;
4432
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
+ }
4433
4455
  getResourceLabels(resourceType, resourceRef) {
4434
4456
  const params = new URLSearchParams({ resourceRef });
4435
4457
  return this.transport.request(`/api/me/resources/${resourceType}/labels?${params.toString()}`);
@@ -4602,10 +4624,9 @@ var AppsApi = class {
4602
4624
  //#endregion
4603
4625
  //#region src/apis/desktop-commands.ts
4604
4626
  const DEFAULT_POLL_INTERVAL_MS = 300;
4605
- const resolveTimeoutMs = (timeoutMs) => {
4606
- const value = timeoutMs ?? 6e5;
4607
- if (!Number.isFinite(value) || value <= 0 || value > 432e5) throw new RangeError(`timeoutMs must be between 1 and ${DESKTOP_COMMAND_MAX_TIMEOUT_MS} milliseconds`);
4608
- return value;
4627
+ const assertTimeoutMs = (timeoutMs) => {
4628
+ if (timeoutMs === void 0) return;
4629
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 432e5) throw new RangeError(`timeoutMs must be between 1 and ${DESKTOP_COMMAND_MAX_TIMEOUT_MS} milliseconds`);
4609
4630
  };
4610
4631
  const sleep = (ms, signal) => new Promise((resolve, reject) => {
4611
4632
  if (signal?.aborted) {
@@ -4637,6 +4658,10 @@ var DesktopCommandsApi = class {
4637
4658
  get(commandId) {
4638
4659
  return this.transport.request(`/api/desktop/commands/${encodeURIComponent(commandId)}`);
4639
4660
  }
4661
+ /** The target desktop accepts before running; `accepted: false` means drop it. */
4662
+ accept(commandId) {
4663
+ return this.transport.request(`/api/desktop/commands/${encodeURIComponent(commandId)}/accept`, { method: "POST" });
4664
+ }
4640
4665
  reportResult(commandId, input) {
4641
4666
  return this.transport.request(`/api/desktop/commands/${encodeURIComponent(commandId)}/result`, {
4642
4667
  method: "POST",
@@ -4645,15 +4670,17 @@ var DesktopCommandsApi = class {
4645
4670
  });
4646
4671
  }
4647
4672
  async run(input, options = {}) {
4673
+ assertTimeoutMs(options.timeoutMs);
4648
4674
  const { command } = await this.create(input);
4649
4675
  if (isTerminalDesktopCommandStatus(command.status)) return command;
4650
4676
  return this.wait(command.commandId, options);
4651
4677
  }
4652
4678
  async wait(commandId, options = {}) {
4653
- const timeoutMs = resolveTimeoutMs(options.timeoutMs);
4679
+ assertTimeoutMs(options.timeoutMs);
4680
+ const startedAt = Date.now();
4654
4681
  const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
4655
- const deadline = Date.now() + timeoutMs;
4656
4682
  let latest = (await this.get(commandId)).command;
4683
+ const deadline = startedAt + (options.timeoutMs ?? defaultDesktopCommandTimeoutMs(latest.command));
4657
4684
  while (!isTerminalDesktopCommandStatus(latest.status)) {
4658
4685
  const remaining = deadline - Date.now();
4659
4686
  if (remaining <= 0) break;
@@ -4780,4 +4807,4 @@ var CohubHttpClient = class {
4780
4807
  };
4781
4808
  const createHttpClient = (options) => new CohubHttpClient(options);
4782
4809
  //#endregion
4783
- export { BoardEffectInputSchema as $, SkillsApi as A, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS as B, SessionPatchReducer as C, ReferencesApi as D, SessionAccessApi as E, ChannelsApi as F, parseDesktopCommand as G, DESKTOP_COMMAND_VERSION as H, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS as I, NAVIGATION_ERROR_MESSAGE_MAX_LENGTH as J, parseDesktopCommandId as K, DESKTOP_COMMAND_MAX_TIMEOUT_MS as L, ModelsApi as M, GenerationsApi as N, SearchApi as O, CronJobsApi as P, BoardSemanticMutationSchema as Q, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES as R, parseAssistantMessageCommit as S, ensureRealtimeConnected as T, isDesktopCallMethod as U, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS as V, isTerminalDesktopCommandStatus as W, BoardItemPatchSchema as X, BoardAuthoringItemSchema as Y, BoardSemanticCommandSchema as Z, PublicInviteApi as _, DesktopCommandsApi as a, BoardCompositionSchema as at, SessionGenerationStreamClient as b, ReferralsApi as c, parseBoardCompositionInput as ct, TasksApi as d, BOARD_BUILTIN_CAPABILITIES as dt, BoardEffectSchema as et, BoardClient as f, BOARD_BUILTIN_CLIP_KINDS as ft, SpacesApi as g, SpaceWebhooksApi as h, WorkCommerceApi as i, BoardCompositionInputSchema as it, PromptsApi as j, PublicAssetsApi as k, UsersApi as l, BoardConnectionSchema as lt, SpacePublicFilesApi as m, DEFAULT_BOARD_RENDER_LIMITS as mt, createHttpClient as n, BOARD_ANIMATION_CHANNELS as nt, UiCommandsApi as o, BoardProceduralClipSchema as ot, SpaceClient as p, BOARD_BUILTIN_EFFECT_KINDS as pt, resolveOpenSurface as q, AppCommerceApi as r, BOARD_ANIMATION_CHANNEL_CAPABILITIES as rt, AppsApi as s, BoardTrackSchema as st, CohubHttpClient as t, parseBoardEffectInput as tt, UserApi as u, BOARD_ARROW_STROKE_SIZE as ut, buildSpaceInvitePath as v, createSessionPatchReducer as w, createSessionGenerationStreamClient as x, buildSpacePath as y, DESKTOP_COMMAND_PENDING_TTL_SECONDS 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 };