@pie-players/pie-assessment-toolkit 0.3.75 → 1.0.0-beta.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 (142) hide show
  1. package/README.md +449 -541
  2. package/custom-elements.json +1315 -0
  3. package/dist/attempt/TestSession.d.ts +0 -1
  4. package/dist/attempt/TestSession.js +10 -5
  5. package/dist/components/ItemScope.custom-element.js +1 -1
  6. package/dist/components/ItemToolBar.custom-element.js +1 -1
  7. package/dist/components/PieAssessmentToolkit.custom-element.js +8 -8
  8. package/dist/components/SectionToolBar.custom-element.js +1 -1
  9. package/dist/components/chunks/{ItemToolBar-7tt7v2xp.js → ItemToolBar-3zrc4p3d.js} +4 -4
  10. package/dist/components/chunks/{ItemToolBar-jx38w99s.js → ItemToolBar-66hcwmfw.js} +2 -2
  11. package/dist/components/chunks/ItemToolBar-9gqhfxtn.js +40 -0
  12. package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
  13. package/dist/components/chunks/{ItemToolBar-97hx60z4.js → ItemToolBar-kkj69ptr.js} +1 -1
  14. package/dist/components/chunks/sre-engine-bj997w4b.js +1 -0
  15. package/dist/context/assessment-toolkit-context.d.ts +6 -0
  16. package/dist/index.d.ts +7 -7
  17. package/dist/index.js +4 -10
  18. package/dist/policy/core/PolicySource.d.ts +1 -1
  19. package/dist/policy/core/ToolPolicyEngine.d.ts +46 -7
  20. package/dist/policy/core/ToolPolicyEngine.js +73 -16
  21. package/dist/policy/core/compose-decision.d.ts +3 -3
  22. package/dist/policy/core/compose-decision.js +30 -30
  23. package/dist/policy/core/decision-types.d.ts +44 -32
  24. package/dist/policy/core/feature-decision.d.ts +17 -15
  25. package/dist/policy/core/feature-decision.js +11 -13
  26. package/dist/policy/core/pnp-policy-inputs.d.ts +5 -2
  27. package/dist/policy/core/pnp-policy-inputs.js +12 -32
  28. package/dist/policy/core/provenance.d.ts +1 -1
  29. package/dist/policy/core/tool-parameters.d.ts +11 -0
  30. package/dist/policy/core/tool-parameters.js +15 -0
  31. package/dist/policy/engine.d.ts +1 -1
  32. package/dist/policy/sources/PnpPolicySource.d.ts +14 -22
  33. package/dist/policy/sources/PnpPolicySource.js +30 -28
  34. package/dist/runtime/SectionControllerBinding.d.ts +6 -2
  35. package/dist/runtime/SectionControllerBinding.js +21 -15
  36. package/dist/runtime/adapter/subscriber-fanout.d.ts +0 -1
  37. package/dist/runtime/adapter/subscriber-fanout.js +1 -4
  38. package/dist/runtime/composition-emit-scheduler.d.ts +3 -3
  39. package/dist/runtime/composition-emit-scheduler.js +1 -1
  40. package/dist/runtime/content-region.d.ts +0 -4
  41. package/dist/runtime/content-region.js +2 -2
  42. package/dist/runtime/core/SectionEngineCore.d.ts +0 -2
  43. package/dist/runtime/core/SectionEngineCore.js +0 -4
  44. package/dist/runtime/core/cohort.d.ts +0 -5
  45. package/dist/runtime/core/cohort.js +0 -9
  46. package/dist/runtime/core/engine-output.d.ts +3 -2
  47. package/dist/runtime/core/engine-transition.js +3 -2
  48. package/dist/runtime/registration-events.d.ts +11 -14
  49. package/dist/runtime/runtime-id.d.ts +1 -1
  50. package/dist/runtime/runtime-id.js +1 -1
  51. package/dist/runtime/shell-scope.d.ts +4 -4
  52. package/dist/runtime/shell-scope.js +15 -9
  53. package/dist/runtime/tool-host-contract.d.ts +0 -4
  54. package/dist/runtime/tool-host-contract.js +1 -1
  55. package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
  56. package/dist/services/AccessibilityCatalogResolver.js +29 -42
  57. package/dist/services/ElementToolStateStore.d.ts +32 -61
  58. package/dist/services/ElementToolStateStore.js +37 -67
  59. package/dist/services/HighlightCoordinator.d.ts +9 -21
  60. package/dist/services/HighlightCoordinator.js +52 -80
  61. package/dist/services/RangeSerializer.d.ts +4 -35
  62. package/dist/services/RangeSerializer.js +4 -48
  63. package/dist/services/SSMLExtractor.d.ts +8 -31
  64. package/dist/services/SSMLExtractor.js +18 -40
  65. package/dist/services/TTSService.d.ts +49 -56
  66. package/dist/services/TTSService.js +308 -283
  67. package/dist/services/ToolCoordinator.d.ts +6 -49
  68. package/dist/services/ToolCoordinator.js +25 -61
  69. package/dist/services/ToolRegistry.d.ts +82 -80
  70. package/dist/services/ToolRegistry.js +91 -67
  71. package/dist/services/ToolkitCoordinator.d.ts +83 -81
  72. package/dist/services/ToolkitCoordinator.js +186 -174
  73. package/dist/services/framework-error.d.ts +30 -7
  74. package/dist/services/framework-error.js +10 -13
  75. package/dist/services/interfaces.d.ts +47 -71
  76. package/dist/services/section-controller-types.d.ts +7 -7
  77. package/dist/services/section-session-assignment.js +6 -8
  78. package/dist/services/spoken-audio-cards.d.ts +2 -4
  79. package/dist/services/spoken-audio-cards.js +2 -4
  80. package/dist/services/tool-config-validation.d.ts +5 -7
  81. package/dist/services/tool-config-validation.js +11 -26
  82. package/dist/services/tool-context.d.ts +7 -23
  83. package/dist/services/tool-context.js +99 -53
  84. package/dist/services/tool-failure.d.ts +30 -0
  85. package/dist/services/tool-failure.js +11 -0
  86. package/dist/services/tool-icons.d.ts +1 -2
  87. package/dist/services/tool-icons.js +2 -2
  88. package/dist/services/tool-instance-id.d.ts +8 -6
  89. package/dist/services/tool-instance-id.js +12 -18
  90. package/dist/services/tool-providers/TTSToolProvider.d.ts +6 -43
  91. package/dist/services/tool-providers/TTSToolProvider.js +7 -63
  92. package/dist/services/tool-providers/ToolProviderApi.d.ts +0 -7
  93. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +7 -42
  94. package/dist/services/tool-providers/ToolProviderRegistry.js +21 -63
  95. package/dist/services/tool-request.d.ts +4 -0
  96. package/dist/services/tool-request.js +7 -2
  97. package/dist/services/toolbar-decided-tools.d.ts +1 -0
  98. package/dist/services/toolbar-decided-tools.js +1 -0
  99. package/dist/services/toolbar-items.js +2 -5
  100. package/dist/services/tools-config-normalizer.d.ts +18 -9
  101. package/dist/services/tools-config-normalizer.js +25 -19
  102. package/dist/services/tts/browser-provider.d.ts +2 -5
  103. package/dist/services/tts/browser-provider.js +4 -20
  104. package/dist/services/tts/catalog-span-alignment.js +14 -7
  105. package/dist/services/tts/flat-tree.d.ts +0 -7
  106. package/dist/services/tts/flat-tree.js +2 -2
  107. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +2 -2
  108. package/dist/services/tts/generated-speech/index.d.ts +1 -1
  109. package/dist/services/tts/generated-speech/index.js +1 -1
  110. package/dist/services/tts/generated-speech/types.d.ts +1 -1
  111. package/dist/services/tts/generated-speech/types.js +1 -1
  112. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +10 -12
  113. package/dist/services/tts/highlight-target-resolver.d.ts +12 -0
  114. package/dist/services/tts/math-aware-text-processing.js +10 -29
  115. package/dist/services/tts/math-speech.d.ts +1 -1
  116. package/dist/services/tts/math-speech.js +3 -0
  117. package/dist/services/tts/mathml-sanitization.d.ts +7 -0
  118. package/dist/services/tts/mathml-sanitization.js +9 -1
  119. package/dist/services/tts/provider-options.d.ts +12 -1
  120. package/dist/services/tts/ssml/spoken-text.js +1 -1
  121. package/dist/services/tts/start-failure.d.ts +0 -7
  122. package/dist/services/tts/start-failure.js +1 -1
  123. package/dist/services/tts/text-segmentation.d.ts +5 -0
  124. package/dist/services/tts/text-segmentation.js +13 -10
  125. package/dist/services/tts-runtime-config.d.ts +32 -12
  126. package/dist/services/tts-runtime-config.js +14 -20
  127. package/dist/tools/content-capability-resolution.d.ts +2 -2
  128. package/dist/tools/registration.d.ts +6 -5
  129. package/dist/tools/registration.js +10 -9
  130. package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
  131. package/dist/tools/registrations/toolbar-registration-helpers.js +6 -0
  132. package/dist/tools/tool-surface-host.js +1 -8
  133. package/package.json +8 -6
  134. package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +0 -2
  135. package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +0 -40
  136. package/dist/components/chunks/sre-engine-jd8cc08x.js +0 -1
  137. package/dist/services/ContextVariableStore.d.ts +0 -113
  138. package/dist/services/ContextVariableStore.js +0 -206
  139. package/dist/services/tts-control-events.d.ts +0 -13
  140. package/dist/services/tts-control-events.js +0 -7
  141. package/dist/utils/logger.d.ts +0 -6
  142. package/dist/utils/logger.js +0 -10
