@playcademy/sdk 0.16.1-beta.10 → 0.16.1-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -144,6 +144,20 @@ review. Save payloads merge at both the item and response levels; omitted values
144
144
  and `null` clears one response. Keep the returned `responseVersion` and send it with the next
145
145
  mutation.
146
146
 
147
+ The returned `flow` is authoritative and is derived from `purpose`; callers do not configure
148
+ navigation, feedback, and submission independently:
149
+
150
+ - `attempt-submit` is used for diagnostic and end-of-course attempts. Responses remain editable
151
+ through `save()` until `submit()` finalizes the whole attempt and returns feedback.
152
+ - `item-submit` is used for review and mastery attempts. Submit each item in assessment order with
153
+ `submitItem()`. That operation atomically saves the item's responses, locks them, and returns safe
154
+ immediate feedback. Give each item request its own stable `submissionId` and reuse that ID for an
155
+ ambiguous retry; the returned canonical `itemSubmissions` ledger identifies committed items after
156
+ retry or resume.
157
+
158
+ The host rejects `submitItem()` for attempt-submit flows, rejects `submit()` until every item-submit
159
+ item is committed, and enforces `expectedResponseVersion` for every mutation.
160
+
147
161
  For local development, import the current ordered catalog before starting the dev host:
148
162
 
149
163
  ```bash
package/dist/index.d.ts CHANGED
@@ -1559,6 +1559,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1559
1559
  latest: (options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
1560
1560
  get: (attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
1561
1561
  save: (attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
1562
+ submitItem: (attemptId: string, input: _playcademy_types.SubmitAssessmentItemInput) => Promise<_playcademy_types.SubmitAssessmentItemResult>;
1562
1563
  submit: (attemptId: string, input: _playcademy_types.SubmitAssessmentInput) => Promise<_playcademy_types.AssessmentSubmitResult>;
1563
1564
  };
1564
1565
  readonly user: TimebackUser;
@@ -1910,6 +1911,15 @@ type EmbedActivity = EmbedActivityCompleted | EmbedActivityAbandoned | EmbedActi
1910
1911
  /** The child called `endActivity()` and its report was relayed. */
1911
1912
  interface EmbedActivityCompleted {
1912
1913
  status: 'completed';
1914
+ /**
1915
+ * The platform run this completion records under. One run ends in at
1916
+ * most one completion (the server dedupes on it), and a resumed launch
1917
+ * keeps the interrupted run's id — so this doubles as the completion's
1918
+ * attempt identity: feed it to whatever consumes the result and drop
1919
+ * anything you have seen before. Absent only for pure UX embeds
1920
+ * (launched without `timeback`), which record nothing.
1921
+ */
1922
+ runId?: string;
1913
1923
  /** Correct answers, from the child's report. */
1914
1924
  correct: number;
1915
1925
  /** Total questions, from the child's report. */
@@ -1938,6 +1948,12 @@ interface EmbedActivityCompleted {
1938
1948
  */
1939
1949
  interface EmbedActivityAbandoned {
1940
1950
  status: 'abandoned';
1951
+ /**
1952
+ * The platform run the launch was recording under, when one had
1953
+ * opened. Absent when the child never started an activity or the
1954
+ * launch was a pure UX embed.
1955
+ */
1956
+ runId?: string;
1941
1957
  timing: EmbedSessionTiming;
1942
1958
  /**
1943
1959
  * The final resume envelope, when the child checkpointed during the
@@ -1959,6 +1975,13 @@ interface EmbedActivityFailed {
1959
1975
  interface EmbedSession {
1960
1976
  /** The mounted child iframe. Useful for focus management. */
1961
1977
  readonly iframe: HTMLIFrameElement;
1978
+ /**
1979
+ * The platform run this launch records under, or null before the run
1980
+ * opens (the child's first activity) and for pure UX embeds. Stable
1981
+ * once set; also echoed on the finished record, which is where most
1982
+ * callers should read it.
1983
+ */
1984
+ readonly runId: string | null;
1962
1985
  /**
1963
1986
  * The latest resume envelope, live during play; null until the child
1964
1987
  * first checkpoints. Read it on your own cadence to persist
package/dist/index.js CHANGED
@@ -1119,6 +1119,9 @@ class ChildSession {
1119
1119
  this.#abandon();
1120
1120
  this.#teardown();
1121
1121
  }
1122
+ get runId() {
1123
+ return this.#parentRunId;
1124
+ }
1122
1125
  get checkpoint() {
1123
1126
  if (!this.#hasCheckpoint) {
1124
1127
  return null;
@@ -1227,6 +1230,9 @@ class ChildSession {
1227
1230
  status: "abandoned",
1228
1231
  timing: this.#relayedTiming()
1229
1232
  };
1233
+ if (this.#parentRunId) {
1234
+ activity.runId = this.#parentRunId;
1235
+ }
1230
1236
  const resume = this.checkpoint;
1231
1237
  if (resume) {
1232
1238
  activity.resume = resume;
@@ -1251,9 +1257,11 @@ class ChildSession {
1251
1257
  }
1252
1258
  this.#settled = true;
1253
1259
  this.#clearResume();
1260
+ this.#ensureBridgeRun();
1254
1261
  let endMemo = null;
1255
1262
  this.#resolveFinished({
1256
1263
  status: "completed",
1264
+ runId: this.#parentRunId ?? undefined,
1257
1265
  correct: childReport.scoreData.correctQuestions,
1258
1266
  total: childReport.scoreData.totalQuestions,
1259
1267
  timing: this.#completedTiming(childReport),
@@ -3042,6 +3050,22 @@ function createTimebackNamespace(client) {
3042
3050
  }
3043
3051
  return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/save`, "POST", input);
