@seriousgaming/sdk 0.0.0-stage → 1.0.0-alpha.2

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 (170) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +1 -0
  3. package/README.md +33 -2
  4. package/lib/build/callbacks.d.ts +998 -0
  5. package/lib/build/callbacks.js +32 -0
  6. package/lib/build/context-merge.d.ts +59 -0
  7. package/lib/build/context-merge.js +118 -0
  8. package/lib/build/document.d.ts +36 -0
  9. package/lib/build/document.js +8 -0
  10. package/lib/build/encoding.d.ts +26 -0
  11. package/lib/build/encoding.js +78 -0
  12. package/lib/build/endpoints.d.ts +421 -0
  13. package/lib/build/endpoints.js +46 -0
  14. package/lib/build/hooks.d.ts +74 -0
  15. package/lib/build/hooks.js +1 -0
  16. package/lib/build/index.d.ts +18 -0
  17. package/lib/build/index.js +16 -0
  18. package/lib/build/llm.d.ts +166 -0
  19. package/lib/build/llm.js +17 -0
  20. package/lib/build/resource-schema.d.ts +1048 -0
  21. package/lib/build/resource-schema.js +7 -0
  22. package/lib/build/scheduling.d.ts +112 -0
  23. package/lib/build/scheduling.js +217 -0
  24. package/lib/build/seed.d.ts +51 -0
  25. package/lib/build/seed.js +89 -0
  26. package/lib/build/signatures.d.ts +372 -0
  27. package/lib/build/signatures.js +1 -0
  28. package/lib/build/speech.d.ts +102 -0
  29. package/lib/build/speech.js +11 -0
  30. package/lib/build/tick-validation.d.ts +23 -0
  31. package/lib/build/tick-validation.js +96 -0
  32. package/lib/build/timer.d.ts +74 -0
  33. package/lib/build/timer.js +1 -0
  34. package/lib/build/validators.d.ts +116 -0
  35. package/lib/build/validators.js +1 -0
  36. package/lib/commands.d.ts +357 -0
  37. package/lib/commands.js +14 -0
  38. package/lib/e2e.d.ts +238 -0
  39. package/lib/e2e.js +248 -0
  40. package/lib/projection.d.ts +266 -0
  41. package/lib/projection.js +23 -0
  42. package/lib/testing/index.d.ts +13 -0
  43. package/lib/testing/index.js +13 -0
  44. package/lib/testing/logic-args.d.ts +177 -0
  45. package/lib/testing/logic-args.js +229 -0
  46. package/lib/testing/stub-services.d.ts +41 -0
  47. package/lib/testing/stub-services.js +176 -0
  48. package/lib/ui/CrossTeamDataContext.d.ts +36 -0
  49. package/lib/ui/CrossTeamDataContext.js +32 -0
  50. package/lib/ui/PresentationContext.d.ts +72 -0
  51. package/lib/ui/PresentationContext.js +13 -0
  52. package/lib/ui/PresentationWrapper.d.ts +12 -0
  53. package/lib/ui/PresentationWrapper.js +78 -0
  54. package/lib/ui/SessionUserInterfaceCallbacksContext.d.ts +3 -0
  55. package/lib/ui/SessionUserInterfaceCallbacksContext.js +7 -0
  56. package/lib/ui/SessionUserInterfaceDataContext.d.ts +103 -0
  57. package/lib/ui/SessionUserInterfaceDataContext.js +17 -0
  58. package/lib/ui/SessionUserInterfaceHookFailuresContext.d.ts +44 -0
  59. package/lib/ui/SessionUserInterfaceHookFailuresContext.js +15 -0
  60. package/lib/ui/SessionUserInterfaceHostOverlaysContext.d.ts +23 -0
  61. package/lib/ui/SessionUserInterfaceHostOverlaysContext.js +23 -0
  62. package/lib/ui/SessionUserInterfaceLocaleContext.d.ts +11 -0
  63. package/lib/ui/SessionUserInterfaceLocaleContext.js +13 -0
  64. package/lib/ui/SessionUserInterfaceMetaContext.d.ts +39 -0
  65. package/lib/ui/SessionUserInterfaceMetaContext.js +13 -0
  66. package/lib/ui/SessionUserInterfaceParticipantsContext.d.ts +12 -0
  67. package/lib/ui/SessionUserInterfaceParticipantsContext.js +16 -0
  68. package/lib/ui/SessionUserInterfaceTeamConfigurationContext.d.ts +53 -0
  69. package/lib/ui/SessionUserInterfaceTeamConfigurationContext.js +23 -0
  70. package/lib/ui/SessionUserInterfaceTeamLinksContext.d.ts +56 -0
  71. package/lib/ui/SessionUserInterfaceTeamLinksContext.js +12 -0
  72. package/lib/ui/SessionUserInterfaceTeamsContext.d.ts +13 -0
  73. package/lib/ui/SessionUserInterfaceTeamsContext.js +13 -0
  74. package/lib/ui/SessionUserInterfaceThemeContext.d.ts +9 -0
  75. package/lib/ui/SessionUserInterfaceThemeContext.js +7 -0
  76. package/lib/ui/SessionUserInterfaceThemePreferenceContext.d.ts +3 -0
  77. package/lib/ui/SessionUserInterfaceThemePreferenceContext.js +7 -0
  78. package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.d.ts +42 -0
  79. package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.js +26 -0
  80. package/lib/ui/SessionUserInterfaceWrapper.d.ts +8 -0
  81. package/lib/ui/SessionUserInterfaceWrapper.js +321 -0
  82. package/lib/ui/SettingsEditorCallbacksContext.d.ts +3 -0
  83. package/lib/ui/SettingsEditorCallbacksContext.js +7 -0
  84. package/lib/ui/SettingsEditorContext.d.ts +33 -0
  85. package/lib/ui/SettingsEditorContext.js +16 -0
  86. package/lib/ui/SettingsEditorLocaleContext.d.ts +13 -0
  87. package/lib/ui/SettingsEditorLocaleContext.js +15 -0
  88. package/lib/ui/SettingsEditorThemeContext.d.ts +9 -0
  89. package/lib/ui/SettingsEditorThemeContext.js +7 -0
  90. package/lib/ui/SettingsEditorThemePreferenceContext.d.ts +3 -0
  91. package/lib/ui/SettingsEditorThemePreferenceContext.js +7 -0
  92. package/lib/ui/SettingsEditorWrapper.d.ts +13 -0
  93. package/lib/ui/SettingsEditorWrapper.js +74 -0
  94. package/lib/ui/UserInterfaceCallbacksContext.d.ts +3 -0
  95. package/lib/ui/UserInterfaceCallbacksContext.js +7 -0
  96. package/lib/ui/UserInterfaceDataContext.d.ts +78 -0
  97. package/lib/ui/UserInterfaceDataContext.js +11 -0
  98. package/lib/ui/UserInterfaceLocaleContext.d.ts +13 -0
  99. package/lib/ui/UserInterfaceLocaleContext.js +15 -0
  100. package/lib/ui/UserInterfaceOwnTeamContext.d.ts +25 -0
  101. package/lib/ui/UserInterfaceOwnTeamContext.js +26 -0
  102. package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.d.ts +52 -0
  103. package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.js +13 -0
  104. package/lib/ui/UserInterfaceParticipantContext.d.ts +16 -0
  105. package/lib/ui/UserInterfaceParticipantContext.js +24 -0
  106. package/lib/ui/UserInterfaceParticipantsContext.d.ts +12 -0
  107. package/lib/ui/UserInterfaceParticipantsContext.js +13 -0
  108. package/lib/ui/UserInterfaceRunKindContext.d.ts +31 -0
  109. package/lib/ui/UserInterfaceRunKindContext.js +31 -0
  110. package/lib/ui/UserInterfaceTeamsContext.d.ts +12 -0
  111. package/lib/ui/UserInterfaceTeamsContext.js +13 -0
  112. package/lib/ui/UserInterfaceThemeContext.d.ts +9 -0
  113. package/lib/ui/UserInterfaceThemeContext.js +7 -0
  114. package/lib/ui/UserInterfaceThemePreferenceContext.d.ts +3 -0
  115. package/lib/ui/UserInterfaceThemePreferenceContext.js +7 -0
  116. package/lib/ui/UserInterfaceTimerContext.d.ts +10 -0
  117. package/lib/ui/UserInterfaceTimerContext.js +12 -0
  118. package/lib/ui/UserInterfaceValidationContext.d.ts +25 -0
  119. package/lib/ui/UserInterfaceValidationContext.js +14 -0
  120. package/lib/ui/UserInterfaceViewerRoleContext.d.ts +14 -0
  121. package/lib/ui/UserInterfaceViewerRoleContext.js +17 -0
  122. package/lib/ui/UserInterfaceWrapper.d.ts +8 -0
  123. package/lib/ui/UserInterfaceWrapper.js +219 -0
  124. package/lib/ui/expectedParentOrigin.d.ts +10 -0
  125. package/lib/ui/expectedParentOrigin.js +32 -0
  126. package/lib/ui/index.d.ts +50 -0
  127. package/lib/ui/index.js +52 -0
  128. package/lib/ui/internal/SurfaceGate.d.ts +19 -0
  129. package/lib/ui/internal/SurfaceGate.js +17 -0
  130. package/lib/ui/internal/SurfaceScope.d.ts +17 -0
  131. package/lib/ui/internal/SurfaceScope.js +16 -0
  132. package/lib/ui/internal/createContexts.d.ts +50 -0
  133. package/lib/ui/internal/createContexts.js +32 -0
  134. package/lib/ui/internal/document-attributes.d.ts +9 -0
  135. package/lib/ui/internal/document-attributes.js +87 -0
  136. package/lib/ui/internal/surface-timer.d.ts +3 -0
  137. package/lib/ui/internal/surface-timer.js +11 -0
  138. package/lib/ui/internal/useLatestRef.d.ts +7 -0
  139. package/lib/ui/internal/useLatestRef.js +11 -0
  140. package/lib/ui/internal/useOptimisticMerge.d.ts +19 -0
  141. package/lib/ui/internal/useOptimisticMerge.js +28 -0
  142. package/lib/ui/internal/useSurfaceControls.d.ts +52 -0
  143. package/lib/ui/internal/useSurfaceControls.js +19 -0
  144. package/lib/ui/internal/useTypedPastState.d.ts +17 -0
  145. package/lib/ui/internal/useTypedPastState.js +17 -0
  146. package/lib/ui/mutation-commit.d.ts +32 -0
  147. package/lib/ui/mutation-commit.js +1 -0
  148. package/lib/ui/mutation-outcome.d.ts +66 -0
  149. package/lib/ui/mutation-outcome.js +49 -0
  150. package/lib/ui/request-id.d.ts +18 -0
  151. package/lib/ui/request-id.js +44 -0
  152. package/lib/ui/testing.d.ts +365 -0
  153. package/lib/ui/testing.js +572 -0
  154. package/lib/ui/tick-snapshot.d.ts +28 -0
  155. package/lib/ui/tick-snapshot.js +34 -0
  156. package/lib/ui/ui-protocol.d.ts +799 -0
  157. package/lib/ui/ui-protocol.js +50 -0
  158. package/lib/ui/useAsset.d.ts +80 -0
  159. package/lib/ui/useAsset.js +139 -0
  160. package/lib/ui/useEndpointCallbacks.d.ts +31 -0
  161. package/lib/ui/useEndpointCallbacks.js +68 -0
  162. package/lib/ui/useHostChannel.d.ts +90 -0
  163. package/lib/ui/useHostChannel.js +338 -0
  164. package/lib/ui/useMutationReporter.d.ts +100 -0
  165. package/lib/ui/useMutationReporter.js +97 -0
  166. package/lib/ui/usePastState.d.ts +39 -0
  167. package/lib/ui/usePastState.js +63 -0
  168. package/lib/ui/useViewerStorage.d.ts +54 -0
  169. package/lib/ui/useViewerStorage.js +141 -0
  170. package/package.json +89 -3