@@ -1,10 +1,25 @@
1
- import type { ToolConfigDiagnostic } from "./tool-config-validation.js";
1
+ import type { ToolFailurePhase } from "./tool-failure.js";
2
2
  export type FrameworkErrorKind = "tool-config" | "runtime-init" | "runtime-dispose" | "coordinator-init" | "provider-init" | "provider-register" | "tts-init" | "tool-state-load" | "tool-state-save" | "section-controller-init" | "section-controller-dispose" | "tool-surface"
3
3
  /**
4
4
  * A toolbar could not load a tool's module. Recoverable unless policy grants
5
5
  * the tool: the toolbar withholds the tool and the assessment goes on.
6
6
  */
7
7
  | "tool-module-load"
8
+ /**
9
+ * A toolbar threw opening a requested tool or answering whether it hosts one.
10
+ * Recoverable: the request goes unclaimed or passes the toolbar by.
11
+ */
12
+ | "tool-request"
13
+ /**
14
+ * A tool registration's relevance check or applicability gate threw.
15
+ * Recoverable: the toolbar applies the answer a missing check would give.
16
+ */
17
+ | "tool-registration"
18
+ /**
19
+ * A tool could not play, seek or change the rate of speech after it started.
20
+ * Recoverable: the tool stays available and the learner can retry.
21
+ */
22
+ | "tool-playback"
8
23
  /**
9
24
  * A timed-media section could not deliver a policy as authored: a media time
10
25
  * source missing `canPause` / `canRestrictSeeking` (recoverable — cues still
@@ -34,6 +49,14 @@ export type FrameworkErrorSeverity = "warning" | "error";
34
49
  * later section runs on, so readiness stays failed. Set by the report site.
35
50
  */