3044
3052
  },
3053
+ submitItem: async (attemptId, input) => {
3054
+ assertPlatformMode(client, "timeback.assessments.submitItem()");
3055
+ if (!attemptId?.trim()) {
3056
+ throw new Error("attemptId is required");
3057
+ }
3058
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
3059
+ throw new Error("expectedResponseVersion must be a non-negative integer");
3060
+ }
3061
+ if (!input.submissionId?.trim()) {
3062
+ throw new Error("submissionId is required");
3063
+ }
3064
+ if (!input.itemIdentifier?.trim()) {
3065
+ throw new Error("itemIdentifier is required");
3066
+ }
3067
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/submit-item`, "POST", input);
3068
+ },
3045
3069
  submit: async (attemptId, input) => {
3046
3070
  assertPlatformMode(client, "timeback.assessments.submit()");
3047
3071
  if (!attemptId?.trim()) {
@@ -3460,7 +3484,7 @@ async function request({
3460
3484
  return rawText && rawText.length > 0 ? rawText : undefined;
3461
3485
  }
3462
3486
  // src/version.ts
3463
- var SDK_VERSION = "0.16.1-beta.10";
3487
+ var SDK_VERSION = "0.16.1-beta.12";
3464
3488
 
3465
3489
  // src/clients/base.ts
3466
3490
  class PlaycademyBaseClient {
@@ -1,7 +1,7 @@
1
1
  import { SchemaInfo } from '@playcademy/cloudflare';
2
2
  import { GamePermission, AUTH_PROVIDER_IDS } from '@playcademy/constants';
3
3
  import { TimebackGrade, TimebackSubject, ELevel, HeartbeatRequest, EndActivityRequest, EndActivityScoreData, EndActivityResponse, TimebackCourseConfig, CourseConfig, OrganizationConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig } from '@playcademy/types/timeback';
4
- export { ELevel } from '@playcademy/types/timeback';
4
+ export { AssessmentAttemptSnapshot, AssessmentFlow, AssessmentItemSubmission, AssessmentResponseUpdate, AssessmentResponseValue, AssessmentResponses, AssessmentSaveResult, AssessmentScore, AssessmentStandardRef, AssessmentSubmitResult, ELevel, PlayableAssessment, PlayableAssessmentChoice, PlayableAssessmentGraphic, PlayableAssessmentHotspot, PlayableAssessmentInteraction, PlayableAssessmentItem, PlayableContentNode, SaveAssessmentInput, StartAssessmentInput, SubmitAssessmentInput, SubmitAssessmentItemInput, SubmitAssessmentItemResult } from '@playcademy/types/timeback';
5
5
  import * as _playcademy_types from '@playcademy/types';
6
6
  import { GameManifest, LocalDayContext } from '@playcademy/types';
7
7
  export { AuthenticatedUser, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, GameCourseMetrics, GameLeaderboardEntry, GameManifest, GameMetricComparisonKind, GameMetricComparisonMetric, GameMetricComparisonRow, GameMetricComparisonRowStatus, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRunMetrics, GameRunMetricsComparison, GameRunMetricsComparisonStatus, GameRunMetricsComparisonSummary, GameTimebackIntegration, GameType, GameUser, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LocalDayContext, LocalDaySource, ManifestV1, ManifestV2, ManifestVersions, PopulateStudentResponse, UserEnrollment, UserInfo, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData } from '@playcademy/types';
@@ -2977,6 +2977,15 @@ type EmbedActivity = EmbedActivityCompleted | EmbedActivityAbandoned | EmbedActi
2977
2977
  /** The child called `endActivity()` and its report was relayed. */
2978
2978
  interface EmbedActivityCompleted {
2979
2979
  status: 'completed';
2980
+ /**
2981
+ * The platform run this completion records under. One run ends in at
2982
+ * most one completion (the server dedupes on it), and a resumed launch
2983
+ * keeps the interrupted run's id — so this doubles as the completion's
2984
+ * attempt identity: feed it to whatever consumes the result and drop
2985
+ * anything you have seen before. Absent only for pure UX embeds
2986
+ * (launched without `timeback`), which record nothing.
2987
+ */
2988
+ runId?: string;
2980
2989
  /** Correct answers, from the child's report. */
2981
2990
  correct: number;
2982
2991
  /** Total questions, from the child's report. */
@@ -3005,6 +3014,12 @@ interface EmbedActivityCompleted {
3005
3014
  */
3006
3015
  interface EmbedActivityAbandoned {
3007
3016
  status: 'abandoned';
3017
+ /**
3018
+ * The platform run the launch was recording under, when one had
3019
+ * opened. Absent when the child never started an activity or the
3020
+ * launch was a pure UX embed.
3021
+ */
3022
+ runId?: string;
3008
3023
  timing: EmbedSessionTiming;
3009
3024
  /**
3010
3025
  * The final resume envelope, when the child checkpointed during the
@@ -3026,6 +3041,13 @@ interface EmbedActivityFailed {
3026
3041
  interface EmbedSession {
3027
3042
  /** The mounted child iframe. Useful for focus management. */
3028
3043
  readonly iframe: HTMLIFrameElement;
3044
+ /**
3045
+ * The platform run this launch records under, or null before the run
3046
+ * opens (the child's first activity) and for pure UX embeds. Stable
3047
+ * once set; also echoed on the finished record, which is where most
3048
+ * callers should read it.
3049
+ */
3050
+ readonly runId: string | null;
3029
3051
  /**
3030
3052
  * The latest resume envelope, live during play; null until the child
3031
3053
  * first checkpoints. Read it on your own cadence to persist
package/dist/internal.js CHANGED
@@ -1119,6 +1119,9 @@ class ChildSession {
1119
1119
  this.#abandon();
1120
1120
  this.#teardown();
1121
1121
  }
1122
+ get runId() {
1123
+ return this.#parentRunId;
1124
+ }
1122
1125
  get checkpoint() {
1123
1126
  if (!this.#hasCheckpoint) {
1124
1127
  return null;
@@ -1227,6 +1230,9 @@ class ChildSession {
1227
1230
  status: "abandoned",
1228
1231
  timing: this.#relayedTiming()
1229
1232
  };
1233
+ if (this.#parentRunId) {
1234
+ activity.runId = this.#parentRunId;
1235
+ }
1230
1236
  const resume = this.checkpoint;
1231
1237
  if (resume) {
1232
1238
  activity.resume = resume;
@@ -1251,9 +1257,11 @@ class ChildSession {
1251
1257
  }
1252
1258
  this.#settled = true;
1253
1259
  this.#clearResume();
1260
+ this.#ensureBridgeRun();
1254
1261
  let endMemo = null;
1255
1262
  this.#resolveFinished({
1256
1263
  status: "completed",
1264
+ runId: this.#parentRunId ?? undefined,
1257
1265
  correct: childReport.scoreData.correctQuestions,
1258
1266
  total: childReport.scoreData.totalQuestions,
1259
1267
  timing: this.#completedTiming(childReport),
@@ -3042,6 +3050,22 @@ function createTimebackNamespace(client) {
3042
3050
  }
3043
3051
  return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/save`, "POST", input);
