@pie-players/pie-assessment-toolkit 0.3.75 → 1.0.0-beta.1

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 +451 -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-asenkvx6.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 +32 -30
  23. package/dist/policy/core/decision-types.d.ts +51 -34
  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 +7 -3
  27. package/dist/policy/core/pnp-policy-inputs.js +17 -33
  28. package/dist/policy/core/provenance.d.ts +2 -2
  29. package/dist/policy/core/tool-parameters.d.ts +12 -0
  30. package/dist/policy/core/tool-parameters.js +27 -0
  31. package/dist/policy/engine.d.ts +1 -1
  32. package/dist/policy/sources/PnpPolicySource.d.ts +18 -24
  33. package/dist/policy/sources/PnpPolicySource.js +57 -29
  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 +51 -57
  66. package/dist/services/TTSService.js +323 -284
  67. package/dist/services/ToolCoordinator.d.ts +6 -49
  68. package/dist/services/ToolCoordinator.js +25 -61
  69. package/dist/services/ToolRegistry.d.ts +83 -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 +51 -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 +39 -24
  83. package/dist/services/tool-context.js +158 -92
  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 +2 -0
  98. package/dist/services/toolbar-decided-tools.js +2 -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 +13 -22
  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 +7 -6
  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,12 +1,20 @@
1
1
  /**
2
- * Tool Registry
2
+ * Tool registry: the registration contract every tool declares itself with,
3
+ * the shape validation `register` and `override` run, and the `ToolRegistry`
4
+ * class.
3
5
  *
4
- * Central registry for all assessment tools. Manages tool metadata, visibility logic,
5
- * and button/instance creation. Supports dynamic registration and override by integrators.
6
+ * Layout: the exported contract types come first, ending with
7
+ * `ToolRegistration`, then `assertToolRegistrationShape`, then the class. Its
8
+ * members run from mutation and change observation, through queries and the
9
+ * filters for the second and third passes of tool resolution (relevance in
10
+ * `filterVisibleInContext`, applicability in `filterDecidedToolIds`; the policy
11
+ * engine owns the first), to module loading with its undefined-element
12
+ * warnings and the toolbar and surface render helpers.
6
13
  */
7
14
  import { dynamicMessageKey } from "@pie-players/pie-players-shared/i18n/provider";
8
15
  import { resolveToolTag, } from "../tools/tool-tag-map.js";
9
16
  import { PENDING_INPUT_WARNING_DELAY_MS, warnOncePerDocument, } from "../runtime/page-warnings.js";
17
+ const logToolCallbackFailure = (toolId, phase, error) => console.error(`[ToolRegistry] Tool '${toolId}' ${phase} check threw:`, error);
10
18
  /**
11
19
  * A registration's display name in the interface locale.
12
20
  *
@@ -228,10 +236,7 @@ export class ToolRegistry {
228
236
  return toolIds.map((toolId) => this.normalizeToolId(toolId));
229
237
  }
230
238
  /**
231
- * Register a tool
232
- *
233
- * @param registration - Tool registration
234
- * @throws Error if toolId is already registered
239
+ * Register a tool. Throws when its `toolId` is already registered.
235
240
  */