@@ -0,0 +1,998 @@
1
+ /**
2
+ * Callback interfaces for SDK components
3
+ *
4
+ * Defines the callbacks available to the engine, user interfaces, and configuration
5
+ * user interfaces. LLM-specific types are imported from ./llm.
6
+ *
7
+ * Asset references — every callback below that takes or returns a file
8
+ * identifier uses the unified asset URI scheme:
9
+ *
10
+ * `asset:<path>` — package-bundled file. The path is relative to the
11
+ * active scenario's `assets/` directory.
12
+ * `blob:<id>` — runtime-uploaded file. The id is whatever the runtime
13
+ * assigns when the blob was stored.
14
+ *
15
+ * Any string starting with `asset:` or `blob:` is unambiguously an asset
16
+ * reference. No other strings are.
17
+ */
18
+ import { ExtractTextFunction } from './document.js';
19
+ import { LLMGenerateFunction } from './llm.js';
20
+ import { ApiUse } from './resource-schema.js';
21
+ import { GenerateSpeechFunction } from './speech.js';
22
+ /**
23
+ * Shape returned by `readAsset` for both `asset:` and `blob:` references.
24
+ */
25
+ export interface ReadAssetResponse {
26
+ /** Raw bytes of the referenced file. */
27
+ data: Uint8Array;
28
+ /** MIME type of the referenced file (e.g. `application/pdf`, `image/png`). */
29
+ mimeType: string;
30
+ /**
31
+ * Display name of the referenced file. For `blob:` references this is the
32
+ * original name supplied at upload time. For `asset:` references this is
33
+ * the basename of the path.
34
+ */
35
+ name: string;
36
+ }
37
+ /**
38
+ * Who a team is: the fields every team entry in the SDK carries (§5.6).
39
+ *
40
+ * One shape wherever a team is listed or addressed, so a renderer written for
41
+ * one list serves every other: the team lists a surface receives, a cross-team
42
+ * or past-state row, an endpoint's `team` and `session.teams`, the state
43
+ * projection and a presentation's teams all extend it.
44
+ */
45
+ export interface TeamInfo {
46
+ /** Stable `team-N` id (§5.6). */
47
+ teamId: string;
48
+ /**
49
+ * Platform-assigned, session-stable team index. Assigned at team creation
50
+ * (dense 0..N-1 at session start) and never reassigned: removals leave a
51
+ * gap, later-added teams take the lowest unused index. The same team has
52
+ * the same index everywhere it appears. `teams.defaultNames` entries are
53
+ * applied by this index.
54
+ */
55
+ index: number;
56
+ /** The team's display name, resolved per §5.4 (a facilitator rename included). */
57
+ name: string;
58
+ }
59
+ /**
60
+ * One team's state, live. Returned per team by the `useUserInterfaceCrossTeamData` hook.
61
+ *
62
+ * {@link PastStateEntry} is this plus *when*, which is the only thing that
63
+ * actually differs between watching a team now and reviewing it later. Sharing
64
+ * the base is what lets one cell renderer serve both: `entry.name` and
65
+ * `entry.context.ledger` read the same either way.
66
+ */
67
+ /**
68
+ * One team's state at one committed past tick, as returned by `getPastState`.
69
+ *
70
+ * One entry per (tick, team) pair, which is what lets a single call answer
71
+ * every historical question the surfaces used to need four for: one round for
72
+ * one team, a range for one team, one round across every team, a range across
73
+ * every team. Group by `tick` for a results table, filter by `teamId` for a
74
+ * trend line.
75
+ *
76
+ * `context` is composed exactly like the live `data` a surface receives — the
77
+ * team's own context with the shared store as it stood at that tick folded in
78
+ * — so a review screen renders a historical board through the surface's
79
+ * *current* config, and is then narrowed to `capabilityIds` when the query
80
+ * named any.
81
+ *
82
+ * Entries are point-in-time and immutable: the value for a given tick never
83
+ * changes within a session (an undo or reset removes it, it does not rewrite
84
+ * it), so a consumer can cache what it has fetched.
85
+ */
86
+ export interface TeamStateEntry<T = Record<string, unknown>> extends TeamInfo {
87
+ /**
88
+ * The team's context, composed like the live `data` a surface receives and
89
+ * narrowed to what this surface may see: its declared `crossTeam` reads for
90
+ * another team, its whole declared set for its own.
91
+ */
92
+ context: T;
93
+ }
94
+ export interface PastStateEntry<T = Record<string, unknown>> extends TeamStateEntry<T> {
95
+ /**
96
+ * The snapshot's number: snapshot N is the state after tick N (after
97
+ * `Init`, for snapshot 0), with the decisions round N + 1 entered on it.
98
+ */
99
+ tick: number;
100
+ /** When the snapshot was taken (epoch milliseconds). */
101
+ recordedAt: number;
102
+ /** Whether the tick controller had signalled game complete at that tick. */
103
+ complete: boolean;
104
+ }
105
+ /**
106
+ * What to read from the past.
107
+ *
108
+ * Both tick bounds are inclusive and `toTick` is clamped to the last committed
109
+ * past tick, so `{ fromTick: 0, toTick: completedTicks }` means 'everything
110
+ * reviewable' with no head arithmetic. Omitting `toTick` reads the single tick
111
+ * `fromTick`.
112
+ */
113
+ export interface PastStateQuery<T = Record<string, unknown>> {
114
+ /** First tick to read, inclusive. */
115
+ fromTick: number;
116
+ /**
117
+ * Last tick to read, inclusive; clamped to the last committed past tick.
118
+ * Omit to read only `fromTick`.
119
+ */
120
+ toTick?: number;
121
+ /**
122
+ * Which teams to read.
123
+ *
124
+ * `'own'` (the default) is the caller's own team on a game surface. `'all'`
125
+ * is every team. A team id reads that one team, which is how a session
126
+ * surface addresses a team it is not scoped to. Team ids are opaque and
127
+ * stable: take one from `SessionData` or `setTeamList`, never build one
128
+ * from an index. A runtime never assigns `'own'` or `'all'` as a team id
129
+ * (§5.6), so the sentinels cannot collide with one.
130
+ */
131
+ teams?: 'own' | 'all' | string;
132
+ /**
133
+ * Capabilities to include in each returned `context`. Omit for the
134
+ * surface's whole declared set.
135
+ *
136
+ * This is what makes a range read cheap: a results chart over sixty rounds
137
+ * wants one field per round, not sixty whole boards, and a growing log kept
138
+ * in context to avoid the round trips is delivered to every surface on
139
+ * every push for the rest of the session.
140
+ */
141
+ capabilityIds?: (keyof T & string)[];
142
+ }
143
+ /**
144
+ * A participant the platform tracks for some team in the session (§13.1).
145
+ *
146
+ * The one participant type, wherever a participant appears: the caller's own
147
+ * identity (`useUserInterfaceParticipant`), the live roster for the caller's
148
+ * team (`useUserInterfaceParticipants`) and for every team
149
+ * (`useSessionUserInterfaceParticipants`), the `getAllTeamsParticipants`
150
+ * callback, hook payloads, a validator's `actor`, an endpoint's roster and
151
+ * the state projection. The `teamId` groups a flat list by team; join it
152
+ * against the team list for each team's display name and index.
153
+ *
154
+ * `connected` is separate from existence: a participant record lives for the
155
+ * whole session once created, so an absent participant is `connected: false`
156
+ * rather than a missing entry.
157
+ */
158
+ export interface Participant {
159
+ /** Stable participant id, unique within its team. */
160
+ id: string;
161
+ /** The participant's display name. */
162
+ name: string;
163
+ /** Zero-based join order within the team. */
164
+ index: number;
165
+ /**
166
+ * True while the participant has a live connection to the platform.
167
+ *
168
+ * Informative wherever it appears, including in a hook payload: a
169
+ * facilitator can move a participant who is offline, so a `participantMove`
170
+ * hook sees `false` there and can act on it.
171
+ */
172
+ connected: boolean;
173
+ /** The team this participant belongs to (e.g. `team-1`). */
174
+ teamId: string;
175
+ }
176
+ /**
177
+ * The host's theme preference. `'system'` means the host follows the
178
+ * operating system's colour scheme; `'light'` and `'dark'` are explicit
179
+ * choices. Distinct from the resolved palette delivered to UIs through the
180
+ * theme channel — a UI reading `'system'` knows the host has not overridden
181
+ * the OS, which the resolved light/dark mode alone cannot tell it.
182
+ */
183
+ export type ThemePreference = 'light' | 'dark' | 'system';
184
+ /**
185
+ * A host-rendered overlay a session surface may ask the host to open
186
+ * (SPECIFICATION.md §6.4).
187
+ *
188
+ * These stay host-owned: the host renders them in its own chrome, above the
189
+ * surface, where the surface can neither restyle nor suppress them. Only the
190
+ * trigger is delegated, so a facilitator in front of a surface that owns the
191
+ * whole canvas does not have to leave it to reach the guide or a print sheet.
192
+ *
193
+ * The enum is extensible. Treat an unknown member as an overlay you cannot
194
+ * offer rather than an error, and render a control only for a member the host
195
+ * actually reports (see `openHostOverlay`).
196
+ */
197
+ export type HostOverlay = 'facilitator-guide' | 'print-sheets';
198
+ /**
199
+ * Every callback a runtime may offer logic: the engine, hooks, tick
200
+ * controllers and endpoints.
201
+ *
202
+ * The four `ApiUse` callbacks (`generateText`, `extractText`,
203
+ * `generateSpeech`, `writeBlob`) are services, gated by the resource's `uses`
204
+ * (§6.11). This interface is the superset, which is why three of them carry a
205
+ * `?`: whether one is there depends on what the resource declared. Type a
206
+ * resource's callbacks with its generated `Callbacks` ({@link LogicCallbacks})
207
+ * instead, where a declared service is present, so it is called without `?.`
208
+ * or `!`, and an undeclared one is absent from the type (and throws
209
+ * {@link UndeclaredApiUseError} if reached anyway). A runtime that cannot
210
+ * provide a service a package declares refuses the package at load.
211
+ */
212
+ export interface SignatureCallbacks {
213
+ /**
214
+ * Generate text using an LLM
215
+ *
216
+ * Examples:
217
+ * ```typescript
218
+ * // Simple string prompt
219
+ * const response = await callbacks.generateText("Summarize this: " + text);
220
+ *
221
+ * // Options object with prompt
222
+ * const response = await callbacks.generateText({
223
+ * prompt: "Summarize this: " + text
224
+ * });
225
+ *
226
+ * // With conversation history
227
+ * const response = await callbacks.generateText({
228
+ * messages: [
229
+ * { role: 'user', content: 'What is 2+2?' },
230
+ * { role: 'assistant', content: '4' },
231
+ * { role: 'user', content: 'What about 3+3?' }
232
+ * ]
233
+ * });
234
+ *
235
+ * // Structured output with JSON Schema
236
+ * const response = await callbacks.generateText({
237
+ * prompt: "Extract the name and age from: John is 30 years old",
238
+ * jsonSchema: {
239
+ * type: 'object',
240
+ * properties: {
241
+ * name: { type: 'string' },
242
+ * age: { type: 'integer' }
243
+ * },
244
+ * required: ['name', 'age']
245
+ * }
246
+ * });
247
+ *
248
+ * // With custom system prompt
249
+ * const response = await callbacks.generateText({
250
+ * prompt: "Translate to Spanish: Hello",
251
+ * systemPrompt: "You are a professional translator. Respond only with the translation."
252
+ * });
253
+ * ```
254
+ */
255
+ generateText: LLMGenerateFunction;
256
+ /**
257
+ * Read the bytes of an asset by reference. Accepts either an
258
+ * `asset:<path>` reference (resolved against the active scenario's
259
+ * `assets/` directory) or a `blob:<id>` reference (resolved against the
260
+ * runtime's blob store).
261
+ *
262
+ * Example:
263
+ * ```typescript
264
+ * const file = await callbacks.readAsset("blob:abc123");
265
+ * const briefing = await callbacks.readAsset("asset:briefing.pdf");
266
+ * ```
267
+ */
268
+ readAsset: (reference: string) => Promise<ReadAssetResponse>;
269
+ /**
270
+ * Read a committed past tick, without keeping a copy of it in context.
271
+ *
272
+ * The same history a surface reads through its own `getPastState` (§12.5),
273
+ * offered to logic on the same terms. An endpoint answering a facilitator's
274
+ * 'how has this team's profit moved' has no other route to it: unlike a
275
+ * surface, it cannot ask a UI to fetch the rounds and pass them in, because
276
+ * the caller it exists for may be an agent with no surface at all.
277
+ *
278
+ * It is also what lets an engine stop carrying a round-keyed ledger. A
279
+ * value the next round needs had to live in context, where it rides every
280
+ * push to every participant for the rest of the session (§12.4.2); read it
281
+ * back from here instead and it rides nothing.
282
+ *
283
+ * **What is reachable.** Every round committed before this call into logic
284
+ * began. For an engine that is every round strictly before the one its tick
285
+ * is closing — the round being closed is the board in `context`, and it is
286
+ * not recorded until the tick commits. A hook or endpoint runs outside a
287
+ * tick and reaches the head. Ticks outside that are omitted rather than
288
+ * returned as holes, exactly as for a surface, so a short result is normal.
289
+ *
290
+ * **Whose rounds.** `teams: 'own'` (the default) is the team this call is
291
+ * scoped to. A session-scoped endpoint (§6.12) has no team of its own, so
292
+ * `'own'` reads as the whole session there, which is the rule a session
293
+ * surface already follows. Entries carry the capabilities this resource
294
+ * declared in `reads` and `writes`, projected exactly as its live context
295
+ * is (§7.4).
296
+ *
297
+ * Not gated by `uses`: it reads state this resource is already entitled to
298
+ * see, one tick further back.
299
+ *
300
+ * Always present, like `readAsset`, the other callback `uses` does not
301
+ * gate.
302
+ *
303
+ * Example:
304
+ * ```typescript
305
+ * // what this team submitted last round
306
+ * const [previous] = await callbacks.getPastState<Context>({
307
+ * fromTick: meta.tick - 1,
308
+ * });
309
+ *
310
+ * // one field across every completed round, rather than a ledger in context
311
+ * const trend = await callbacks.getPastState<Context>({
312
+ * fromTick: 0,
313
+ * toTick: meta.tick - 1,
314
+ * capabilityIds: ['profit'],
315
+ * });
316
+ * ```
317
+ */
318
+ getPastState: <T = Record<string, unknown>>(query: PastStateQuery<T>) => Promise<PastStateEntry<T>[]>;
319
+ /**
320
+ * Extract text content from a document referenced under the unified
321
+ * asset URI scheme. Accepts either an `asset:` or `blob:` reference.
322
+ * Returns the extracted text, suitable for passing to an LLM.
323
+ *
324
+ * Currently supports PDF files.
325
+ *
326
+ * Example:
327
+ * ```typescript
328
+ * const result = await callbacks.extractText("blob:abc123");
329
+ * const summary = await callbacks.generateText("Summarize: " + result.content);
330
+ * ```
331
+ */
332
+ extractText?: ExtractTextFunction;
333
+ /**
334
+ * Synthesise speech from text. Returns a `blob:`-prefixed reference for
335
+ * the generated audio, which can be played in a UI via `getAssetUrl` or
336
+ * read in the engine via `readAsset`.
337
+ *
338
+ * Example:
339
+ * ```typescript
340
+ * const audio = await callbacks.generateSpeech({
341
+ * text: 'Welcome to the simulation.',
342
+ * voice: 'narrator',
343
+ * });
344
+ * // audio.reference can be passed to a UI for playback
345
+ * ```
346
+ */
347
+ generateSpeech?: GenerateSpeechFunction;
348
+ /**
349
+ * Write raw bytes to the runtime blob store. Returns a `blob:`-prefixed
350
+ * reference that can be returned to a caller (e.g. an endpoint response)
351
+ * and later resolved via `getAssetUrl`, `readAsset`, or `extractText`.
352
+ *
353
+ * The optional `options.retention` controls when the blob is deleted: `'game'`
354
+ * (default) blobs are wiped on game reset; `'settings'` blobs survive resets
355
+ * and are wiped only when the session is re-initialised.
356
+ *
357
+ * Example:
358
+ * ```typescript
359
+ * const reference = await callbacks.writeBlob(buffer, {
360
+ * name: 'summary.pdf',
361
+ * mimeType: 'application/pdf',
362
+ * });
363
+ * return { reportRef: reference };
364
+ * ```
365
+ */
366
+ writeBlob?: (data: Uint8Array | ArrayBuffer, options: {
367
+ name: string;
368
+ mimeType: string;
369
+ retention?: 'settings' | 'game';
370
+ }) => Promise<string>;
371
+ }
372
+ /**
373
+ * Callbacks available to user interface components
374
+ */
375
+ export interface UserInterfaceCallbacks {
376
+ /**
377
+ * Request the game to advance to the next tick.
378
+ * Equivalent to the player clicking the next-tick button.
379
+ *
380
+ * Note this does not submit anything: writes reach the team's context when
381
+ * `setData` is called, not when a tick is requested. Advancing the tick
382
+ * runs the engine over what is already there.
383
+ *
384
+ * Returns a promise that resolves once the tick has finished and context is
385
+ * updated, and rejects with a `MutationError` when the tick cannot run (e.g.
386
+ * the tick limit is reached). Safe to ignore for fire-and-forget.
387
+ *
388
+ * Example:
389
+ * ```typescript
390
+ * await callbacks.requestTick();
391
+ * ```
392
+ */
393
+ requestTick: () => Promise<void>;
394
+ /**
395
+ * Rename the team this surface belongs to.
396
+ *
397
+ * Names no team, because there is nothing to name: the host resolves which
398
+ * team the caller is on from its own bookkeeping, so a surface can only ever
399
+ * rename its own. The new name is the team's real name everywhere, reaching
400
+ * the host's own chrome and print sheets as well as every game surface, so a
401
+ * game has no reason to keep a display name of its own alongside it.
402
+ *
403
+ * Available on every player surface, participants and observers alike, with
404
+ * nothing to declare. The facilitator can rename the same team at the same
405
+ * moment from the session surface, so read the result back from
406
+ * `useUserInterfaceOwnTeam()` rather than trusting the write: the last change wins and the
407
+ * pushed value is the only authority.
408
+ *
409
+ * Resolves once the new roster is persisted and broadcast. Rejects with a
410
+ * `MutationError` when the host refuses: `denied` for a frozen session or a
411
+ * name it will not accept (blank, or past the host's length cap), which is
412
+ * terminal, and `error` for anything transient.
413
+ *
414
+ * Example:
415
+ * ```typescript
416
+ * await callbacks.renameOwnTeam('The Cartographers');
417
+ * ```
418
+ */
419
+ renameOwnTeam: (name: string) => Promise<void>;
420
+ /**
421
+ * Upload a file as a blob. Returns a `blob:`-prefixed reference that can
422
+ * be stored in context/config and later resolved via `getAssetUrl`,
423
+ * `readAsset`, or `extractText`.
424
+ *
425
+ * The blob's deletion lifecycle is assigned by the host from the UI type,
426
+ * not chosen by the bundle: uploads from the setup UI or the scenario
427
+ * editor survive game resets and are wiped only when the session is
428
+ * re-initialised; uploads from every other UI surface are wiped on game
429
+ * reset.
430
+ *
431
+ * Example:
432
+ * ```typescript
433
+ * const reference = await callbacks.uploadBlob(file);
434
+ * // Store in context: setData({ attachment: reference })
435
+ * ```
436
+ */
437
+ uploadBlob: (file: File) => Promise<string>;
438
+ /**
439
+ * Resolve an asset reference to a URL that can be used directly in HTML
440
+ * elements. Accepts either an `asset:<path>` or `blob:<id>` reference.
441
+ *
442
+ * Example:
443
+ * ```typescript
444
+ * const url = callbacks.getAssetUrl("blob:abc123");
445
+ * const logo = callbacks.getAssetUrl("asset:shared/logo.svg");
446
+ * // <img src={url} />
447
+ * ```
448
+ */
449
+ getAssetUrl: (reference: string) => string;
450
+ /**
451
+ * Read the bytes of an asset by reference. Accepts either an
452
+ * `asset:<path>` reference (resolved against the active scenario's
453
+ * `assets/` directory) or a `blob:<id>` reference.
454
+ *
455
+ * Example:
456
+ * ```typescript
457
+ * const file = await callbacks.readAsset("blob:abc123");
458
+ * ```
459
+ */
460
+ readAsset: (reference: string) => Promise<ReadAssetResponse>;
461
+ /**
462
+ * Read committed past state: one round or a range, this team or every
463
+ * team, whole boards or named capabilities.
464
+ *
465
+ * Always resolves to a list, ordered by `tick` then by stable team index,
466
+ * with one {@link PastStateEntry} per (tick, team) pair. A single round is
467
+ * a list of one, and an empty list means nothing in that range is
468
+ * reviewable — the same answer for a range past the head, an undone range,
469
+ * and a reset session. There is no separate `null`: 'nothing here' has one
470
+ * spelling.
471
+ *
472
+ * Reviewable history is scoped to the session. A reset bulk-deletes the
473
+ * snapshots, so every past round a player could scrub to disappears.
474
+ *
475
+ * Ticks that are not committed past ticks are **omitted** rather than
476
+ * returned as holes: a tick at or above the current head (read it from
477
+ * `useUserInterfaceData`'s `completedTicks`), an undone or reset tick, and any
478
+ * negative, non-integer or out-of-range tick. A host MAY return fewer
479
+ * entries than the range implies, so treat a short result as normal and
480
+ * read on from the last tick you received. It rejects with a
481
+ * {@link TickSnapshotError} only on a genuine transport or host fault.
482
+ *
483
+ * **Authorisation.** Not gated by the bundle's `uses`. Own-team entries
484
+ * carry the capabilities this surface declared in `reads` and `writes`.
485
+ * `teams: 'all'` additionally requires `crossTeam: true` on a capability
486
+ * for it to appear in *another* team's entry (§6.2), which is the same
487
+ * boundary the live `useUserInterfaceCrossTeamData` hook enforces: reading another
488
+ * team's history grants nothing reading it live would not.
489
+ *
490
+ * Debounce scrubbing and cache what you fetch: an entry is immutable until
491
+ * the head decreases, so re-visiting a round is free.
492
+ *
493
+ * Example:
494
+ * ```typescript
495
+ * // this round, this team
496
+ * const [board] = await callbacks.getPastState<Context>({ fromTick: day });
497
+ * if (board) renderBoard(board.context);
498
+ *
499
+ * // a profit trend over every completed round, one field per round
500
+ * const trend = await callbacks.getPastState<Context>({
501
+ * fromTick: 0,
502
+ * toTick: completedTicks,
503
+ * capabilityIds: ['profit'],
504
+ * });
505
+ *
506
+ * // every team's ledger at one past round
507
+ * const settlement = await callbacks.getPastState<Context>({
508
+ * fromTick: day,
509
+ * teams: 'all',
510
+ * capabilityIds: ['ledger'],
511
+ * });
512
+ * ```
513
+ */
514
+ getPastState: <T = Record<string, unknown>>(query: PastStateQuery<T>) => Promise<PastStateEntry<T>[]>;
515
+ /**
516
+ * Read the current participant roster of every team in the session, for
517
+ * building a cross-team directory (who is on each team) in a negotiation or
518
+ * trading game. Returns one flat entry per participant across all teams,
519
+ * each tagged with its `teamId`; join against {@link useUserInterfaceTeams}
520
+ * for team names and indexes.
521
+ *
522
+ * The result is a point-in-time snapshot, not a live subscription: it does
523
+ * not update as participants join or leave, so re-call it to refresh. The
524
+ * caller's own team stays live through the {@link useUserInterfaceParticipants}
525
+ * hook.
526
+ *
527
+ * Only meaningful in shared-state games, and gated by the bundle's
528
+ * `crossTeamParticipants` declaration: a UI that did not declare it resolves
529
+ * to an empty array. Rejects only on a genuine transport or host fault.
530
+ *
531
+ * Example:
532
+ * ```typescript
533
+ * const everyone = await callbacks.getAllTeamsParticipants();
534
+ * const byTeam = Map.groupBy(everyone, (p) => p.teamId);
535
+ * ```
536
+ */
537
+ getAllTeamsParticipants: () => Promise<Participant[]>;
538
+ /**
539
+ * Server-side endpoints declared by this UI's bundle. Each entry calls a
540
+ * named, author-written handler; the host validates the request against
541
+ * the endpoint's `requestSchema`, invokes the handler, and validates the
542
+ * response before returning it.
543
+ *
544
+ * Generated `types.ts` narrows this to a strongly-typed map of the
545
+ * endpoints the UI declared in its `endpoints` array.
546
+ *
547
+ * Example:
548
+ * ```typescript
549
+ * const { summary } = await callbacks.endpoints['com.example.summarise']({ text });
550
+ * ```
551
+ */
552
+ endpoints: Record<string, (request: unknown) => Promise<unknown>>;
553
+ /**
554
+ * Set the host's theme preference. The host applies the new preference,
555
+ * persists it, and broadcasts the change back to every UI, so the value
556
+ * returned by {@link useUserInterfaceThemePreference} updates everywhere.
557
+ * Pass `'system'` to follow the operating system.
558
+ *
559
+ * Read the current preference reactively with
560
+ * {@link useUserInterfaceThemePreference}; this callback only sets it.
561
+ *
562
+ * Example:
563
+ * ```typescript
564
+ * callbacks.setThemePreference('dark');
565
+ * ```
566
+ */
567
+ setThemePreference: (preference: ThemePreference) => void;
568
+ }
569
+ /**
570
+ * Callbacks available to waiting room user interface components.
571
+ *
572
+ * A waiting room UI is a game UI shown in a different display context, so it
573
+ * receives the player callback surface unchanged rather than a reduced one.
574
+ * The alias exists so generated `types.ts` files name the surface they consume.
575
+ */
576
+ export type WaitingRoomUserInterfaceCallbacks = UserInterfaceCallbacks;
577
+ /**
578
+ * Callbacks available to session user interface components.
579
+ *
580
+ * This is the game's whole-session facilitator surface, so the callbacks are
581
+ * session-level across every team rather than scoped to one. Game flow
582
+ * callbacks accept an optional `teamId` to target a specific team; omitting
583
+ * it applies the action to all teams (or is ignored in shared-state mode).
584
+ */
585
+ export interface SessionUserInterfaceCallbacks {
586
+ /**
587
+ * Upload a file as a blob. Returns a `blob:`-prefixed reference. The host
588
+ * assigns the blob's deletion lifecycle from the UI type (see
589
+ * `UserInterfaceCallbacks.uploadBlob`).
590
+ *
591
+ * Example:
592
+ * ```typescript
593
+ * const reference = await callbacks.uploadBlob(file);
594
+ * ```
595
+ */
596
+ uploadBlob: (file: File) => Promise<string>;
597
+ /**
598
+ * Resolve an asset reference to a URL. Accepts either an
599
+ * `asset:<path>` or `blob:<id>` reference.
600
+ *
601
+ * Example:
602
+ * ```typescript
603
+ * const url = callbacks.getAssetUrl("blob:abc123");
604
+ * ```
605
+ */
606
+ getAssetUrl: (reference: string) => string;
607
+ /**
608
+ * Read the bytes of an asset by reference.
609
+ *
610
+ * Example:
611
+ * ```typescript
612
+ * const file = await callbacks.readAsset("blob:abc123");
613
+ * ```
614
+ */
615
+ readAsset: (reference: string) => Promise<ReadAssetResponse>;
616
+ /**
617
+ * Read committed past state, the same call the player surface has, with
618
+ * two differences that follow from a session UI not being scoped to a
619
+ * team.
620
+ *
621
+ * `teams` defaults to `'all'` here rather than `'own'`, because a
622
+ * facilitator surface has no own team, and it accepts a `team-N` id to
623
+ * read one named team. And there is no `crossTeam` gate: a session UI
624
+ * already receives every team's declared context live through
625
+ * `SessionData`, so reading the same declared fields shifted in time
626
+ * grants it nothing it did not have. It is bounded by this surface's own
627
+ * declared `reads` and `writes`, and never returns a `private` capability
628
+ * (§6.1).
629
+ *
630
+ * Everything else matches: a list ordered by `tick` then stable team
631
+ * index, one entry per (tick, team) pair, unreviewable ticks omitted, an
632
+ * empty list for a range holding nothing, and a rejection only on a
633
+ * genuine fault.
634
+ *
635
+ * Example:
636
+ * ```typescript
637
+ * // a results table: rows are rounds, entries are teams
638
+ * const rows = await callbacks.getPastState<Context>({
639
+ * fromTick: 0,
640
+ * toTick: head,
641
+ * capabilityIds: ['profit'],
642
+ * });
643
+ *
644
+ * // one team's board at one past round
645
+ * const [board] = await callbacks.getPastState<Context>({
646
+ * fromTick: day,
647
+ * teams: 'team-1',
648
+ * });
649
+ * ```
650
+ */
651
+ getPastState: <T = Record<string, unknown>>(query: PastStateQuery<T>) => Promise<PastStateEntry<T>[]>;
652
+ /**
653
+ * Request a tick. Pass a `teamId` to tick a specific team (independent
654
+ * mode) or omit to tick all teams.
655
+ *
656
+ * Returns a promise that resolves once the tick(s) have finished and rejects
657
+ * with a `MutationError` when the tick cannot run. Safe to ignore.
658
+ *
659
+ * Example:
660
+ * ```typescript
661
+ * await callbacks.requestTick(); // all teams
662
+ * await callbacks.requestTick("team-1"); // specific team
663
+ * ```
664
+ */
665
+ requestTick: (teamId?: string) => Promise<void>;
666
+ /**
667
+ * Pause automatic ticking. Pass a `teamId` to pause a specific team
668
+ * or omit to pause all teams. Resolves once the host has applied it, and
669
+ * rejects with a `MutationError` when the host refuses it.
670
+ */
671
+ pauseTimer: (teamId?: string) => Promise<void>;
672
+ /**
673
+ * Resume automatic ticking after a pause. Pass a `teamId` to resume a
674
+ * specific team or omit to resume all teams. Resolves once the host has
675
+ * applied it, and rejects with a `MutationError` when the host refuses it.
676
+ */
677
+ resumeTimer: (teamId?: string) => Promise<void>;
678
+ /**
679
+ * Reset the game back to its initial state. Pass a `teamId` to reset a
680
+ * specific team or omit to reset the entire session. Returns a promise that
681
+ * resolves once the reset has completed and rejects with a `MutationError`
682
+ * on failure.
683
+ */
684
+ resetGame: (teamId?: string) => Promise<void>;
685
+ /**
686
+ * Undo the last tick. Pass a `teamId` to undo for a specific team or
687
+ * omit to undo for all teams. Returns a promise that resolves once the undo
688
+ * has completed and rejects with a `MutationError` on failure.
689
+ */
690
+ undoTick: (teamId?: string) => Promise<void>;
691
+ /**
692
+ * Move the deadline of the currently scheduled tick by `byMs`
693
+ * milliseconds. Positive buys the room more time; negative brings the deadline forward, clamped at
694
+ * now rather than firing retroactively. Pass a `teamId` to adjust one team
695
+ * or omit to adjust every team.
696
+ *
697
+ * This moves *this* tick only. The interval the tick controller returns for
698
+ * the ticks after it is untouched, so a session does not drift because a
699
+ * facilitator gave one round an extra two minutes. Read the result back
700
+ * from `useUserInterfaceTimer().extendedMs` rather than assuming the call
701
+ * won.
702
+ *
703
+ * Deliberately not bounded by the §11.8 tick floor and ceiling: those bound
704
+ * the cadence a tick controller may schedule, and a facilitator already
705
+ * reaches outside both with `requestTick` (immediate) and `pauseTimer`
706
+ * (indefinite).
707
+ *
708
+ * Rejects with a `MutationError` carrying `{ kind: 'denied' }` when there is
709
+ * no tick to move — automatic ticking is disabled, the game is complete, or
710
+ * the timer is paused — and when the host cannot schedule the result. A
711
+ * denial is terminal.
712
+ *
713
+ * Example:
714
+ * ```typescript
715
+ * await callbacks.extendTick(120_000); // two more minutes, every team
716
+ * await callbacks.extendTick(120_000, 'team-1'); // just this team
717
+ * await callbacks.extendTick(-30_000); // pull the deadline in 30 seconds
718
+ * ```
719
+ */
720
+ extendTick: (byMs: number, teamId?: string) => Promise<void>;
721
+ /**
722
+ * Ask the host to open one of its own overlays: the facilitator guide, a
723
+ * team's print sheet. The host renders it in its own chrome, above the
724
+ * surface, so this delegates the trigger and nothing else.
725
+ *
726
+ * `target` is the overlay's argument. A guide identifier for
727
+ * `'facilitator-guide'`, a team id for `'print-sheets'`; omitting it means
728
+ * the whole thing — the game's first declared guide, every team.
729
+ *
730
+ * Render a control only for an overlay in
731
+ * `useSessionUserInterfaceHostOverlays()`. Which overlays exist is the
732
+ * host's answer: a production host and the SDK's dev server do not offer
733
+ * the same set, and a button for one the host does not have is a button
734
+ * that cannot work.
735
+ *
736
+ * Rejects with a `MutationError` carrying `{ kind: 'denied' }` for an
737
+ * overlay this host does not offer, and for a `target` the active game does
738
+ * not declare. A denial is terminal.
739
+ *
740
+ * Example:
741
+ * ```typescript
742
+ * await callbacks.openHostOverlay('facilitator-guide');
743
+ * await callbacks.openHostOverlay('print-sheets', 'team-1');
744
+ * ```
745
+ */
746
+ openHostOverlay: (overlay: HostOverlay, target?: string) => Promise<void>;
747
+ /**
748
+ * Open or close the session's waiting room. While it is open, every player
749
+ * sees the game's waiting room UI instead of its game UIs; closing it swaps
750
+ * them back with no reload. Game state is untouched in both directions —
751
+ * per-team context, current tick, and timers all survive the toggle, which
752
+ * is the point: a facilitator can park a live session for a discussion and
753
+ * resume exactly where teams were.
754
+ *
755
+ * Alone among the session callbacks it takes no `teamId`: the waiting room
756
+ * is one flag for the whole session, so there is no per-team variant to
757
+ * target. Opening it does not stop the tick clock (see
758
+ * `TickControllerFunctionArgs.waitingRoomOpen` for how to do that).
759
+ *
760
+ * The host usually renders a waiting-room control in its own chrome too, so
761
+ * both writers exist by design. Read the resulting flag back from
762
+ * `useSessionUserInterfaceMeta().waitingRoomOpen` rather than assuming this
763
+ * call won.
764
+ *
765
+ * Example:
766
+ * ```typescript
767
+ * await callbacks.setWaitingRoom(true); // park the session
768
+ * ```
769
+ */
770
+ setWaitingRoom: (open: boolean) => Promise<void>;
771
+ /**
772
+ * Throw the walkthrough away and start it again at tick 0 (§5.7).
773
+ *
774
+ * The walkthrough is a disposable second run of the game, used to
775
+ * demonstrate it: one team, one participant, isolated from this session in
776
+ * both directions. Resetting it is how a facilitator clears the decisions
777
+ * they entered while talking the room through, and since it always begins
778
+ * at tick 0 there is nothing else to undo.
779
+ *
780
+ * Rejects with a `MutationError` carrying `{ kind: 'denied' }` while the
781
+ * session has no walkthrough yet, since opening its link is what creates
782
+ * one and there is nothing to reset before then.
783
+ *
784
+ * Example:
785
+ * ```typescript
786
+ * await callbacks.resetWalkthrough();
787
+ * ```
788
+ */
789
+ resetWalkthrough: () => Promise<void>;
790
+ /**
791
+ * Add a team to the session. The host assigns the lowest unused team index
792
+ * and resolves the default name for that index when `name` is omitted.
793
+ *
794
+ * Rejects with a `MutationError` carrying `{ kind: 'denied' }` when the
795
+ * session is already at its maximum team count, when the session is
796
+ * shared-state (whose team count is fixed at creation), or when the
797
+ * session is read-only. The SDK's dev server relaxes the shared-state rule
798
+ * before the first tick as an authoring convenience; do not rely on it.
799
+ * A denial is terminal: retrying cannot succeed. Read
800
+ * `useSessionUserInterfaceTeamConfiguration()` to disable the control before
801
+ * the facilitator reaches it.
802
+ *
803
+ * Example:
804
+ * ```typescript
805
+ * await callbacks.createTeam('Marketing');
806
+ * ```
807
+ */
808
+ createTeam: (name?: string) => Promise<void>;
809
+ /**
810
+ * Rename a team. The new name reaches every surface through the team list.
811
+ *
812
+ * Example:
813
+ * ```typescript
814
+ * await callbacks.renameTeam('team-1', 'Marketing');
815
+ * ```
816
+ */
817
+ renameTeam: (teamId: string, name: string) => Promise<void>;
818
+ /**
819
+ * Rename a participant. The new name reaches every surface through the
820
+ * participant list.
821
+ *
822
+ * Example:
823
+ * ```typescript
824
+ * await callbacks.renameParticipant('participant-3', 'Sam');
825
+ * ```
826
+ */
827
+ renameParticipant: (participantId: string, name: string) => Promise<void>;
828
+ /**
829
+ * Reassign a participant to another team.
830
+ *
831
+ * The participant keeps their id but takes a fresh index within the
832
+ * destination team, and their open surfaces are remounted against that team
833
+ * (§13.2). Two consequences, which are easy to confuse:
834
+ *
835
+ * - **Their browser loses local component state**, because the surface
836
+ * re-renders from scratch: a half-typed field, an uncommitted slider.
837
+ * Ordinary remount behaviour, and rarely worth warning anyone about.
838
+ * - **Their earlier writes stay with the source team.** Anything they wrote
839
+ * through `setData` is in that team's context and does not travel, so
840
+ * they arrive with none of their own state and the source team keeps work
841
+ * done by someone no longer on it.
842
+ *
843
+ * The second is what a game has to handle, and the mechanism is the
844
+ * `participantMove` hook: seed the destination on `phase: 'arrival'` the
845
+ * way `participantJoin` seeds a new participant. Note there is no transfer
846
+ * channel — a hook returns one team's context, so the departure hook's
847
+ * return value is invisible to arrival. A shared-state game carries state
848
+ * across through the shared store; an independent-mode game re-derives it
849
+ * from the payload and its own configuration. Where it genuinely cannot be
850
+ * re-derived, that is the case worth telling a facilitator about.
851
+ *
852
+ * A move does not reset either team: the source team's context, including
853
+ * every pending pre-tick decision in it, is untouched.
854
+ *
855
+ * Any write of theirs already in flight against the old team fails as
856
+ * denied rather than landing on either team.
857
+ *
858
+ * Example:
859
+ * ```typescript
860
+ * await callbacks.moveParticipant('participant-3', 'team-2');
861
+ * ```
862
+ */
863
+ moveParticipant: (participantId: string, teamId: string) => Promise<void>;
864
+ /**
865
+ * Delete a team from an independent-mode session. No other team's index or
866
+ * id changes, and the deleted team's index is left as a gap that the next
867
+ * `createTeam` takes back as a new team.
868
+ *
869
+ * Only an empty team can go: move every participant out with
870
+ * `moveParticipant` first, connected or not, because a participant is
871
+ * never erased. Rejects with a `MutationError` carrying `{ kind: 'denied' }`
872
+ * while the team has participants, in a shared-state session (whose
873
+ * cohort is fixed for the session), for the last team, for a team the
874
+ * session does not hold, and when the session is read-only. A denial is
875
+ * terminal: retrying cannot succeed. Read
876
+ * `useSessionUserInterfaceTeamConfiguration().mode` to offer the control
877
+ * only in independent mode.
878
+ *
879
+ * Example:
880
+ * ```typescript
881
+ * await callbacks.deleteTeam('team-3');
882
+ * ```
883
+ */
884
+ deleteTeam: (teamId: string) => Promise<void>;
885
+ /**
886
+ * Server-side endpoints declared by this UI's bundle. See
887
+ * {@link UserInterfaceCallbacks.endpoints} for details.
888
+ */
889
+ endpoints: Record<string, (request: unknown) => Promise<unknown>>;
890
+ /**
891
+ * Set the host's theme preference. See
892
+ * {@link UserInterfaceCallbacks.setThemePreference} for details. Read the
893
+ * current value reactively with the matching theme-preference hook.
894
+ */
895
+ setThemePreference: (preference: ThemePreference) => void;
896
+ }
897
+ /**
898
+ * Error thrown when a resource invokes a callback it did not declare in its
899
+ * bundle's `uses` array. Carries the offending resource identifier and the
900
+ * callback name so a runtime can surface both to the operator.
901
+ */
902
+ export declare class UndeclaredApiUseError extends Error {
903
+ readonly resourceIdentifier: string;
904
+ readonly api: ApiUse;
905
+ constructor(resourceIdentifier: string, api: ApiUse);
906
+ }
907
+ /**
908
+ * The {@link SignatureCallbacks} members that `uses` does not gate (§6.11).
909
+ *
910
+ * Runtime plumbing, and reads of state the resource already declared, rather
911
+ * than externally-visible APIs an operator gates access to. They are always
912
+ * present, so a generated `Callbacks` type has to carry them: without this the
913
+ * runtime passed them and no author could see them, which is how `readAsset`
914
+ * came to be reachable in principle and used nowhere.
915
+ */
916
+ export type UngatedSignatureCallbacks = Pick<SignatureCallbacks, 'readAsset' | 'getPastState'>;
917
+ /**
918
+ * The callbacks a logic-bearing resource actually receives: the gated ones it
919
+ * declared in `uses`, plus the ungated ones it gets regardless.
920
+ *
921
+ * Every declared service is required here, never optional: a runtime that
922
+ * cannot provide one refuses the package at load (§6.11), so a resource that
923
+ * declared `generateText` calls `callbacks.generateText(...)` directly. A
924
+ * service it did not declare is not in the type at all.
925
+ *
926
+ * This is what each resource's generated `types.ts` aliases its `Callbacks` to.
927
+ * It exists as a named helper rather than being spelled out as an intersection
928
+ * at every call site because the spelled-out form is what an author reads first
929
+ * when asking what their handler is handed, and 'a Pick intersected with a
930
+ * Required Pick' answers that question badly.
931
+ */
932
+ export type LogicCallbacks<Uses extends ApiUse = never> = Required<Pick<SignatureCallbacks, Uses>> & UngatedSignatureCallbacks;
933
+ /**
934
+ * Callbacks available to a settings editor: the `setup` and `scenario_editor`
935
+ * slots, which share one wrapper (§6.5).
936
+ */
937
+ export interface SettingsEditorCallbacks {
938
+ /**
939
+ * Upload a file as a blob. Returns a `blob:`-prefixed reference that can
940
+ * be stored in a setting and later resolved via `getAssetUrl` or
941
+ * `readAsset`. Uploads from a settings editor survive game resets and are
942
+ * wiped only when the session is re-initialised; the host assigns this
943
+ * lifecycle from the UI type.
944
+ *
945
+ * Example:
946
+ * ```typescript
947
+ * const reference = await callbacks.uploadBlob(file);
948
+ * ```
949
+ */
950
+ uploadBlob: (file: File) => Promise<string>;
951
+ /**
952
+ * Resolve an asset reference to a URL. Accepts either an
953
+ * `asset:<path>` or `blob:<id>` reference.
954
+ *
955
+ * Example:
956
+ * ```typescript
957
+ * const url = callbacks.getAssetUrl("blob:abc123");
958
+ * // <img src={url} />
959
+ * ```
960
+ */
961
+ getAssetUrl: (reference: string) => string;
962
+ /**
963
+ * Read the bytes of an asset by reference.
964
+ *
965
+ * Example:
966
+ * ```typescript
967
+ * const file = await callbacks.readAsset("blob:abc123");
968
+ * ```
969
+ */
970
+ readAsset: (reference: string) => Promise<ReadAssetResponse>;
971
+ /**
972
+ * Request the game to leave the configuration step and start play.
973
+ * Equivalent to the operator clicking the host-side Play button, which is
974
+ * always rendered (SPECIFICATION.md §8.3). Send this from a finish control
975
+ * of your own when the form wants one; it is never the only way out, so a
976
+ * bug here cannot strand a facilitator on the configuration step.
977
+ *
978
+ * Meaningful in the `setup` slot only. A scenario editor has no session
979
+ * to start, so the host refuses it from there (§6.5).
980
+ *
981
+ * Example:
982
+ * ```typescript
983
+ * callbacks.requestPlay();
984
+ * ```
985
+ */
986
+ requestPlay: () => void;
987
+ /**
988
+ * Server-side endpoints declared by this UI's bundle. See
989
+ * {@link UserInterfaceCallbacks.endpoints} for details.
990
+ */
991
+ endpoints: Record<string, (request: unknown) => Promise<unknown>>;
992
+ /**
993
+ * Set the host's theme preference. See
994
+ * {@link UserInterfaceCallbacks.setThemePreference} for details. Read the
995
+ * current value reactively with the matching theme-preference hook.
996
+ */
997
+ setThemePreference: (preference: ThemePreference) => void;
998
+ }