36
51
  export type FrameworkErrorScope = "cohort" | "runtime";
52
+ /**
53
+ * The section a `cohort`-scoped error was reported for. A section can fail after
54
+ * the learner has left it, so a reader holding another section ignores the error.
55
+ */
56
+ export interface FrameworkErrorCohort {
57
+ sectionId: string;
58
+ attemptId?: string;
59
+ }
37
60
  export interface FrameworkErrorModel {
38
61
  kind: FrameworkErrorKind;
39
62
  severity: FrameworkErrorSeverity;
@@ -42,6 +65,8 @@ export interface FrameworkErrorModel {
42
65
  details: string[];
43
66
  recoverable: boolean;
44
67
  scope: FrameworkErrorScope;
68
+ /** Set on a `cohort`-scoped error whose report site knows its section. */
69
+ cohort?: FrameworkErrorCohort;
45
70
  cause?: unknown;
46
71
  }
47
72
  export declare function toFrameworkErrorModel(args: {
@@ -53,6 +78,7 @@ export declare function toFrameworkErrorModel(args: {
53
78
  recoverable?: boolean;
54
79
  /** Defaults to `runtime`. */
55
80
  scope?: FrameworkErrorScope;
81
+ cohort?: FrameworkErrorCohort;
56
82
  cause?: unknown;
57
83
  }): FrameworkErrorModel;
58
84
  export declare function frameworkErrorFromUnknown(args: {
@@ -61,6 +87,7 @@ export declare function frameworkErrorFromUnknown(args: {
61
87
  error: unknown;
62
88
  recoverable?: boolean;
63
89
  scope?: FrameworkErrorScope;
90
+ cohort?: FrameworkErrorCohort;
64
91
  }): FrameworkErrorModel;
65
92
  /**
66
93
  * Coordinator-side error context shape consumed by
@@ -73,7 +100,7 @@ export declare function frameworkErrorFromUnknown(args: {
73
100
  * truth for coordinator code; values of that type satisfy this shape.
74
101
  */
75
102
  export interface FrameworkErrorCoordinatorContext {
76
- phase: "coordinator-ready" | "state-load" | "state-save" | "provider-register" | "provider-init" | "tts-init" | "section-controller-init" | "section-controller-dispose" | "tool-module-load";
103
+ phase: "coordinator-ready" | "state-load" | "state-save" | "provider-register" | "provider-init" | "tts-init" | "section-controller-init" | "section-controller-dispose" | ToolFailurePhase;
77
104
  toolId?: string;
78
105
  details?: Record<string, unknown>;
79
106
  }
@@ -95,10 +122,6 @@ export declare function frameworkErrorFromCoordinatorContext(args: {
95
122
  context: FrameworkErrorCoordinatorContext;
96
123
  recoverable?: boolean;
97
124
  scope?: FrameworkErrorScope;
98
- }): FrameworkErrorModel;
99
- export declare function frameworkErrorFromToolConfigDiagnostics(args: {
100
- source: string;
101
- diagnostics: ToolConfigDiagnostic[];
102
- recoverable?: boolean;
125
+ cohort?: FrameworkErrorCohort;
103
126
  }): FrameworkErrorModel;
104
127
  export declare function formatFrameworkErrorForConsole(error: FrameworkErrorModel): string;
@@ -7,6 +7,7 @@ export function toFrameworkErrorModel(args) {
7
7
  details: [...(args.details || [])],
8
8
  recoverable: args.recoverable === true,
9
9
  scope: args.scope ?? "runtime",
10
+ ...(args.cohort ? { cohort: { ...args.cohort } } : {}),
10
11
  cause: args.cause,
11
12
  };
12
13
  }
@@ -20,6 +21,7 @@ export function frameworkErrorFromUnknown(args) {
20
21
  message,
21
22
  recoverable: args.recoverable,
22
23
  scope: args.scope,
24
+ cohort: args.cohort,
23
25
  cause: args.error,
24
26
  });
25
27
  }
@@ -33,6 +35,13 @@ const COORDINATOR_PHASE_TO_KIND = {
33
35
  "section-controller-init": "section-controller-init",
34
36
  "section-controller-dispose": "section-controller-dispose",
35
37
  "tool-module-load": "tool-module-load",
38
+ "tool-request-open": "tool-request",
39
+ "tool-request-host-check": "tool-request",
40
+ "tool-visibility": "tool-registration",
41
+ "tool-applicability": "tool-registration",
42
+ "tool-state-load": "tool-state-load",
43
+ "tool-state-save": "tool-state-save",
44
+ "tool-playback": "tool-playback",
36
45
  };
37
46
  /**
38
47
  * Build a {@link FrameworkErrorModel} from a coordinator-side error +
@@ -58,19 +67,7 @@ export function frameworkErrorFromCoordinatorContext(args) {
58
67
  error: args.error,
59
68
  recoverable: args.recoverable,
60
69
  scope: args.scope,
61
- });
62
- }
63
- export function frameworkErrorFromToolConfigDiagnostics(args) {
64
- const details = args.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`);
65
- const message = details.length > 0
66
- ? "Invalid tools config."
67
- : "Invalid tools config (no diagnostics details were provided).";
68
- return toFrameworkErrorModel({
69
- kind: "tool-config",
70
- source: args.source,
71
- message,
72
- details,
73
- recoverable: args.recoverable,
70
+ cohort: args.cohort,
74
71
  });
75
72
  }
76
73
  export function formatFrameworkErrorForConsole(error) {
@@ -9,22 +9,24 @@
9
9
  */
10
10
  import type { I18nServiceApi } from "@pie-players/pie-players-shared/i18n";
11
11
  import type { AccessibilityCatalog, SectionControllerSessionState } from "@pie-players/pie-players-shared/types";
12
- import type { AccessibilityCatalogResolver, CatalogChangeListener, CatalogLookupContext, CatalogLookupOptions, CatalogOwnerRegistration, CatalogOwnerView, CatalogStatistics, CatalogType, ResolvedCatalog } from "./AccessibilityCatalogResolver.js";
12
+ import type { CatalogChangeListener, CatalogLookupContext, CatalogLookupOptions, CatalogOwnerRegistration, CatalogOwnerView, CatalogStatistics, CatalogType, ResolvedCatalog } from "./AccessibilityCatalogResolver.js";
13
13
  import type { CatalogOwnerContext } from "./catalog-owner.js";
14
14
  import type { FrameworkErrorListener } from "./framework-error-bus.js";
15
15
  import type { FrameworkErrorModel } from "./framework-error.js";
16
- import type { Annotation, HighlightColor, HighlightType } from "./HighlightCoordinator.js";
16
+ import type { ToolFailurePhase } from "./tool-failure.js";
17
+ import type { Annotation, HighlightColor } from "./HighlightCoordinator.js";
17
18
  import type { SerializedRange } from "./RangeSerializer.js";
18
19
  import type { SectionControllerHandle, SectionItemEventSubscriptionArgs, SectionScopedEventSubscriptionArgs, SectionEventSubscriptionArgs, ToolkitCoordinatorHooks } from "./ToolkitCoordinator.js";
20
+ import type { ElementIdComponents } from "./ElementToolStateStore.js";
19
21
  import type { ZIndexLayer } from "./ToolCoordinator.js";
20
- import type { PlaybackState, TTSConfig } from "./TTSService.js";
22
+ import type { PlaybackState } from "./TTSService.js";
21
23
  import type { TTSHighlightTargetResolverProvider } from "./tts/highlight-target-resolver.js";
22
24
  import type { TextToSpeechToolProviderConfig, ToolPlacementConfig, ToolProviderConfig } from "./tools-config-normalizer.js";
23
25
  import type { ToolProviderApi } from "./tool-providers/ToolProviderApi.js";
24
26
  import type { ToolProviderRegistry } from "./tool-providers/ToolProviderRegistry.js";
25
- import type { FeaturePolicyDecision, PolicySource, PnpEnforcementMode, ResolvedEngineInputs, ToolPolicyChangeListener, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolScope } from "../policy/engine.js";
26
- import type { AssessmentEntity, ItemSettings } from "@pie-players/pie-players-shared/types";
27
- import type { ITTSProvider, TTSProviderCapabilities } from "@pie-players/pie-tts";
27
+ import type { FeaturePolicyDecision, PolicySource, PnpEnforcementMode, ResolvedEngineInputs, ToolPolicyChangeListener, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolPolicyDiagnostic, ToolScope } from "../policy/engine.js";
28
+ import type { AssessmentEntity, ItemSettings, ToolParametersFor } from "@pie-players/pie-players-shared/types";
29
+ import type { TTSConfig } from "@pie-players/pie-tts";
28
30
  import type { ResolvedToolContext, ToolContextResolverContext, ToolRegistry } from "./ToolRegistry.js";
29
31
  import type { ToolOpenRequest, ToolRequestTarget } from "./tool-request.js";
30
32
  export type { I18nServiceApi };
@@ -34,10 +36,6 @@ export type { I18nServiceApi };
34
36
  * Manages content highlighting for TTS, annotations, and selections.
35
37
  */
36
38
  export interface HighlightCoordinatorApi {
37
- /**
38
- * Highlight a text range
39
- */
40
- highlightRange(range: Range, type: HighlightType, color: HighlightColor): void;
41
39
  /**
42
40
  * Highlight the word being read (temporary): one range per tree the word
43
41
  * spans, all painted, so a word split across inline elements highlights
@@ -68,17 +66,9 @@ export interface HighlightCoordinatorApi {
68
66
  */
69
67
  clearTTS(): void;
70
68
  /**
71
- * Clear highlights of a specific type
72
- */
73
- clearHighlights(type: HighlightType): void;
74
- /**
75
- * Check if highlighting is supported in current environment
69
+ * Clear the TTS word highlight, keeping the sentence highlight
76
70
  */
77
- isSupported(): boolean;
78
- /**
79
- * Update TTS highlight style dynamically
80
- */
81
- updateTTSHighlightStyle(color: string, opacity: number): void;
71
+ clearTTSWord(): void;
82
72
  /**
83
73
  * Record a learner annotation over a range.
84
74
  *
@@ -214,6 +204,13 @@ export interface SpeakOptions {
214
204
  * element target is its own root.
215
205
  */
216
206
  contentRoot?: Element | null;
207
+ /**
208
+ * Who starts the read. The run carries it until the next read starts, a
209
+ * seek's restart included, and {@link TtsServiceApi.getRunOwner} reports it.
210
+ */
211
+ ownerId?: string;
212
+ /** The rate the read starts at; it stays the service's rate afterwards. */
213
+ rate?: number;
217
214
  }
218
215
  /**
219
216
  * TTS service interface
@@ -222,10 +219,6 @@ export interface SpeakOptions {
222
219
  * Supports QTI 3.0 accessibility catalogs for pre-authored spoken content.
223
220
  */
224
221
  export interface TtsServiceApi {
225
- /**
226
- * Initialize TTS with a provider
227
- */
228
- initialize(provider: ITTSProvider, config?: Partial<TTSConfig>): Promise<void>;
229
222
  /**
230
223
  * Read `target` aloud: a range reads the text it selects, an element its
231
224
  * content. A node with a spoken card that the target holds whole reads its
@@ -243,7 +236,8 @@ export interface TtsServiceApi {
243
236
  */
244
237
  resume(): void;
245
238
  /**
246
- * Stop playback
239
+ * Stop playback and release the run owner. A read that ends on its own
240
+ * keeps its owner.
247
241
  */
248
242
  stop(): void;
249
243
  /**
@@ -251,13 +245,6 @@ export interface TtsServiceApi {
251
245
  * service cannot speak.
252
246
  */
253
247
  dispose(): void;
254
- /**
255
- * Request active TTS controls to hand off/deactivate their UI state.
256
- *
257
- * This is an orchestration hint for TTS tool chrome and does not replace
258
- * playback controls such as stop/pause/resume.
259
- */
260
- requestControlHandoff(): void;
261
248
  /**
262
249
  * Seek forward by sentence units
263
250
  */
@@ -266,30 +253,21 @@ export interface TtsServiceApi {
266
253
  * Seek backward by sentence units
267
254
  */
268
255
  seekBackward(units?: number): Promise<void>;
269
- /**
270
- * Check if currently playing
271
- */
272
- isPlaying(): boolean;
273
- /**
274
- * Check if paused
275
- */
276
- isPaused(): boolean;
277
256
  /**
278
257
  * Get current playback state
279
258
  */
280
259
  getState(): PlaybackState;
281
260
  /**
282
- * Get currently speaking text
261
+ * The `ownerId` the latest read started with, or null when it named none,
262
+ * nothing has been read or {@link stop} released it. A new read announces
263
+ * itself through {@link onStateChange} with `loading`, and a stop that
264
+ * releases an owner announces `idle`, so a listener sees the owner change.
283
265
  */
284
- getCurrentText(): string | null;
266
+ getRunOwner(): string | null;
285
267
  /**
286
268
  * Subscribe to state changes. Returns the function that unsubscribes.
287
269
  */
288
270
  onStateChange(callback: (state: PlaybackState) => void): () => void;
289
- /**
290
- * Get capabilities of current provider
291
- */
292
- getCapabilities(): TTSProviderCapabilities | null;
293
271
  /**
294
272
  * Update TTS settings dynamically (rate, pitch, voice)
295
273
  */
@@ -298,18 +276,10 @@ export interface TtsServiceApi {
298
276
  * Update playback speed and apply it to active planned playback.
299
277
  */
300
278
  setPlaybackRate(rate: number): Promise<void>;
301
- /**
302
- * Set highlight coordinator for word highlighting
303
- */
304
- setHighlightCoordinator(coordinator: HighlightCoordinatorApi): void;
305
279
  /**
306
280
  * Set a late-bound provider for optional host TTS highlight target remapping.
307
281
  */
308
- setHighlightTargetResolverProvider?(provider: TTSHighlightTargetResolverProvider | null): () => void;
309
- /**
310
- * Set accessibility catalog resolver for spoken content
311
- */
312
- setCatalogResolver(resolver: AccessibilityCatalogResolver): void;
282
+ setHighlightTargetResolverProvider(provider: TTSHighlightTargetResolverProvider | null): () => void;
313
283
  }
314
284
  /**
315
285
  * Accessibility catalog resolver interface
@@ -387,18 +357,15 @@ export interface AccessibilityCatalogResolverApi {
387
357
  */
388
358
  export interface ElementToolStateStoreApi {
389
359
  /**
390
- * Generate a globally unique element ID from components
360
+ * Generate a globally unique element ID from its parts. `attemptId` is `""`
361
+ * when the host names no attempt; a tool keyed by item or section leaves
362
+ * `elementId`, and for a section `itemId`, as `""`.
391
363
  */
392
- getGlobalElementId(assessmentId: string, sectionId: string, itemId: string, elementId: string): string;
364
+ getGlobalElementId(parts: ElementIdComponents): string;
393
365
  /**
394
366
  * Parse a global element ID into its components
395
367
  */
396
- parseGlobalElementId(globalElementId: string): {
397
- assessmentId: string;
398
- sectionId: string;
399
- itemId: string;
400
- elementId: string;
401
- } | null;
368
+ parseGlobalElementId(globalElementId: string): ElementIdComponents | null;
402
369
  /**
403
370
  * Set state for a specific tool on an element
404
371
  */
@@ -436,7 +403,7 @@ export interface ElementToolStateStoreApi {
436
403
  */
437
404
  clearTool(toolId: string): void;
438
405
  /**
439
- * Clear all elements in a specific section
406
+ * Clear all elements in a specific section, across its attempts
440
407
  */
441
408
  clearSection(assessmentId: string, sectionId: string): void;
442
409
  /**
@@ -480,9 +447,9 @@ export interface ToolkitCoordinatorApi {
480
447
  */
481
448
  readonly toolProviderRegistry: ToolProviderRegistry;
482
449
  /**
483
- * Ensure TTS service is initialized and ready.
450
+ * Ensure the TTS service is initialized from the `textToSpeech` tool config.
484
451
  */
485
- ensureTTSReady(config?: Record<string, unknown>): Promise<void>;
452
+ ensureTTSReady(): Promise<void>;
486
453
  /**
487
454
  * Ensure a tool's provider is initialized and ready. Providers register under
488
455
  * their tool's id, so a tool passes its own base tool id. A tool starts its
@@ -619,11 +586,11 @@ export interface ToolkitCoordinatorApi {
619
586
  */
620
587
  reportFrameworkError?(model: FrameworkErrorModel): void;
621
588
  /**
622
- * Report that a toolbar could not load a tool's module. The tool degrades
623
- * unless policy grants it, in which case the failure is fatal. Optional so
624
- * structural host coordinators remain assignable; a toolbar without it logs.
589
+ * Report that a tool failed in `phase`, under the tool failure policy
590
+ * {@link ToolFailurePhase} describes. Optional so structural host
591
+ * coordinators remain assignable; a reporter without it logs.
625
592
  */
626
- reportToolModuleFailure?(toolId: string, error: unknown): void;
593
+ reportToolFailure?(toolId: string, phase: ToolFailurePhase, error: unknown): void;
627
594
  /**
628
595
  * Resolve the visible tool set for a given placement level + scope.
629
596
  * Returns the engine's full decision (visible tools, diagnostics,
@@ -643,7 +610,7 @@ export interface ToolkitCoordinatorApi {
643
610
  * Optional so host-supplied coordinator stubs predating this method stay
644
611
  * assignable; call sites must feature-detect.
645
612
  */
646
- decideFeaturePolicy?(featureId: string, scope?: ToolScope): FeaturePolicyDecision;
613
+ decideFeaturePolicy?<K extends string>(featureId: K, scope?: ToolScope): FeaturePolicyDecision<ToolParametersFor<K>>;
647
614
  /**
648
615
  * Subscribe to policy-engine change events. Fires whenever the
649
616
  * coordinator's bound policy inputs change (`updateToolConfig`,
@@ -653,6 +620,15 @@ export interface ToolkitCoordinatorApi {
653
620
  * should call `decideToolPolicy(...)` with their level / scope.
654
621
  */
655
622
  onPolicyChange(listener: ToolPolicyChangeListener): () => void;
623
+ /**
624
+ * Subscribe to the policy diagnostics the coordinator reports: the ones it
625
+ * logs as console warnings, once per code, tool and item. A new listener is
626
+ * first handed every diagnostic already reported. A change to the bound
627
+ * policy inputs or to PNP enforcement clears the record, and so does a tool
628
+ * registration that makes a named support id known or unknown, so a conflict
629
+ * that survives the change is reported again once.
630
+ */
631
+ onPolicyDiagnostic(listener: (diagnostic: ToolPolicyDiagnostic) => void): () => void;
656
632
  /**
657
633
  * Subscribe to accessibility-catalog registrations and removals.
658
634
  *
@@ -27,14 +27,14 @@ export interface SectionSessionPersistenceConfig {
27
27
  context: SectionControllerContext;
28
28
  strategy: SectionSessionPersistenceStrategy;
29
29
  }
30
+ /**
31
+ * applySession contract:
32
+ * - Preferred input is the output of getSession().
33
+ * - itemSessions may contain canonical attempt entries
34
+ * ({ itemIdentifier, session, isCompleted }) or raw session payloads
35
+ * ({ id, data, ... }); controller normalizes these before applying.
36
+ */
30
37
  export interface SectionControllerApplySessionOptions {
31
- /**
32
- * applySession contract:
33
- * - Preferred input is the output of getSession().
34
- * - itemSessions may contain canonical attempt entries
35
- * ({ itemIdentifier, session, isCompleted }) or raw session payloads
36
- * ({ id, data, ... }); controller normalizes these before applying.
37
- */
38
38
  mode?: "replace" | "merge";
39
39
  }
40
40
  export interface SectionControllerLoadedRenderable {
@@ -15,16 +15,14 @@
15
15
  */
16
16
  import { hasResponseField, hasResponseValue, } from "@pie-players/pie-players-shared/pie";
17
17
  import { structurallyEqual } from "../utils/structural-equality.js";
18
- function isRecord(value) {
19
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
20
- }
18
+ import { isPlainRecord } from "@pie-players/pie-players-shared/object";
21
19
  /** An entry is canonical (`{ itemIdentifier, session, ... }`) or the raw item session. */
22
20
  function itemSessionOf(entry) {
23
- return isRecord(entry) && isRecord(entry.session) ? entry.session : entry;
21
+ return isPlainRecord(entry) && isPlainRecord(entry.session) ? entry.session : entry;
24
22
  }
25
23
  function sameItemSessions(next, current) {
26
- const nextSessions = isRecord(next) ? next : {};
27
- const currentSessions = isRecord(current) ? current : {};
24
+ const nextSessions = isPlainRecord(next) ? next : {};
25
+ const currentSessions = isPlainRecord(current) ? current : {};
28
26
  const itemIds = new Set([
29
27
  ...Object.keys(nextSessions),
30
28
  ...Object.keys(currentSessions),
@@ -63,10 +61,10 @@ function sameSessionContent(next, current) {
63
61
  export function resolveSectionSessionAssignment(current, next) {
64
62
  if (!current)
65
63
  return next;
66
- const itemSessions = isRecord(next.itemSessions)
64
+ const itemSessions = isPlainRecord(next.itemSessions)
67
65
  ? { ...next.itemSessions }
68
66
  : {};
69
- const currentItemSessions = isRecord(current.itemSessions)
67
+ const currentItemSessions = isPlainRecord(current.itemSessions)
70
68
  ? current.itemSessions
71
69
  : {};
72
70
  for (const [itemId, currentEntry] of Object.entries(currentItemSessions)) {
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * QTI 3 treats a recording and synthesized speech as the *same* support — both
5
5
  * are `spoken`, and a card carries recorded audio through `qti-file-href` plus a
6
- * MIME type — so this is not a new accommodation but the other form the existing
7
- * one can take. Some programs prefer a human voice to synthesis; PIE's `spoken`
6
+ * MIME type — so a recording is the other form the existing support takes.
7
+ * Some programs prefer a human voice to synthesis; PIE's `spoken`
8
8
  * card was string-only, so it had no way to say "play this file for this node".
9
9
  *
10
10
  * A node commonly carries both forms in the same language: the reading script
@@ -17,8 +17,6 @@
17
17
  * Validation posture matches sign-language cards: "treat as absent, never as
18
18
  * partially valid". A malformed payload must not produce a silent player that
19
19
  * looks like read-aloud is working.
20
- *
21
- * Part of PIE Assessment Toolkit.
22
20
  */
23
21
  import type { CatalogCardPayload, MediaFragmentRange, MediaSource } from "@pie-players/pie-players-shared/types";
24
22
  /** A validated recorded spoken alternate, flattened for playback. */
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * QTI 3 treats a recording and synthesized speech as the *same* support — both
5
5
  * are `spoken`, and a card carries recorded audio through `qti-file-href` plus a
6
- * MIME type — so this is not a new accommodation but the other form the existing
7
- * one can take. Some programs prefer a human voice to synthesis; PIE's `spoken`
6
+ * MIME type — so a recording is the other form the existing support takes.
7
+ * Some programs prefer a human voice to synthesis; PIE's `spoken`
8
8
  * card was string-only, so it had no way to say "play this file for this node".
9
9
  *
10
10
  * A node commonly carries both forms in the same language: the reading script
@@ -17,8 +17,6 @@
17
17
  * Validation posture matches sign-language cards: "treat as absent, never as
18
18
  * partially valid". A malformed payload must not produce a silent player that
19
19
  * looks like read-aloud is working.
20
- *
21
- * Part of PIE Assessment Toolkit.
22
20
  */
23
21
  import { isUnsupportedMediaAssetVersion, normalizeMediaFragment, normalizeMediaSources, trimmedOrUndefined, } from "@pie-players/pie-players-shared/media";
24
22
  /**
@@ -1,5 +1,4 @@
1
1
  import { type CanonicalToolsConfig, type ToolsConfigInput } from "./tools-config-normalizer.js";
2
- import { type FrameworkErrorModel } from "./framework-error.js";
3
2
  import type { ToolRegistry } from "./ToolRegistry.js";
4
3
  export type ToolConfigStrictness = "off" | "warn" | "error";
5
4
  export type ToolConfigDiagnosticSeverity = "warning" | "error";
@@ -19,13 +18,12 @@ export interface ToolConfigValidationResult {
19
18
  config: CanonicalToolsConfig;
20
19
  diagnostics: ToolConfigDiagnostic[];
21
20
  }
22
- export declare function frameworkErrorFromToolConfigValidation(args: {
23
- source: string;
24
- diagnostics?: ToolConfigDiagnostic[] | null;
25
- error?: unknown;
26
- recoverable?: boolean;
27
- }): FrameworkErrorModel;
28
21
  export declare function normalizeToolConfigStrictness(value: unknown): ToolConfigStrictness;
22
+ /**
23
+ * Normalize a tools config, sanitize and validate each provider entry with its
24
+ * registration's hooks, check ids and levels against `options.toolRegistry`,
25
+ * and report the diagnostics by `options.strictness` (default `"error"`).
26
+ */
29
27
  export declare function normalizeAndValidateToolsConfig(input?: ToolsConfigInput | null, options?: ToolConfigValidationOptions): ToolConfigValidationResult;
30
28
  /**
31
29
  * The normalization and diagnostics of {@link normalizeAndValidateToolsConfig},
@@ -1,20 +1,4 @@
1
1
  import { normalizeToolsConfig, } from "./tools-config-normalizer.js";
2
- import { frameworkErrorFromToolConfigDiagnostics, frameworkErrorFromUnknown, } from "./framework-error.js";
3
- export function frameworkErrorFromToolConfigValidation(args) {
4
- if (Array.isArray(args.diagnostics) && args.diagnostics.length > 0) {
5
- return frameworkErrorFromToolConfigDiagnostics({
6
- source: args.source,
7
- diagnostics: args.diagnostics,
8
- recoverable: args.recoverable,
9
- });
10
- }
11
- return frameworkErrorFromUnknown({
12
- kind: "tool-config",
13
- source: args.source,
14
- error: args.error ?? new Error("Invalid tools config"),
15
- recoverable: args.recoverable,
16
- });
17
- }
18
2
  const DEFAULT_STRICTNESS = "error";
19
3
  export function normalizeToolConfigStrictness(value) {
20
4
  if (value === "off" || value === "warn" || value === "error") {
@@ -198,6 +182,11 @@ function throwValidationError(diagnostics, source) {
198
182
  .join("\n");
199
183
  throw new Error(`[tool-config-validation:${source}] Invalid tools config:\n${formatted}`);
200
184
  }
185
+ /**
186
+ * Normalize a tools config, sanitize and validate each provider entry with its
187
+ * registration's hooks, check ids and levels against `options.toolRegistry`,
188
+ * and report the diagnostics by `options.strictness` (default `"error"`).
189
+ */
201
190
  export function normalizeAndValidateToolsConfig(input, options = {}) {
202
191
  const result = collectToolConfigDiagnostics(input, options.toolRegistry);
203
192
  reportToolConfigDiagnostics(result.diagnostics, options);
@@ -212,11 +201,9 @@ export function collectToolConfigDiagnostics(input, toolRegistry) {
212
201
  const normalized = normalizeToolsConfig(input);
213
202
  const diagnostics = [];
214
203
  // Tool-id and provider checks need a registry to check against, and every
215
- // collector below returns early without one. Say so: this package no longer
216
- // falls back to a packaged registry, so a caller that used to get id
217
- // validation for free now gets none, and a silent downgrade of "your ids are
218
- // valid" to "nobody looked" is the kind of change that surfaces as a typo
219
- // reaching a learner.
204
+ // collector below returns early without one. Say so, since there is no
205
+ // packaged fallback registry: silence would read as "your ids are valid" when
206
+ // nobody looked, and a typo would reach a learner.
220
207
  const hasConfiguredTools = Object.values(normalized.placement).some((ids) => ids.length > 0) ||
221
208
  Object.keys(normalized.providers).length > 0 ||
222
209
  (normalized.policy.allowed?.length ?? 0) > 0 ||
@@ -260,11 +247,9 @@ export function reportToolConfigDiagnostics(diagnostics, options = {}) {
260
247
  if (strictness === "warn") {
261
248
  emitWarnings(diagnostics, source);
262
249
  }
263
- // Severity decides what `strictness: "error"` rejects. Every diagnostic was
264
- // `"error"` until `tools.registryUnavailable`, which reports that validation
265
- // could not run rather than that the config is wrong — throwing on it would
266
- // turn "no registry supplied" from a host's existing, working setup into a
267
- // construction failure.
250
+ // Severity decides what `strictness: "error"` rejects. `tools.registryUnavailable`
251
+ // is a warning: it reports that validation could not run, and throwing on it
252
+ // would fail construction for a host whose setup works without a registry.
268
253
  const blocking = diagnostics.filter((entry) => entry.severity === "error");
269
254
  if (strictness === "error" && blocking.length > 0) {
270
255
  throwValidationError(blocking, source);
@@ -102,30 +102,9 @@ export interface ElementToolContext extends BaseToolContext {
102
102
  */
103
103
  export type ToolContext = AssessmentToolContext | SectionToolContext | ItemToolContext | PassageToolContext | RubricToolContext | ElementToolContext;
104
104
  /**
105
- * Type guard to check if context is at item level
106
- */
107
- export declare function isItemContext(context: ToolContext): context is ItemToolContext;
108
- /**
109
- * Type guard to check if context is at passage level
110
- */
111
- export declare function isPassageContext(context: ToolContext): context is PassageToolContext;
112
- /**
113
- * Type guard to check if context is at rubric level
114
- */
115
- export declare function isRubricContext(context: ToolContext): context is RubricToolContext;
116
- /**
117
- * Type guard to check if context is at element level
118
- */
119
- export declare function isElementContext(context: ToolContext): context is ElementToolContext;
120
- /** The plain text a context carries, tags removed. */
121
- export declare function extractTextContent(context: ToolContext): string;
122
- /**
123
- * The authored markup a context carries, tags intact.
124
- *
125
- * For indicators that live in the markup rather than in the prose — `<math>`
126
- * above all, whose whole signal is the element name.
105
+ * Helper to check if context contains mathematical content
106
+ * (Basic heuristic - can be overridden by tools)
127
107
  */
128
- export declare function extractMarkupContent(context: ToolContext): string;
129
108
  export declare function hasMathContent(context: ToolContext): boolean;
130
109
  /**
131
110
  * Helper to check if context contains choice-based interactions
@@ -135,6 +114,11 @@ export declare function hasChoiceInteraction(context: ToolContext): boolean;
135
114
  * Helper to check if context contains text that can be read aloud
136
115
  */
137
116
  export declare function hasReadableText(context: ToolContext): boolean;
117
+ /**
118
+ * Whether read-aloud has something to read: readable text, or markup carrying
119
+ * a catalog idref, whose spoken card reads for content with no text.
120
+ */
121
+ export declare function hasSpokenContent(context: ToolContext): boolean;
138
122
  /**
139
123
  * Helper to check if context contains science content
140
124
  * (Basic heuristic - can be overridden by tools)