236
241
  register(registration) {
237
242
  assertToolRegistrationShape(registration);
@@ -242,9 +247,8 @@ export class ToolRegistry {
242
247
  this.emitChange({ kind: "register", toolIds: [registration.toolId] });
243
248
  }
244
249
  /**
245
- * Override an existing tool registration
246
- *
247
- * @param registration - New tool registration (must have existing toolId)
250
+ * Replace an existing tool registration. Throws when its `toolId` is not
251
+ * registered.
248
252
  */
249
253
  override(registration) {
250
254
  assertToolRegistrationShape(registration);
@@ -255,9 +259,7 @@ export class ToolRegistry {
255
259
  this.emitChange({ kind: "override", toolIds: [registration.toolId] });
256
260
  }
257
261
  /**
258
- * Unregister a tool
259
- *
260
- * @param toolId - Tool ID to remove
262
+ * Unregister a tool. An unknown id is a no-op and emits no change.
261
263
  */
262
264
  unregister(toolId) {
263
265
  if (!this.tools.has(toolId))
@@ -265,46 +267,21 @@ export class ToolRegistry {
265
267
  this.tools.delete(toolId);
266
268
  this.emitChange({ kind: "unregister", toolIds: [toolId] });
267
269
  }
268
- /**
269
- * Get a tool registration by ID
270
- *
271
- * @param toolId - Tool ID
272
- * @returns Tool registration or undefined
273
- */
274
270
  get(toolId) {
275
271
  return this.tools.get(toolId);
276
272
  }
277
- /**
278
- * Check if a tool is registered
279
- *
280
- * @param toolId - Tool ID
281
- * @returns true if registered
282
- */
283
273
  has(toolId) {
284
274
  return this.tools.has(toolId);
285
275
  }
286
- /**
287
- * Get all registered tool IDs
288
- *
289
- * @returns Array of tool IDs
290
- */
276
+ /** Registered tool ids, in registration order. */
291
277
  getAllToolIds() {
292
278
  return Array.from(this.tools.keys());
293
279
  }
294
- /**
295
- * Get all tool registrations
296
- *
297
- * @returns Array of tool registrations
298
- */
280
+ /** Registrations, in registration order. */
299
281
  getAllTools() {
300
282
  return Array.from(this.tools.values());
301
283
  }
302
- /**
303
- * Get tools that support a specific level
304
- *
305
- * @param level - Tool level (assessment, section, item, passage, element)
306
- * @returns Array of tool registrations that support this level
307
- */
284
+ /** Registrations whose `supportedLevels` include `level`. */
308
285
  getToolsByLevel(level) {
309
286
  return this.getAllTools().filter((tool) => tool.supportedLevels.includes(level));
310
287
  }
@@ -355,16 +332,14 @@ export class ToolRegistry {
355
332
  return toolIds.filter((toolId) => this.getToolActivation(toolId) === activation);
356
333
  }
357
334
  /**
358
- * Filter tools by visibility in a given context
359
- *
360
- * Pass 2 of the three-pass model: Given a list of allowed tool IDs (from Pass 1),
361
- * ask each tool if it's relevant in this context.
335
+ * Pass 2 of tool resolution: the registrations among `allowedToolIds` (the
336
+ * ids policy allowed in pass 1) that support `context.level` and answer
337
+ * `isVisibleInContext` with `true`.
362
338
  *
363
- * @param allowedToolIds - Tool IDs that passed Pass 1 (orchestrator approval)
364
- * @param context - Context to evaluate
365
- * @returns Array of visible tool registrations
339
+ * @param onFailure - Receives a relevance check's throw; the tool is then not
340
+ * visible. Logs when omitted.
366
341
  */
367
- filterVisibleInContext(allowedToolIds, context) {
342
+ filterVisibleInContext(allowedToolIds, context, onFailure = logToolCallbackFailure) {
368
343
  const visible = [];
369
344
  for (const toolId of allowedToolIds) {
370
345
  const tool = this.get(toolId);
@@ -376,19 +351,18 @@ export class ToolRegistry {
376
351
  }
377
352
  continue;
378
353
  }
379
- // Check if tool supports this level
380
354
  if (!tool.supportedLevels.includes(context.level)) {
381
355
  continue;
382
356
  }
383
- // Pass 2: Ask tool if it's relevant. A region capability declares no
384
- // answer and has no toolbar presence, so it is never visible here.
357
+ // A region capability declares no answer and has no toolbar presence,
358
+ // so it is never visible here.
385
359
  try {
386
360
  if (tool.isVisibleInContext?.(context)) {
387
361
  visible.push(tool);
388
362
  }
389
363
  }
390
364
  catch (error) {
391
- console.error(`Error evaluating visibility for tool '${toolId}':`, error);
365
+ onFailure(toolId, "tool-visibility", error);
392
366
  }
393
367
  }
394
368
  return visible;
@@ -405,8 +379,10 @@ export class ToolRegistry {
405
379
  *
406
380
  * @param toolId - Tool to ask
407
381
  * @param contexts - Every context the tool could act on at this placement
382
+ * @param onFailure - Receives the gate's throw; the tool then counts as
383
+ * applicable. Logs when omitted.
408
384
  */
409
- isApplicableToAnyContext(toolId, contexts) {
385
+ isApplicableToAnyContext(toolId, contexts, onFailure = logToolCallbackFailure) {
410
386
  const tool = this.get(toolId);
411
387
  if (!tool?.isApplicableToContent)
412
388
  return true;
@@ -417,17 +393,64 @@ export class ToolRegistry {
417
393
  return tool.isApplicableToContent?.(context) ?? true;
418
394
  }
419
395
  catch (error) {
420
- console.error(`Error evaluating applicability for tool '${toolId}':`, error);
396
+ onFailure(toolId, "tool-applicability", error);
421
397
  // A gate that throws has not established that the tool is useless.
422
398
  return true;
423
399
  }
424
400
  });
425
401
  }
426
402
  /**
427
- * Get tool metadata for building UIs
428
- * Useful for building PNP configuration interfaces
403
+ * Passes 2 and 3 over a policy decision's tools: the ids a surface at `level`
404
+ * shows against `contexts`, in decision order. This is the rule a toolbar
405
+ * applies, for a host that builds its own tool surface.
429
406
  *
430
- * @returns Array of tool metadata (id, name, description, levels, activation)
407
+ * - A tool that does not support `level` is dropped.
408
+ * - Relevance (`isVisibleInContext`) keeps a tool visible in any one context.
409
+ * It does not run for a granted entry (`required` or `alwaysAvailable`),
410
+ * at section level, whose relevance is item-dependent, or when `contexts`
411
+ * is empty, since content that has not resolved cannot rule a tool out.
412
+ * - Applicability (`isApplicableToContent`) then removes a tool that can act
413
+ * on none of `contexts`, granted or not. It does not run at section level.
414
+ *
415
+ * @param entries - The decision's surviving entries (Pass 1), such as
416
+ * `ToolPolicyDecision.visibleTools`
417
+ * @param level - The level of the surface the tools render on
418
+ * @param contexts - Every context the tools could act on at that surface
419
+ * @param onFailure - Receives a relevance or applicability check's throw.
420
+ * Logs when omitted.
421
+ */
422
+ filterDecidedToolIds(entries, level, contexts, onFailure = logToolCallbackFailure) {
423
+ const granted = new Set();
424
+ const decided = [];
425
+ for (const entry of entries) {
426
+ const toolId = this.normalizeToolId(entry.toolId);
427
+ if (!toolId)
428
+ continue;
429
+ if (entry.required || entry.alwaysAvailable)
430
+ granted.add(toolId);
431
+ if (decided.includes(toolId))
432
+ continue;
433
+ if (!this.get(toolId)?.supportedLevels.includes(level))
434
+ continue;
435
+ decided.push(toolId);
436
+ }
437
+ if (level === "section")
438
+ return decided;
439
+ const relevant = new Set(contexts.length === 0
440
+ ? decided
441
+ : decided.filter((toolId) => granted.has(toolId)));
442
+ for (const context of contexts) {
443
+ const ungranted = decided.filter((toolId) => !relevant.has(toolId));
444
+ for (const tool of this.filterVisibleInContext(ungranted, context, onFailure)) {
445
+ relevant.add(tool.toolId);
446
+ }
447
+ }
448
+ return decided.filter((toolId) => relevant.has(toolId) &&
449
+ this.isApplicableToAnyContext(toolId, contexts, onFailure));
450
+ }
451
+ /**
452
+ * Plain metadata for every registration, in registration order, for UIs
453
+ * such as a PNP configuration screen.
431
454
  */
432
455
  getToolMetadata() {
433
456
  return this.getAllTools().map((tool) => ({
@@ -575,7 +598,7 @@ export class ToolRegistry {
575
598
  const timer = setTimeout(() => {
576
599
  if (customElements.get(tagName))
577
600
  return;
578
- warnOncePerDocument(doc, `undefinedToolElement.${toolId}`, `[ToolRegistry] Tool "${toolId}" renders <${tagName}>, which is still undefined after ${PENDING_INPUT_WARNING_DELAY_MS / 1000} s, and its registry has no module loader for it. Pass toolModuleLoaders to createPackagedToolRegistry (DEFAULT_TOOL_MODULE_LOADERS from @pie-players/pie-default-tool-loaders loads the stock tools), register one with setToolModuleLoaders, or import the tool's package before it renders. Reported once per page.`);
601
+ warnOncePerDocument(doc, `undefinedToolElement.${toolId}`, `[ToolRegistry] Tool "${toolId}" renders <${tagName}>, which is still undefined after ${PENDING_INPUT_WARNING_DELAY_MS / 1000} s, and its registry has no module loader for it. Register one with setToolModuleLoaders or import the tool's package before it renders; a registry from createPackagedToolRegistry has the packaged loaders unless its toolModuleLoaders replaced them. Reported once per page.`);
579
602
  }, PENDING_INPUT_WARNING_DELAY_MS);
580
603
  void customElements.whenDefined(tagName).then(() => clearTimeout(timer));
581
604
  }
@@ -615,10 +638,7 @@ export class ToolRegistry {
615
638
  }
616
639
  const mergedContext = {
617
640
  ...toolbarContext,
618
- componentOverrides: {
619
- ...(this.componentOverrides || {}),
620
- ...(toolbarContext.componentOverrides || {}),
621
- },
641
+ componentOverrides: this.mergeComponentOverrides(toolbarContext.componentOverrides),
622
642
  };
623
643
  return tool.renderToolbar(context, mergedContext);
624
644
  }
@@ -641,10 +661,14 @@ export class ToolRegistry {
641
661
  }
642
662
  return tool.renderSurface({
643
663
  ...context,
644
- componentOverrides: {
645
- ...(this.componentOverrides || {}),
646
- ...(context.componentOverrides || {}),
647
- },
664
+ componentOverrides: this.mergeComponentOverrides(context.componentOverrides),
648
665
  });
649
666
  }
667
+ /** The registry's component overrides, with the caller's taking precedence. */
668
+ mergeComponentOverrides(contextOverrides) {
669
+ return {
670
+ ...(this.componentOverrides || {}),
671
+ ...(contextOverrides || {}),
672
+ };
673
+ }
650
674
  }
@@ -1,19 +1,27 @@
1
1
  /**
2
- * ToolkitCoordinator - Centralized Assessment Toolkit Service Management
2
+ * ToolkitCoordinator: one per assessment, the owner of every toolkit service
3
+ * (TTSService, ToolCoordinator, HighlightCoordinator, ElementToolStateStore,
4
+ * AccessibilityCatalogResolver, ToolProviderRegistry), the ToolPolicyEngine,
5
+ * the framework-error bus and the section controllers. It holds toolkit
6
+ * concerns only; navigation, timing and progress belong to the player.
3
7
  *
4
- * Orchestrates all toolkit services (TTS, tools, accessibility, state management) from a single entry point.
5
- * Provides centralized configuration for tool availability and settings.
6
- *
7
- * Key features:
8
- * - Owns all toolkit services (TTSService, ToolCoordinator, HighlightCoordinator, ElementToolStateStore, AccessibilityCatalogResolver)
9
- * - Single configuration point for tool availability and settings
10
- * - Sensible defaults for standalone usage
11
- * - Direct access for settings widgets (no player dependency)
12
- * - Clean separation: toolkit concerns only (NOT assessment state like navigation, timing, progress)
13
- *
14
- * Part of PIE Assessment Toolkit.
8
+ * Layout: the module's exported config, hook and event types come first, then
9
+ * the class, whose members run in this order:
10
+ * - config resolution and the constructor;
11
+ * - error, telemetry and tool-failure reporting, readiness listeners and the
12
+ * framework-error bus;
13
+ * - element tool-state persistence;
14
+ * - tool registry adoption and provider registration;
15
+ * - section-event subscriptions, which follow the active cohort;
16
+ * - section controller creation, publication and disposal;
17
+ * - coordinator disposal;
18
+ * - TTS bring-up and readiness;
19
+ * - tool config updates and the policy engine surface, with the tool-context
20
+ * resolvers split around it (installation before, queries after);
21
+ * - tool-open requests;
22
+ * - config-change application and TTS reconfiguration, last.
15
23
  */
16
- import type { AccessibilityCatalog, AssessmentEntity, ItemSettings } from "@pie-players/pie-players-shared/types";
24
+ import type { AccessibilityCatalog, AssessmentEntity, ItemSettings, ToolParametersFor } from "@pie-players/pie-players-shared/types";
17
25
  import { type CanonicalToolsConfig, type ToolPlacementConfig, type ToolPolicyConfig, type TextToSpeechToolProviderConfig, type ToolProviderConfig, type ToolProvidersConfig, type ToolsConfigInput } from "./tools-config-normalizer.js";
18
26
  import { type ToolConfigStrictness } from "./tool-config-validation.js";
19
27
  import { AccessibilityCatalogResolver } from "./AccessibilityCatalogResolver.js";
@@ -22,14 +30,15 @@ import { ElementToolStateStore } from "./ElementToolStateStore.js";
22
30
  import { type FrameworkErrorModel } from "./framework-error.js";
23
31
  import { FrameworkErrorBus, type FrameworkErrorListener } from "./framework-error-bus.js";
24
32
  import { HighlightCoordinator } from "./HighlightCoordinator.js";
25
- import { ToolCoordinator } from "./ToolCoordinator.js";
33
+ import type { ToolCoordinatorApi } from "./interfaces.js";
26
34
  import { TTSService } from "./TTSService.js";
27
35
  import { ToolProviderRegistry } from "./tool-providers/index.js";
28
36
  import type { ToolProviderApi } from "./tool-providers/ToolProviderApi.js";
29
37
  import { ToolRegistry } from "./ToolRegistry.js";
30
38
  import type { ResolvedToolContext, ToolContextResolverContext, ToolContextResolverMap } from "./ToolRegistry.js";
39
+ import type { ToolFailurePhase } from "./tool-failure.js";
31
40
  import type { ToolOpenRequest, ToolRequestTarget } from "./tool-request.js";
32
- import { type FeaturePolicyDecision, type PnpEnforcementMode, type PolicySource, type ResolvedEngineInputs, type ToolPolicyChangeListener, type ToolPolicyDecision, type ToolPolicyDecisionRequest, type ToolScope } from "../policy/engine.js";
41
+ import { type FeaturePolicyDecision, type PnpEnforcementMode, type PolicySource, type ResolvedEngineInputs, type ToolPolicyChangeListener, type ToolPolicyDecision, type ToolPolicyDecisionRequest, type ToolPolicyDiagnostic, type ToolScope } from "../policy/engine.js";
33
42
  import type { SectionControllerContext, SectionControllerEvent, SectionControllerEventType, SectionControllerFactoryDefaults, SectionControllerHandle, SectionControllerKey, SectionControllerSessionState, SectionSessionPersistenceStrategy, SectionPersistenceFactoryDefaults } from "./section-controller-types.js";
34
43
  export type { SectionControllerContext, SectionControllerEvent, SectionControllerEventType, SectionControllerFactoryDefaults, SectionControllerHandle, SectionControllerKey, SectionControllerLoadedRenderable, SectionSessionPersistenceConfig, SectionSessionPersistenceStrategy, SectionControllerRuntimeState, SectionControllerSessionState, SectionPersistenceFactoryDefaults, } from "./section-controller-types.js";
35
44
  export type { FeaturePolicyDecision, PnpEnforcementMode, ResolvedEngineInputs, ToolPolicyChangeListener, ToolPolicyDecision, ToolPolicyDecisionRequest, } from "../policy/engine.js";
@@ -137,7 +146,7 @@ export interface ToolkitCoordinatorConfig {
137
146
  frameworkErrorBus?: FrameworkErrorBus;
138
147
  }
139
148
  export interface ToolkitErrorContext {
140
- phase: "coordinator-ready" | "state-load" | "state-save" | "provider-register" | "provider-init" | "tts-init" | "section-controller-init" | "section-controller-dispose" | "tool-module-load";
149
+ phase: "coordinator-ready" | "state-load" | "state-save" | "provider-register" | "provider-init" | "tts-init" | "section-controller-init" | "section-controller-dispose" | ToolFailurePhase;
141
150
  /** The tool whose provider failed. */
142
151
  toolId?: string;
143
152
  details?: Record<string, unknown>;
@@ -154,8 +163,8 @@ export interface SectionControllerLifecycleEvent {
154
163
  /**
155
164
  * Subscribe-time arguments for {@link ToolkitCoordinator.subscribeSectionEvents}.
156
165
  *
157
- * Phase D contract: subscriptions follow the toolkit's *active section
158
- * cohort* automatically. The listener is bound to whatever section
166
+ * Subscriptions follow the toolkit's *active section cohort*
167
+ * automatically. The listener is bound to whatever section
159
168
  * controller is active at subscribe time and is migrated, with snapshot
160
169
  * replay, on every cohort transition. There is no per-subscription
161
170
  * `(sectionId, attemptId)` binding.
@@ -176,8 +185,8 @@ export type SectionScopedEvent = Extract<SectionControllerEvent, {
176
185
  /**
177
186
  * Subscribe-time arguments for {@link ToolkitCoordinator.subscribeItemEvents}.
178
187
  *
179
- * See {@link SectionEventSubscriptionArgs} for the Phase D active-cohort
180
- * binding contract.
188
+ * See {@link SectionEventSubscriptionArgs} for the active-cohort binding
189
+ * contract.
181
190
  */
182
191
  export interface SectionItemEventSubscriptionArgs {
183
192
  listener: (event: SectionItemEvent) => void;
@@ -188,8 +197,8 @@ export interface SectionItemEventSubscriptionArgs {
188
197
  * Subscribe-time arguments for
189
198
  * {@link ToolkitCoordinator.subscribeSectionLifecycleEvents}.
190
199
  *
191
- * See {@link SectionEventSubscriptionArgs} for the Phase D active-cohort
192
- * binding contract.
200
+ * See {@link SectionEventSubscriptionArgs} for the active-cohort binding
201
+ * contract.
193
202
  */
194
203
  export interface SectionScopedEventSubscriptionArgs {
195
204
  listener: (event: SectionScopedEvent) => void;
@@ -240,10 +249,8 @@ export type ToolkitTelemetryListener = (args: {
240
249
  *
241
250
  * @example
242
251
  * ```typescript
243
- * // Both are exported by @pie-players/pie-default-tool-loaders
244
- * const toolRegistry = createPackagedToolRegistry({
245
- * toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
246
- * });
252
+ * // Exported by @pie-players/pie-default-tool-loaders
253
+ * const toolRegistry = createPackagedToolRegistry();
247
254
  *
248
255
  * // Create coordinator with configuration
249
256
  * const coordinator = new ToolkitCoordinator({
@@ -275,7 +282,7 @@ export declare class ToolkitCoordinator {
275
282
  readonly config: ToolkitCoordinatorConfig;
276
283
  /** Managed services (public for direct access) */
277
284
  readonly ttsService: TTSService;
278
- readonly toolCoordinator: ToolCoordinator;
285
+ readonly toolCoordinator: ToolCoordinatorApi;
279
286
  readonly highlightCoordinator: HighlightCoordinator;
280
287
  readonly elementToolStateStore: ElementToolStateStore;
281
288
  readonly catalogResolver: AccessibilityCatalogResolver;
@@ -288,7 +295,6 @@ export declare class ToolkitCoordinator {
288
295
  private ttsDegraded;
289
296
  private ttsInitPromise?;
290
297
  private ttsReconfigurePromise?;
291
- private stateLoaded;
292
298
  /**
293
299
  * The loader ran, whether or not it returned state. A failed load is
294
300
  * reported once and the coordinator runs without saved tool state.
@@ -303,8 +309,8 @@ export declare class ToolkitCoordinator {
303
309
  * tools reports its failure again as fatal.
304
310
  */
305
311
  private readonly degradedTools;
306
- /** Tools a toolbar reported a module failure for, each reported once. */
307
- private readonly toolModuleFailures;
312
+ /** Tool failures already reported, by phase and tool, each reported once. */
313
+ private readonly reportedToolFailures;
308
314
  private readonly readyChangeListeners;
309
315
  private readonly eagerInit;
310
316
  private toolRegistry;
@@ -331,8 +337,8 @@ export declare class ToolkitCoordinator {
331
337
  private readonly sectionPersistenceStrategies;
332
338
  private readonly sectionControllerLifecycleListeners;
333
339
  /**
334
- * Phase D: every active section-event subscription, indexed by
335
- * listener identity. The listener follows the toolkit's active
340
+ * Every active section-event subscription, indexed by listener
341
+ * identity. The listener follows the toolkit's active
336
342
  * section cohort across transitions; this map is the registry the
337
343
  * coordinator iterates on every cohort change to detach the listener
338
344
  * from the outgoing controller, attach it to the new one, and replay
@@ -344,8 +350,8 @@ export declare class ToolkitCoordinator {
344
350
  */
345
351
  private readonly activeSubscriptions;
346
352
  /**
347
- * Phase D: map key of the section controller currently treated as
348
- * the *active cohort*. Set by `getOrCreateSectionController` (both
353
+ * Map key of the section controller currently treated as the
354
+ * *active cohort*. Set by `getOrCreateSectionController` (both
349
355
  * the create-new and resolve-existing paths) and cleared when the
350
356
  * matching controller is disposed, and `null` while the next requested
351
357
  * cohort is still starting.
@@ -362,6 +368,8 @@ export declare class ToolkitCoordinator {
362
368
  private readonly telemetryListeners;
363
369
  private readonly frameworkErrorBus;
364
370
  private readonly ownsFrameworkErrorBus;
371
+ /** The instance behind {@link toolCoordinator}, kept for disposal. */
372
+ private readonly ownedToolCoordinator;
365
373
  private frameworkErrorHookUnsubscribe;
366
374
  private disposePromise;
367
375
  /**
@@ -372,20 +380,12 @@ export declare class ToolkitCoordinator {
372
380
  */
373
381
  private readonly policyEngine;
374
382
  /**
375
- * Host-set override for PNP/profile enforcement, passed to the engine.
376
- * `null` (the default) is auto-mode, which the engine resolves per decision
377
- * from the policy material the bound assessment and the decision's item carry.
378
- * `"on"` / `"off"` are explicit host opt-in / opt-out and stick
379
- * across subsequent assessment swaps until the host clears
380
- * the override by calling `setPnpEnforcement(null)`.
381
- */
382
- private pnpEnforcementOverride;
383
- /**
384
- * Policy diagnostics already logged, keyed by code, tool id and, for
385
- * `tool-policy.itemSettingNotApplied`, item id; see
383
+ * Policy diagnostics reported since the last input change, keyed by code,
384
+ * tool id and, for `tool-policy.itemSettingNotApplied`, item id; see
386
385
  * {@link warnPolicyDiagnostics}.
387
386
  */
388
- private readonly warnedPolicyDiagnostics;
387
+ private readonly reportedPolicyDiagnostics;
388
+ private readonly policyDiagnosticListeners;
389
389
  /**
390
390
  * Whether {@link decideFeaturePolicy} has already reported serving a decision
391
391
  * with no assessment bound.
@@ -460,13 +460,15 @@ export declare class ToolkitCoordinator {
460
460
  private emitSectionControllerLifecycle;
461
461
  /**
462
462
  * Every report site states whether the coordinator carries on after the
463
- * failure; one that takes down only its section passes `scope: "cohort"`.
463
+ * failure; one that takes down only its section passes that section.
464
464
  */
465
465
  private handleError;
466
466
  /**
467
467
  * Whether policy grants `toolId` as an accommodation on some surface: a
468
468
  * test-administration override, a mounted item's or a district requirement,
469
- * or profile support. Read without the unbound-assessment warning.
469
+ * or profile support. A toolbar tool counts only surfaces where PNP
470
+ * enforcement is on; a region feature ignores enforcement, as its decisions
471
+ * do. Read without the unbound-assessment warning.
470
472
  */
471
473
  private isToolGranted;
472
474
  /**
@@ -475,15 +477,16 @@ export declare class ToolkitCoordinator {
475
477
  * `fallbackFollows` marks a failure another path absorbs, which is recoverable
476
478
  * whatever the policy.
477
479
  */
478
- private reportToolFailure;
480
+ private reportStartFailure;
479
481
  /**
480
- * Report that a toolbar could not load a tool's module. The failure follows
481
- * the start-failure policy: the tool degrades, so the toolbar withholds it,
482
- * unless policy grants it. Reported once per tool however many toolbars fail
483
- * to load it. Every report re-announces the request targets, since the
484
- * reporting toolbar no longer hosts the tool.
482
+ * Report that a tool failed in `phase`. A module that failed to load follows
483
+ * the start-failure policy: the tool degrades, so toolbars withhold it, unless
484
+ * policy grants it. Every other phase is recoverable, since its report site
485
+ * keeps a recovery, and is reported on a microtask. Reported once per tool and
486
+ * phase however many toolbars or instances fail. A module failure re-announces the request targets, since
487
+ * the reporting toolbar no longer hosts the tool.
485
488
  */
486
- reportToolModuleFailure(toolId: string, error: unknown): void;
489
+ reportToolFailure(toolId: string, phase: ToolFailurePhase, error: unknown): void;
487
490
  private reportNewlyGrantedFailures;
488
491
  /**
489
492
  * Subscribe to changes of {@link isReady}: state loading, text-to-speech
@@ -516,6 +519,7 @@ export declare class ToolkitCoordinator {
516
519
  * @internal
517
520
  */
518
521
  reportFrameworkError(model: FrameworkErrorModel): void;
522
+ private sectionControllerKey;
519
523
  private getSectionControllerMapKey;
520
524
  private createDefaultSectionPersistence;
521
525
  private resolveSectionPersistence;
@@ -536,9 +540,6 @@ export declare class ToolkitCoordinator {
536
540
  * @returns Whether `registry` was adopted.
537
541
  */
538
542
  adoptToolRegistry(registry: ToolRegistry | null): boolean;
539
- /**
540
- * Register tool providers in the registry
541
- */
542
543
  private _registerToolProviders;
543
544
  private getProviderDescriptorTools;
544
545
  /**
@@ -567,7 +568,7 @@ export declare class ToolkitCoordinator {
567
568
  }): SectionControllerHandle | undefined;
568
569
  subscribeSectionEvents(args: SectionEventSubscriptionArgs): () => void;
569
570
  /**
570
- * Bind a Phase D subscription to a section controller. Detaches any
571
+ * Bind an active-cohort subscription to a section controller. Detaches any
571
572
  * prior controller binding the subscription was holding, attaches a
572
573
  * fresh `controller.subscribe(...)` callback, and replays the
573
574
  * canonical late-subscribe sequence (content-loaded × N then
@@ -600,6 +601,12 @@ export declare class ToolkitCoordinator {
600
601
  * re-bind them to the new controller.
601
602
  */
602
603
  private clearActiveCohortIfMatches;
604
+ /**
605
+ * Detach every active subscription from its controller. Iterates a snapshot,
606
+ * as `setActiveCohort` does, because a disposer may mutate
607
+ * `activeSubscriptions`.
608
+ */
609
+ private detachActiveSubscriptions;
603
610
  private buildSectionEventPredicate;
604
611
  private collectEventItemIds;
605
612
  /**
@@ -686,9 +693,9 @@ export declare class ToolkitCoordinator {
686
693
  private disposeOwnedResources;
687
694
  private waitForAdmittedInitialization;
688
695
  /**
689
- * Initialize TTS service with provider
696
+ * Initialize the TTS service from the `textToSpeech` tool config.
690
697
  */
691
- ensureTTSReady(config?: TextToSpeechToolProviderConfig): Promise<void>;
698
+ ensureTTSReady(): Promise<void>;
692
699
  private waitForPendingTTSReconfigure;
693
700
  private _initializeTTS;
694
701
  private resolveTTSToolConfig;
@@ -717,19 +724,12 @@ export declare class ToolkitCoordinator {
717
724
  setToolContextResolvers(resolvers: ToolContextResolverMap | null | undefined): void;
718
725
  private notifyToolContextResolverChange;
719
726
  /**
720
- * Get tool configuration.
721
- *
722
- * @param toolId Tool identifier
723
- * @returns Tool configuration or null if not configured
727
+ * Get tool configuration, or null if the tool is not configured.
724
728
  */
725
729
  getToolConfig(toolId: "textToSpeech"): TextToSpeechToolProviderConfig | null;
726
730
  getToolConfig(toolId: string): ToolProviderConfig | null;
727
731
  /**
728
- * Update tool configuration.
729
- * Applies changes to underlying services.
730
- *
731
- * @param toolId Tool identifier
732
- * @param updates Partial configuration updates
732
+ * Update tool configuration and apply the change to the underlying services.
733
733
  */
734
734
  updateToolConfig(toolId: "textToSpeech", updates: Partial<TextToSpeechToolProviderConfig>): void;
735
735
  updateToolConfig(toolId: string, updates: Partial<ToolProviderConfig>): void;
@@ -750,11 +750,14 @@ export declare class ToolkitCoordinator {
750
750
  */
751
751
  decideToolPolicy(request: ToolPolicyDecisionRequest): ToolPolicyDecision;
752
752
  /**
753
- * Log each policy diagnostic as a console warning, once per code and tool
754
- * id, and per item for `tool-policy.itemSettingNotApplied`. Decisions re-run
755
- * on every input change, so a conflict would otherwise repeat on each.
753
+ * Report each policy diagnostic as a console warning and to the
754
+ * {@link onPolicyDiagnostic} listeners, once per code and tool id, and per
755
+ * item for `tool-policy.itemSettingNotApplied`. Every toolbar and feature
756
+ * asks for decisions, so a conflict would otherwise repeat on each; the
757
+ * engine's input diagnostics arrive with each policy change.
756
758
  */
757
759
  private warnPolicyDiagnostics;
760
+ private notifyPolicyDiagnosticListener;
758
761
  /**
759
762
  * File a mounted item's policy settings (`requiredTools`, `restrictedTools`,
760
763
  * `toolParameters`) under its canonical id. They govern the decisions scoped
@@ -772,8 +775,8 @@ export declare class ToolkitCoordinator {
772
775
  * `pnpEnforcement` is not consulted.
773
776
  *
774
777
  * Reports once per coordinator when it is asked about a feature with no
775
- * assessment bound. A host in that state gets a correct denial for every
776
- * capability it asks about, which is indistinguishable from a student who was
778
+ * assessment bound. A host in that state gets a denial for every capability
779
+ * no item requires, which is indistinguishable from a student who was
777
780
  * properly declined — so without this, forgetting {@link updateAssessment}
778
781
  * presents as an accommodation that silently never appears.
779
782
  *
@@ -785,7 +788,7 @@ export declare class ToolkitCoordinator {
785
788
  * `scope` is the surface asking; an item's scope brings in that item's
786
789
  * registered settings ({@link registerItemSettings}).
787
790
  */
788
- decideFeaturePolicy(featureId: string, scope?: ToolScope): FeaturePolicyDecision;
791
+ decideFeaturePolicy<K extends string>(featureId: K, scope?: ToolScope): FeaturePolicyDecision<ToolParametersFor<K>>;
789
792
  /**
790
793
  * Subscribe to policy-engine change events. Fires whenever the
791
794
  * coordinator's bound inputs change (`updateToolConfig`,
@@ -804,6 +807,7 @@ export declare class ToolkitCoordinator {
804
807
  * their own lifetime ends before the coordinator's.
805
808
  */
806
809
  onPolicyChange(listener: ToolPolicyChangeListener): () => void;
810
+ onPolicyDiagnostic(listener: (diagnostic: ToolPolicyDiagnostic) => void): () => void;
807
811
  /**
808
812
  * Subscribe to accessibility-catalog registrations and removals.
809
813
  *
@@ -831,7 +835,8 @@ export declare class ToolkitCoordinator {
831
835
  updateAssessment(assessment: AssessmentEntity | null): void;
832
836
  /**
833
837
  * Override the auto-mode PNP/profile enforcement decision; `null` returns
834
- * to auto-mode.
838
+ * to auto-mode. `"on"` and `"off"` stick across assessment swaps until
839
+ * cleared.
835
840
  */
836
841
  setPnpEnforcement(mode: PnpEnforcementMode | null): void;
837
842
  /**
@@ -877,10 +882,7 @@ export declare class ToolkitCoordinator {
877
882
  canRequestTool(toolId: string, level?: ToolOpenRequest["level"], scopeId?: string): boolean;
878
883
  onToolRequestTargetsChange(listener: () => void): () => void;
879
884
  private resolveConfiguredPnpEnforcement;
880
- /**
881
- * Apply tool configuration changes to underlying services.
882
- * Called after updateToolConfig().
883
- */
885
+ /** Apply an `updateToolConfig` change to the underlying services. */
884
886
  private _applyToolConfigChange;
885
887
  /**
886
888
  * Register the provider a tool's updated config builds, replacing the one