3044
3052
  },
3053
+ submitItem: async (attemptId, input) => {
3054
+ assertPlatformMode(client, "timeback.assessments.submitItem()");
3055
+ if (!attemptId?.trim()) {
3056
+ throw new Error("attemptId is required");
3057
+ }
3058
+ if (!Number.isInteger(input.expectedResponseVersion) || input.expectedResponseVersion < 0) {
3059
+ throw new Error("expectedResponseVersion must be a non-negative integer");
3060
+ }
3061
+ if (!input.submissionId?.trim()) {
3062
+ throw new Error("submissionId is required");
3063
+ }
3064
+ if (!input.itemIdentifier?.trim()) {
3065
+ throw new Error("itemIdentifier is required");
3066
+ }
3067
+ return client["requestGameBackend"](`${ASSESSMENTS_ROUTE}/${encodeURIComponent(attemptId)}/submit-item`, "POST", input);
3068
+ },
3045
3069
  submit: async (attemptId, input) => {
3046
3070
  assertPlatformMode(client, "timeback.assessments.submit()");
3047
3071
  if (!attemptId?.trim()) {
@@ -4336,7 +4360,7 @@ async function request({
4336
4360
  return rawText && rawText.length > 0 ? rawText : undefined;
4337
4361
  }
4338
4362
  // src/version.ts
4339
- var SDK_VERSION = "0.16.1-beta.10";
4363
+ var SDK_VERSION = "0.16.1-beta.12";
4340
4364
 
4341
4365
  // src/clients/base.ts
4342
4366
  class PlaycademyBaseClient {
@@ -359,6 +359,7 @@ declare class PlaycademyClient {
359
359
  latest: (studentId: string, options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
360
360
  get: (studentId: string, attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
361
361
  save: (studentId: string, attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
362
+ submitItem: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentItemInput) => Promise<_playcademy_types.SubmitAssessmentItemResult>;
362
363
  submit: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentInput, context: {
363
364
  sensorUrl: string;
364
365
  }) => Promise<_playcademy_types.AssessmentSubmitResult>;
@@ -108,6 +108,7 @@ function createTimebackNamespace(client) {
108
108
  return client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}?${params.toString()}`, "GET");
109
109
  },
110
110
  save: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/save`, "POST", { ...input, gameId: client.gameId, studentId }),
111
+ submitItem: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit-item`, "POST", { ...input, gameId: client.gameId, studentId }),
111
112
  submit: (studentId, attemptId, input, context) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit`, "POST", {
112
113
  ...input,
113
114
  gameId: client.gameId,
@@ -312,7 +313,7 @@ function extractApiErrorInfo(error) {
312
313
  }
313
314
 
314
315
  // src/version.ts
315
- var SDK_VERSION = "0.16.1-beta.10";
316
+ var SDK_VERSION = "0.16.1-beta.12";
316
317
 
317
318
  // src/server/request.ts
318
319
  async function makeApiRequest(opts) {
package/dist/server.d.ts CHANGED
@@ -359,6 +359,7 @@ declare class PlaycademyClient$1 {
359
359
  latest: (studentId: string, options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
360
360
  get: (studentId: string, attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
361
361
  save: (studentId: string, attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
362
+ submitItem: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentItemInput) => Promise<_playcademy_types.SubmitAssessmentItemResult>;
362
363
  submit: (studentId: string, attemptId: string, input: _playcademy_types.SubmitAssessmentInput, context: {
363
364
  sensorUrl: string;
364
365
  }) => Promise<_playcademy_types.AssessmentSubmitResult>;
package/dist/server.js CHANGED
@@ -297,6 +297,7 @@ function createTimebackNamespace(client) {
297
297
  return client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}?${params.toString()}`, "GET");
298
298
  },
299
299
  save: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/save`, "POST", { ...input, gameId: client.gameId, studentId }),
300
+ submitItem: (studentId, attemptId, input) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit-item`, "POST", { ...input, gameId: client.gameId, studentId }),
300
301
  submit: (studentId, attemptId, input, context) => client["request"](`/api/timeback/assessments/${encodeURIComponent(attemptId)}/submit`, "POST", {
301
302
  ...input,
302
303
  gameId: client.gameId,
@@ -501,7 +502,7 @@ function extractApiErrorInfo(error) {
501
502
  }
502
503
 
503
504
  // src/version.ts
504
- var SDK_VERSION = "0.16.1-beta.10";
505
+ var SDK_VERSION = "0.16.1-beta.12";
505
506
 
506
507
  // src/server/request.ts
507
508
  async function makeApiRequest(opts) {
package/dist/types.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as _playcademy_types from '@playcademy/types';
2
2
  import { GameManifest, LocalDayContext } from '@playcademy/types';
3
3
  export { AuthenticatedUser, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, GameCourseMetrics, GameLeaderboardEntry, GameManifest, GameMetricComparisonKind, GameMetricComparisonMetric, GameMetricComparisonRow, GameMetricComparisonRowStatus, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRunMetrics, GameRunMetricsComparison, GameRunMetricsComparisonStatus, GameRunMetricsComparisonSummary, GameTimebackIntegration, GameType, GameUser, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LocalDayContext, LocalDaySource, ManifestV1, ManifestV2, ManifestVersions, PopulateStudentResponse, UserEnrollment, UserInfo, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData } from '@playcademy/types';
4
4
  import { TimebackCourseConfig, CourseConfig, OrganizationConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig, TimebackGrade, TimebackSubject, ELevel, EndActivityRequest, HeartbeatRequest, EndActivityScoreData, EndActivityResponse } from '@playcademy/types/timeback';
5
- export { ELevel } from '@playcademy/types/timeback';
5
+ export { AssessmentAttemptSnapshot, AssessmentFlow, AssessmentItemSubmission, AssessmentResponseUpdate, AssessmentResponseValue, AssessmentResponses, AssessmentSaveResult, AssessmentScore, AssessmentStandardRef, AssessmentSubmitResult, ELevel, PlayableAssessment, PlayableAssessmentChoice, PlayableAssessmentGraphic, PlayableAssessmentHotspot, PlayableAssessmentInteraction, PlayableAssessmentItem, PlayableContentNode, SaveAssessmentInput, StartAssessmentInput, SubmitAssessmentInput, SubmitAssessmentItemInput, SubmitAssessmentItemResult } from '@playcademy/types/timeback';
6
6
  import { TimebackUserRole, UserEnrollment, UserOrganization, UserInfo } from '@playcademy/types/user';
7
7
  import { GamePermission, AUTH_PROVIDER_IDS } from '@playcademy/constants';
8
8
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
@@ -2000,6 +2000,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
2000
2000
  latest: (options: _playcademy_types.GetLatestAssessmentOptions) => Promise<_playcademy_types.LatestAssessmentResult | null>;
2001
2001
  get: (attemptId: string) => Promise<_playcademy_types.AssessmentAttemptSnapshot>;
2002
2002
  save: (attemptId: string, input: _playcademy_types.SaveAssessmentInput) => Promise<_playcademy_types.AssessmentSaveResult>;
2003
+ submitItem: (attemptId: string, input: _playcademy_types.SubmitAssessmentItemInput) => Promise<_playcademy_types.SubmitAssessmentItemResult>;
2003
2004
  submit: (attemptId: string, input: _playcademy_types.SubmitAssessmentInput) => Promise<_playcademy_types.AssessmentSubmitResult>;
2004
2005
  };
2005
2006
  readonly user: TimebackUser;
@@ -2339,6 +2340,15 @@ type EmbedActivity = EmbedActivityCompleted | EmbedActivityAbandoned | EmbedActi
2339
2340
  /** The child called `endActivity()` and its report was relayed. */
2340
2341
  interface EmbedActivityCompleted {
2341
2342
  status: 'completed';
2343
+ /**
2344
+ * The platform run this completion records under. One run ends in at
2345
+ * most one completion (the server dedupes on it), and a resumed launch
2346
+ * keeps the interrupted run's id — so this doubles as the completion's
2347
+ * attempt identity: feed it to whatever consumes the result and drop
2348
+ * anything you have seen before. Absent only for pure UX embeds
2349
+ * (launched without `timeback`), which record nothing.
2350
+ */
2351
+ runId?: string;
2342
2352
  /** Correct answers, from the child's report. */
2343
2353
  correct: number;
2344
2354
  /** Total questions, from the child's report. */
@@ -2367,6 +2377,12 @@ interface EmbedActivityCompleted {
2367
2377
  */
2368
2378
  interface EmbedActivityAbandoned {
2369
2379
  status: 'abandoned';
2380
+ /**
2381
+ * The platform run the launch was recording under, when one had
2382
+ * opened. Absent when the child never started an activity or the
2383
+ * launch was a pure UX embed.
2384
+ */
2385
+ runId?: string;
2370
2386
  timing: EmbedSessionTiming;
2371
2387
  /**
2372
2388
  * The final resume envelope, when the child checkpointed during the
@@ -2388,6 +2404,13 @@ interface EmbedActivityFailed {
2388
2404
  interface EmbedSession {
2389
2405
  /** The mounted child iframe. Useful for focus management. */
2390
2406
  readonly iframe: HTMLIFrameElement;
2407
+ /**
2408
+ * The platform run this launch records under, or null before the run
2409
+ * opens (the child's first activity) and for pure UX embeds. Stable
2410
+ * once set; also echoed on the finished record, which is where most
2411
+ * callers should read it.
2412
+ */
2413
+ readonly runId: string | null;
2391
2414
  /**
2392
2415
  * The latest resume envelope, live during play; null until the child
2393
2416
  * first checkpoints. Read it on your own cadence to persist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.16.1-beta.10",
3
+ "version": "0.16.1-beta.12",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {