@pie-players/pie-assessment-toolkit 0.3.73 → 0.3.75

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 (274) hide show
  1. package/README.md +469 -318
  2. package/dist/attempt/AssessmentSession.d.ts +1 -5
  3. package/dist/attempt/AssessmentSession.js +0 -29
  4. package/dist/attempt/TestSession.d.ts +1 -20
  5. package/dist/attempt/TestSession.js +8 -99
  6. package/dist/components/ItemScope.custom-element.d.ts +1 -0
  7. package/dist/components/ItemScope.custom-element.js +1 -0
  8. package/dist/components/ItemToolBar.custom-element.d.ts +1 -0
  9. package/dist/components/ItemToolBar.custom-element.js +1 -1
  10. package/dist/components/PieAssessmentToolkit.custom-element.d.ts +1 -0
  11. package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
  12. package/dist/components/SectionToolBar.custom-element.d.ts +1 -0
  13. package/dist/components/SectionToolBar.custom-element.js +1 -1
  14. package/dist/components/chunks/ItemToolBar-7tt7v2xp.js +4 -0
  15. package/dist/components/chunks/ItemToolBar-97hx60z4.js +2 -0
  16. package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +2 -0
  17. package/dist/components/chunks/ItemToolBar-jx38w99s.js +2 -0
  18. package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +40 -0
  19. package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
  20. package/dist/components/chunks/sre-engine-jd8cc08x.js +1 -0
  21. package/dist/components/item-scope-element.d.ts +2 -0
  22. package/dist/components/item-scope-element.js +2 -0
  23. package/dist/context/assessment-toolkit-context.d.ts +32 -6
  24. package/dist/index.d.ts +23 -48
  25. package/dist/index.js +12 -37
  26. package/dist/policy/core/PolicySource.d.ts +1 -1
  27. package/dist/policy/core/ToolPolicyEngine.d.ts +85 -34
  28. package/dist/policy/core/ToolPolicyEngine.js +148 -46
  29. package/dist/policy/core/compose-decision.d.ts +28 -7
  30. package/dist/policy/core/compose-decision.js +75 -54
  31. package/dist/policy/core/decision-types.d.ts +65 -34
  32. package/dist/policy/core/feature-decision.d.ts +16 -9
  33. package/dist/policy/core/feature-decision.js +14 -8
  34. package/dist/policy/core/pnp-policy-inputs.d.ts +24 -33
  35. package/dist/policy/core/pnp-policy-inputs.js +28 -56
  36. package/dist/policy/core/provenance.d.ts +9 -4
  37. package/dist/policy/core/provenance.js +1 -1
  38. package/dist/policy/engine.d.ts +7 -23
  39. package/dist/policy/engine.js +5 -20
  40. package/dist/policy/sources/PnpPolicySource.d.ts +44 -61
  41. package/dist/policy/sources/PnpPolicySource.js +117 -118
  42. package/dist/runtime/SectionControllerBinding.d.ts +279 -0
  43. package/dist/runtime/SectionControllerBinding.js +450 -0
  44. package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
  45. package/dist/runtime/SectionRuntimeEngine.js +30 -372
  46. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
  47. package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
  48. package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
  49. package/dist/runtime/adapter/dom-event-bridge.js +17 -44
  50. package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -2
  51. package/dist/runtime/adapter/subscriber-fanout.js +1 -2
  52. package/dist/runtime/catalog-context.d.ts +19 -0
  53. package/dist/runtime/catalog-context.js +28 -0
  54. package/dist/runtime/composition-emit-scheduler.d.ts +9 -15
  55. package/dist/runtime/composition-emit-scheduler.js +9 -15
  56. package/dist/runtime/content-language.d.ts +32 -0
  57. package/dist/runtime/content-language.js +46 -0
  58. package/dist/runtime/content-region.d.ts +17 -0
  59. package/dist/runtime/content-region.js +29 -0
  60. package/dist/runtime/content-registration.d.ts +13 -0
  61. package/dist/runtime/content-registration.js +25 -0
  62. package/dist/runtime/core/SectionEngineCore.d.ts +2 -2
  63. package/dist/runtime/core/SectionEngineCore.js +2 -2
  64. package/dist/runtime/core/cohort.d.ts +1 -1
  65. package/dist/runtime/core/cohort.js +1 -1
  66. package/dist/runtime/core/engine-input.d.ts +18 -30
  67. package/dist/runtime/core/engine-input.js +11 -12
  68. package/dist/runtime/core/engine-output.d.ts +16 -40
  69. package/dist/runtime/core/engine-output.js +9 -21
  70. package/dist/runtime/core/engine-readiness.d.ts +5 -37
  71. package/dist/runtime/core/engine-readiness.js +8 -41
  72. package/dist/runtime/core/engine-resolver.d.ts +46 -74
  73. package/dist/runtime/core/engine-resolver.js +18 -43
  74. package/dist/runtime/core/engine-state.d.ts +28 -36
  75. package/dist/runtime/core/engine-state.js +18 -21
  76. package/dist/runtime/core/engine-transition.d.ts +14 -19
  77. package/dist/runtime/core/engine-transition.js +71 -51
  78. package/dist/runtime/engine.d.ts +16 -19
  79. package/dist/runtime/engine.js +12 -19
  80. package/dist/runtime/page-warnings.d.ts +13 -0
  81. package/dist/runtime/page-warnings.js +22 -0
  82. package/dist/runtime/policy-input-forwarding.d.ts +25 -0
  83. package/dist/runtime/policy-input-forwarding.js +25 -0
  84. package/dist/runtime/registration-events.d.ts +14 -0
  85. package/dist/runtime/runtime-event-claim.d.ts +8 -0
  86. package/dist/runtime/runtime-event-claim.js +15 -0
  87. package/dist/runtime/shell-event-bridge.d.ts +45 -0
  88. package/dist/runtime/shell-event-bridge.js +127 -0
  89. package/dist/runtime/shell-registration.d.ts +73 -0
  90. package/dist/runtime/shell-registration.js +95 -0
  91. package/dist/runtime/shell-scope.d.ts +68 -0
  92. package/dist/runtime/shell-scope.js +188 -0
  93. package/dist/runtime/tool-host-contract.d.ts +28 -10
  94. package/dist/runtime/tool-host-contract.js +31 -10
  95. package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
  96. package/dist/runtime/unclaimed-registration-watch.js +23 -0
  97. package/dist/services/ElementToolStateStore.d.ts +14 -10
  98. package/dist/services/ElementToolStateStore.js +37 -13
  99. package/dist/services/HighlightCoordinator.d.ts +22 -28
  100. package/dist/services/HighlightCoordinator.js +431 -327
  101. package/dist/services/RangeSerializer.d.ts +10 -1
  102. package/dist/services/RangeSerializer.js +87 -14
  103. package/dist/services/TTSService.d.ts +130 -69
  104. package/dist/services/TTSService.js +713 -570
  105. package/dist/services/ToolCoordinator.d.ts +43 -66
  106. package/dist/services/ToolCoordinator.js +164 -208
  107. package/dist/services/ToolRegistry.d.ts +78 -54
  108. package/dist/services/ToolRegistry.js +107 -105
  109. package/dist/services/ToolkitCoordinator.d.ts +261 -175
  110. package/dist/services/ToolkitCoordinator.js +667 -433
  111. package/dist/services/audio-handoff.d.ts +6 -7
  112. package/dist/services/audio-handoff.js +20 -14
  113. package/dist/services/catalog-owner.d.ts +11 -1
  114. package/dist/services/catalog-owner.js +25 -0
  115. package/dist/services/create-tools-config.d.ts +2 -2
  116. package/dist/services/framework-error-bus.d.ts +3 -6
  117. package/dist/services/framework-error-bus.js +3 -6
  118. package/dist/services/framework-error.d.ts +30 -8
  119. package/dist/services/framework-error.js +9 -7
  120. package/dist/services/interfaces.d.ts +158 -129
  121. package/dist/services/section-controller-types.d.ts +15 -0
  122. package/dist/services/section-session-assignment.d.ts +21 -0
  123. package/dist/services/section-session-assignment.js +82 -0
  124. package/dist/services/spoken-audio-cards.d.ts +0 -2
  125. package/dist/services/spoken-audio-cards.js +1 -3
  126. package/dist/services/tool-config-defaults.d.ts +3 -3
  127. package/dist/services/tool-config-defaults.js +3 -3
  128. package/dist/services/tool-config-validation.d.ts +14 -4
  129. package/dist/services/tool-config-validation.js +63 -71
  130. package/dist/services/tool-context.d.ts +0 -8
  131. package/dist/services/tool-context.js +29 -25
  132. package/dist/services/tool-icons.d.ts +4 -3
  133. package/dist/services/tool-icons.js +4 -3
  134. package/dist/services/tool-instance-id.d.ts +0 -1
  135. package/dist/services/tool-instance-id.js +0 -6
  136. package/dist/services/tool-providers/TTSToolProvider.d.ts +41 -80
  137. package/dist/services/tool-providers/TTSToolProvider.js +81 -40
  138. package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -4
  139. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +32 -28
  140. package/dist/services/tool-providers/ToolProviderRegistry.js +121 -107
  141. package/dist/services/tool-providers/index.d.ts +0 -9
  142. package/dist/services/tool-providers/index.js +2 -4
  143. package/dist/services/tool-request.d.ts +27 -12
  144. package/dist/services/tool-request.js +24 -14
  145. package/dist/services/toolbar-decided-tools.d.ts +15 -0
  146. package/dist/services/toolbar-decided-tools.js +28 -0
  147. package/dist/services/tools-config-normalizer.d.ts +37 -23
  148. package/dist/services/tools-config-normalizer.js +2 -10
  149. package/dist/services/tts/browser-provider.d.ts +38 -2
  150. package/dist/services/tts/browser-provider.js +276 -140
  151. package/dist/services/tts/catalog-span-alignment.d.ts +2 -17
  152. package/dist/services/tts/catalog-span-alignment.js +17 -142
  153. package/dist/services/tts/flat-tree.d.ts +71 -0
  154. package/dist/services/tts/flat-tree.js +269 -0
  155. package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
  156. package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
  157. package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
  158. package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
  159. package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
  160. package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
  161. package/dist/services/tts/generated-speech/types.d.ts +0 -12
  162. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +12 -62
  163. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +38 -5
  164. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +3 -6
  165. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
  166. package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
  167. package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
  168. package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
  169. package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
  170. package/dist/services/tts/math-alignment/index.d.ts +0 -5
  171. package/dist/services/tts/math-alignment/index.js +5 -25
  172. package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +9 -1
  173. package/dist/services/tts/math-alignment/speech-tokenizer.js +38 -36
  174. package/dist/services/tts/math-alignment/types.d.ts +4 -2
  175. package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
  176. package/dist/services/tts/math-aware-text-processing.js +74 -18
  177. package/dist/services/tts/math-control-names.d.ts +21 -0
  178. package/dist/services/tts/math-control-names.js +142 -0
  179. package/dist/services/tts/math-speech.d.ts +16 -1
  180. package/dist/services/tts/math-speech.js +55 -19
  181. package/dist/services/tts/provider-options.d.ts +41 -0
  182. package/dist/services/tts/provider-options.js +1 -0
  183. package/dist/services/tts/sre-engine.d.ts +7 -0
  184. package/dist/services/tts/sre-engine.js +11 -0
  185. package/dist/services/tts/sre-locales.d.ts +14 -0
  186. package/dist/services/tts/sre-locales.js +57 -0
  187. package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
  188. package/dist/services/tts/ssml/spoken-text.js +29 -0
  189. package/dist/services/tts/start-failure.d.ts +11 -0
  190. package/dist/services/tts/start-failure.js +19 -0
  191. package/dist/services/tts/text-processing.d.ts +9 -1
  192. package/dist/services/tts/text-processing.js +46 -34
  193. package/dist/services/tts/text-segmentation.d.ts +7 -0
  194. package/dist/services/tts/text-segmentation.js +47 -0
  195. package/dist/services/tts-runtime-config.d.ts +44 -18
  196. package/dist/services/tts-runtime-config.js +22 -93
  197. package/dist/tools/content-capability-resolution.d.ts +7 -9
  198. package/dist/tools/content-capability-resolution.js +8 -45
  199. package/dist/tools/registration.d.ts +58 -0
  200. package/dist/tools/registration.js +54 -0
  201. package/dist/tools/registrations/toolbar-registration-helpers.js +2 -2
  202. package/dist/tools/tool-surface-host.d.ts +6 -10
  203. package/dist/tools/tool-surface-host.js +44 -42
  204. package/dist/tools/tool-tag-map.d.ts +5 -15
  205. package/dist/tools/tool-tag-map.js +6 -20
  206. package/dist/utils/structural-equality.d.ts +11 -0
  207. package/dist/utils/structural-equality.js +47 -0
  208. package/package.json +24 -59
  209. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
  210. package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
  211. package/dist/components/chunks/ItemToolBar-byq80z42.js +0 -40
  212. package/dist/components/chunks/ItemToolBar-d36g3aw4.js +0 -49
  213. package/dist/context/runtime-context-consumer.d.ts +0 -13
  214. package/dist/context/runtime-context-consumer.js +0 -51
  215. package/dist/item-loader.d.ts +0 -44
  216. package/dist/item-loader.js +0 -61
  217. package/dist/policy/core/engine-context.d.ts +0 -29
  218. package/dist/policy/core/engine-context.js +0 -27
  219. package/dist/policy/core/policy-source-tag.d.ts +0 -31
  220. package/dist/policy/core/policy-source-tag.js +0 -28
  221. package/dist/policy/internal.d.ts +0 -25
  222. package/dist/policy/internal.js +0 -25
  223. package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
  224. package/dist/runtime/adapter/coordinator-bridge.js +0 -126
  225. package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
  226. package/dist/runtime/adapter/framework-error-bridge.js +0 -46
  227. package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
  228. package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
  229. package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
  230. package/dist/runtime/core/engine-stage-derivation.js +0 -29
  231. package/dist/runtime/engine-context.d.ts +0 -39
  232. package/dist/runtime/engine-context.js +0 -31
  233. package/dist/runtime/internal.d.ts +0 -43
  234. package/dist/runtime/internal.js +0 -45
  235. package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -80
  236. package/dist/runtime/section-runtime-engine-host-context.js +0 -95
  237. package/dist/runtime/stage-emit-gate.d.ts +0 -92
  238. package/dist/runtime/stage-emit-gate.js +0 -82
  239. package/dist/services/I18nService.d.ts +0 -47
  240. package/dist/services/I18nService.js +0 -84
  241. package/dist/services/ThemeProvider.d.ts +0 -95
  242. package/dist/services/ThemeProvider.js +0 -334
  243. package/dist/services/ToolConfigResolver.d.ts +0 -161
  244. package/dist/services/ToolConfigResolver.js +0 -229
  245. package/dist/services/catalog-media.d.ts +0 -56
  246. package/dist/services/catalog-media.js +0 -190
  247. package/dist/services/createDefaultToolRegistry.d.ts +0 -53
  248. package/dist/services/createDefaultToolRegistry.js +0 -45
  249. package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
  250. package/dist/services/defaultPersonalNeedsProfile.js +0 -26
  251. package/dist/services/pnp-standard-features.d.ts +0 -238
  252. package/dist/services/pnp-standard-features.js +0 -338
  253. package/dist/services/tool-providers/CortexToolProvider.d.ts +0 -13
  254. package/dist/services/tool-providers/CortexToolProvider.js +0 -32
  255. package/dist/services/tool-providers/DesmosToolProvider.d.ts +0 -14
  256. package/dist/services/tool-providers/DesmosToolProvider.js +0 -32
  257. package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +0 -16
  258. package/dist/services/tool-providers/GeoGebraToolProvider.js +0 -32
  259. package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +0 -35
  260. package/dist/services/tool-providers/LazyCalculatorToolProvider.js +0 -95
  261. package/dist/services/tts/visual-line-ranges.d.ts +0 -18
  262. package/dist/services/tts/visual-line-ranges.js +0 -226
  263. package/dist/tools/client.d.ts +0 -12
  264. package/dist/tools/client.js +0 -14
  265. package/dist/tools/index.d.ts +0 -8
  266. package/dist/tools/index.js +0 -10
  267. package/dist/tools/internal.d.ts +0 -43
  268. package/dist/tools/internal.js +0 -41
  269. package/dist/tools/response-discovery.d.ts +0 -61
  270. package/dist/tools/response-discovery.js +0 -113
  271. package/dist/tools/types.d.ts +0 -337
  272. package/dist/tools/types.js +0 -12
  273. package/dist/tools/variant-resolver.d.ts +0 -41
  274. package/dist/tools/variant-resolver.js +0 -207
@@ -6,6 +6,9 @@
6
6
  *
7
7
  * Part of PIE Assessment Toolkit.
8
8
  */
9
+ import { createPieLogger, isGlobalDebugEnabled, } from "@pie-players/pie-players-shared/pie";
10
+ // Lifecycle lines need `window.PIE_DEBUG = true`, read on each line.
11
+ const logger = createPieLogger("ToolProviderRegistry", isGlobalDebugEnabled);
9
12
  /**
10
13
  * Tool Provider Registry
11
14
  *
@@ -13,25 +16,27 @@
13
16
  * - Registration
14
17
  * - Lazy initialization
15
18
  * - Auth credential fetching
16
- * - Provider lookup by ID or category
19
+ * - Provider lookup by tool id or category
20
+ *
21
+ * The toolkit coordinator registers each provider under its tool's id.
17
22
  *
18
23
  * @example
19
24
  * ```typescript
20
25
  * const registry = new ToolProviderRegistry();
21
26
  *
22
27
  * // Register a provider with auth fetcher
23
- * registry.register('desmos-calculator', {
24
- * provider: new DesmosToolProvider(),
28
+ * registry.register('calculator', {
29
+ * provider: new ExampleToolProvider(),
25
30
  * config: {},
26
31
  * lazy: true,
27
32
  * authFetcher: async () => {
28
- * const response = await fetch('/api/desmos/token');
33
+ * const response = await fetch('/api/example/token');
29
34
  * return response.json();
30
35
  * },
31
36
  * });
32
37
  *
33
38
  * // Get provider (auto-initializes if lazy)
34
- * const desmosProvider = await registry.getProvider('desmos-calculator');
39
+ * const provider = await registry.getProvider('calculator');
35
40
  * ```
36
41
  */
37
42
  export class ToolProviderRegistry {
@@ -51,24 +56,35 @@ export class ToolProviderRegistry {
51
56
  * Register a tool provider
52
57
  *
53
58
  * Adds a provider to the registry. If lazy is false, initializes immediately.
59
+ * A registration under an id already registered replaces it: the replaced
60
+ * provider is destroyed once a start in progress settles, and a caller
61
+ * waiting on that start gets the replacement.
54
62
  *
55
- * @param providerId Unique provider identifier
63
+ * @param toolId Id of the tool the provider serves
56
64
  * @param config Provider configuration
57
- * @throws Error if provider with same ID already registered
58
65
  */
59
- register(providerId, config) {
60
- if (this.providers.has(providerId)) {
61
- console.warn(`[ToolProviderRegistry] Provider "${providerId}" already registered, skipping`);
62
- return;
66
+ register(toolId, config) {
67
+ const replaced = this.providers.get(toolId);
68
+ const replacedStart = this.initializationPromises.get(toolId);
69
+ this.providers.set(toolId, config.provider);
70
+ this.configs.set(toolId, config);
71
+ this.initialized.set(toolId, false);
72
+ this.initializationPromises.delete(toolId);
73
+ if (replaced) {
74
+ if (replacedStart) {
75
+ void replacedStart
76
+ .catch(() => { })
77
+ .then(() => this.destroyProvider(toolId, replaced));
78
+ }
79
+ else {
80
+ this.destroyProvider(toolId, replaced);
81
+ }
63
82
  }
64
- this.providers.set(providerId, config.provider);
65
- this.configs.set(providerId, config);
66
- this.initialized.set(providerId, false);
67
- console.log(`[ToolProviderRegistry] Registered provider "${providerId}" (${config.provider.providerName})`);
83
+ logger.debug(`${replaced ? "Replaced" : "Registered"} provider "${toolId}" (${config.provider.providerName})`);
68
84
  // Initialize immediately if not lazy
69
85
  if (config.lazy === false) {
70
- this.initialize(providerId).catch((error) => {
71
- console.error(`[ToolProviderRegistry] Failed to initialize provider "${providerId}":`, error);
86
+ this.initialize(toolId).catch((error) => {
87
+ console.error(`[ToolProviderRegistry] Failed to initialize provider "${toolId}":`, error);
72
88
  });
73
89
  }
74
90
  }
@@ -78,60 +94,73 @@ export class ToolProviderRegistry {
78
94
  * Fetches auth if needed and initializes the provider.
79
95
  * Safe to call multiple times - subsequent calls wait for first initialization.
80
96
  *
81
- * @param providerId Provider to initialize
97
+ * @param toolId Tool whose provider to initialize
82
98
  * @returns Promise that resolves when initialization complete
83
99
  * @throws Error if provider not registered or initialization fails
84
100
  */
85
- async initialize(providerId) {
101
+ async initialize(toolId) {
86
102
  // Already initialized
87
- if (this.initialized.get(providerId)) {
103
+ if (this.initialized.get(toolId)) {
88
104
  return;
89
105
  }
90
106
  // Initialization in progress - wait for it
91
- const existingPromise = this.initializationPromises.get(providerId);
107
+ const existingPromise = this.initializationPromises.get(toolId);
92
108
  if (existingPromise) {
93
109
  return existingPromise;
94
110
  }
95
111
  // Start new initialization
96
- const initPromise = this._doInitialize(providerId);
97
- this.initializationPromises.set(providerId, initPromise);
112
+ const provider = this.providers.get(toolId);
113
+ const initPromise = this._doInitialize(toolId);
114
+ this.initializationPromises.set(toolId, initPromise);
98
115
  try {
99
116
  await initPromise;
100
117
  }
118
+ catch (error) {
119
+ if (!this.isReplaced(toolId, provider))
120
+ throw error;
121
+ }
101
122
  finally {
102
- this.initializationPromises.delete(providerId);
123
+ if (this.initializationPromises.get(toolId) === initPromise) {
124
+ this.initializationPromises.delete(toolId);
125
+ }
126
+ }
127
+ if (this.isReplaced(toolId, provider)) {
128
+ return this.initialize(toolId);
129
+ }
130
+ }
131
+ /** Whether a registration replaced `provider` under `toolId`. */
132
+ isReplaced(toolId, provider) {
133
+ const current = this.providers.get(toolId);
134
+ return current !== undefined && current !== provider;
135
+ }
136
+ destroyProvider(toolId, provider) {
137
+ try {
138
+ provider.destroy();
139
+ }
140
+ catch (error) {
141
+ console.warn(`[ToolProviderRegistry] Failed to destroy provider "${toolId}":`, error);
103
142
  }
104
143
  }
105
144
  /**
106
145
  * Internal initialization logic
107
146
  */
108
- async _doInitialize(providerId) {
109
- const provider = this.providers.get(providerId);
110
- const config = this.configs.get(providerId);
147
+ async _doInitialize(toolId) {
148
+ const provider = this.providers.get(toolId);
149
+ const config = this.configs.get(toolId);
111
150
  if (!provider || !config) {
112
- throw new Error(`[ToolProviderRegistry] Provider "${providerId}" not registered`);
151
+ throw new Error(`[ToolProviderRegistry] Provider "${toolId}" not registered`);
113
152
  }
114
153
  // Start with base config
115
154
  let providerConfig = { ...config.config };
116
155
  const deriveBackend = (value) => {
117
- if (!value || typeof value !== "object")
118
- return "unknown";
119
- const typed = value;
120
- const direct = typeof typed.backend === "string"
121
- ? typed.backend
122
- : typeof typed.provider === "string"
123
- ? typed.provider
124
- : typeof typed.serverProvider === "string"
125
- ? typed.serverProvider
126
- : typeof typed.providerId === "string"
127
- ? typed.providerId
128
- : "";
129
- return direct || "unknown";
156
+ const backend = value && typeof value === "object"
157
+ ? value.backend
158
+ : undefined;
159
+ return typeof backend === "string" && backend ? backend : "unknown";
130
160
  };
131
161
  const providerInitStartedAt = Date.now();
132
162
  await this.emitTelemetry(config, "pie-tool-init-start", {
133
- toolId: providerId,
134
- providerId,
163
+ toolId,
135
164
  backend: deriveBackend(providerConfig),
136
165
  operation: "provider-initialize",
137
166
  });
@@ -139,18 +168,16 @@ export class ToolProviderRegistry {
139
168
  if (provider.requiresAuth && config.authFetcher) {
140
169
  const authFetchStartedAt = Date.now();
141
170
  await this.emitTelemetry(config, "pie-tool-backend-call-start", {
142
- toolId: providerId,
143
- providerId,
171
+ toolId,
144
172
  backend: deriveBackend(providerConfig),
145
173
  operation: "auth-fetch",
146
174
  });
147
- console.log(`[ToolProviderRegistry] Fetching auth for "${providerId}"...`);
175
+ logger.debug(`Fetching auth for "${toolId}"...`);
148
176
  try {
149
177
  const authData = await config.authFetcher();
150
178
  providerConfig = { ...providerConfig, ...authData };
151
179
  await this.emitTelemetry(config, "pie-tool-backend-call-success", {
152
- toolId: providerId,
153
- providerId,
180
+ toolId,
154
181
  backend: deriveBackend(providerConfig),
155
182
  operation: "auth-fetch",
156
183
  duration: Date.now() - authFetchStartedAt,
@@ -158,42 +185,41 @@ export class ToolProviderRegistry {
158
185
  }
159
186
  catch (error) {
160
187
  await this.emitTelemetry(config, "pie-tool-backend-call-error", {
161
- toolId: providerId,
162
- providerId,
188
+ toolId,
163
189
  backend: deriveBackend(providerConfig),
164
190
  operation: "auth-fetch",
165
191
  duration: Date.now() - authFetchStartedAt,
166
192
  errorType: "ProviderAuthFetchError",
167
193
  message: error instanceof Error ? error.message : String(error),
168
194
  });
169
- console.error(`[ToolProviderRegistry] Auth fetch failed for "${providerId}":`, error);
170
- throw new Error(`Failed to fetch auth credentials for provider "${providerId}"`);
195
+ console.error(`[ToolProviderRegistry] Auth fetch failed for "${toolId}":`, error);
196
+ throw new Error(`Failed to fetch auth credentials for provider "${toolId}"`);
171
197
  }
172
198
  }
173
199
  // Initialize provider
174
200
  try {
175
201
  await provider.initialize(providerConfig);
176
- this.initialized.set(providerId, true);
202
+ if (this.providers.get(toolId) === provider) {
203
+ this.initialized.set(toolId, true);
204
+ }
177
205
  await this.emitTelemetry(config, "pie-tool-init-success", {
178
- toolId: providerId,
179
- providerId,
206
+ toolId,
180
207
  backend: deriveBackend(providerConfig),
181
208
  operation: "provider-initialize",
182
209
  duration: Date.now() - providerInitStartedAt,
183
210
  });
184
- console.log(`[ToolProviderRegistry] Provider "${providerId}" initialized`);
211
+ logger.debug(`Provider "${toolId}" initialized`);
185
212
  }
186
213
  catch (error) {
187
214
  await this.emitTelemetry(config, "pie-tool-init-error", {
188
- toolId: providerId,
189
- providerId,
215
+ toolId,
190
216
  backend: deriveBackend(providerConfig),
191
217
  operation: "provider-initialize",
192
218
  duration: Date.now() - providerInitStartedAt,
193
219
  errorType: "ProviderInitializationError",
194
220
  message: error instanceof Error ? error.message : String(error),
195
221
  });
196
- console.error(`[ToolProviderRegistry] Initialization failed for "${providerId}":`, error);
222
+ console.error(`[ToolProviderRegistry] Initialization failed for "${toolId}":`, error);
197
223
  throw error;
198
224
  }
199
225
  }
@@ -203,44 +229,31 @@ export class ToolProviderRegistry {
203
229
  * Retrieves a registered provider. If autoInitialize is true and provider
204
230
  * is not initialized, initializes it first.
205
231
  *
206
- * @param providerId Provider identifier
232
+ * @param toolId Id of the tool the provider serves
207
233
  * @param autoInitialize Auto-initialize if not ready (default: true)
208
234
  * @returns Provider instance
209
235
  * @throws Error if provider not registered
210
236
  */
211
- async getProvider(providerId, autoInitialize = true) {
212
- console.log("[ToolProviderRegistry] getProvider called", {
213
- providerId,
214
- autoInitialize,
215
- isRegistered: this.providers.has(providerId),
216
- isInitialized: this.initialized.get(providerId),
217
- allProviderIds: Array.from(this.providers.keys()),
218
- });
219
- const provider = this.providers.get(providerId);
237
+ async getProvider(toolId, autoInitialize = true) {
238
+ const provider = this.providers.get(toolId);
220
239
  if (!provider) {
221
240
  console.error("[ToolProviderRegistry] Provider not found:", {
222
- requestedId: providerId,
241
+ requestedId: toolId,
223
242
  availableProviders: Array.from(this.providers.keys()),
224
243
  });
225
- throw new Error(`[ToolProviderRegistry] Provider "${providerId}" not registered`);
244
+ throw new Error(`[ToolProviderRegistry] Provider "${toolId}" not registered`);
226
245
  }
227
246
  // Auto-initialize if needed
228
- if (autoInitialize && !this.initialized.get(providerId)) {
229
- console.log("[ToolProviderRegistry] Auto-initializing provider:", providerId);
230
- await this.initialize(providerId);
247
+ if (autoInitialize && !this.initialized.get(toolId)) {
248
+ logger.debug("Auto-initializing provider:", toolId);
249
+ await this.initialize(toolId);
231
250
  }
232
- console.log("[ToolProviderRegistry] Returning provider:", {
233
- providerId,
234
- providerName: provider.providerName,
235
- isReady: provider.isReady(),
236
- });
237
251
  return provider;
238
252
  }
239
253
  /**
240
- * Get all providers in a category
254
+ * The ids of the tools whose provider is in `category`.
241
255
  *
242
256
  * @param category Tool category to filter by
243
- * @returns Array of provider IDs in the category
244
257
  */
245
258
  getProvidersByCategory(category) {
246
259
  return Array.from(this.providers.entries())
@@ -248,9 +261,7 @@ export class ToolProviderRegistry {
248
261
  .map(([id]) => id);
249
262
  }
250
263
  /**
251
- * Get all registered provider IDs
252
- *
253
- * @returns Array of all provider IDs
264
+ * The ids of the tools with a registered provider.
254
265
  */
255
266
  getProviderIds() {
256
267
  return Array.from(this.providers.keys());
@@ -258,42 +269,42 @@ export class ToolProviderRegistry {
258
269
  /**
259
270
  * Check if provider is registered
260
271
  *
261
- * @param providerId Provider identifier
272
+ * @param toolId Id of the tool the provider serves
262
273
  * @returns true if provider is registered
263
274
  */
264
- has(providerId) {
265
- return this.providers.has(providerId);
275
+ has(toolId) {
276
+ return this.providers.has(toolId);
266
277
  }
267
278
  /**
268
279
  * Check if provider is initialized
269
280
  *
270
- * @param providerId Provider identifier
281
+ * @param toolId Id of the tool the provider serves
271
282
  * @returns true if provider is initialized
272
283
  */
273
- isInitialized(providerId) {
274
- return this.initialized.get(providerId) === true;
284
+ isInitialized(toolId) {
285
+ return this.initialized.get(toolId) === true;
275
286
  }
276
287
  /**
277
288
  * Check if provider is currently initializing
278
289
  *
279
- * @param providerId Provider identifier
290
+ * @param toolId Id of the tool the provider serves
280
291
  * @returns true if initialization in progress
281
292
  */
282
- isInitializing(providerId) {
283
- return this.initializationPromises.has(providerId);
293
+ isInitializing(toolId) {
294
+ return this.initializationPromises.has(toolId);
284
295
  }
285
296
  /**
286
297
  * Unregister and destroy a provider
287
298
  *
288
299
  * Removes the provider from the registry and calls its destroy method.
289
300
  *
290
- * @param providerId Provider to unregister
301
+ * @param toolId Tool whose provider to unregister
291
302
  */
292
- async unregister(providerId) {
293
- const provider = this.providers.get(providerId);
303
+ async unregister(toolId) {
304
+ const provider = this.providers.get(toolId);
294
305
  if (provider) {
295
306
  // Wait for any pending initialization
296
- const initPromise = this.initializationPromises.get(providerId);
307
+ const initPromise = this.initializationPromises.get(toolId);
297
308
  if (initPromise) {
298
309
  try {
299
310
  await initPromise;
@@ -301,15 +312,18 @@ export class ToolProviderRegistry {
301
312
  catch {
302
313
  // Ignore initialization errors during unregister
303
314
  }
315
+ // A provider registered meanwhile owns the slot, and register()
316
+ // already destroys this one once its start settles.
317
+ if (this.providers.get(toolId) !== provider)
318
+ return;
304
319
  }
305
- // Destroy provider
306
- provider.destroy();
320
+ this.destroyProvider(toolId, provider);
307
321
  // Remove from registry
308
- this.providers.delete(providerId);
309
- this.configs.delete(providerId);
310
- this.initialized.delete(providerId);
311
- this.initializationPromises.delete(providerId);
312
- console.log(`[ToolProviderRegistry] Unregistered provider "${providerId}"`);
322
+ this.providers.delete(toolId);
323
+ this.configs.delete(toolId);
324
+ this.initialized.delete(toolId);
325
+ this.initializationPromises.delete(toolId);
326
+ logger.debug(`Unregistered provider "${toolId}"`);
313
327
  }
314
328
  }
315
329
  /**
@@ -318,8 +332,8 @@ export class ToolProviderRegistry {
318
332
  * Unregisters and destroys all providers in the registry.
319
333
  */
320
334
  async destroy() {
321
- const providerIds = Array.from(this.providers.keys());
322
- await Promise.all(providerIds.map((id) => this.unregister(id)));
323
- console.log("[ToolProviderRegistry] Registry destroyed");
335
+ const toolIds = Array.from(this.providers.keys());
336
+ await Promise.all(toolIds.map((id) => this.unregister(id)));
337
+ logger.debug("Registry destroyed");
324
338
  }
325
339
  }
@@ -6,14 +6,5 @@
6
6
  *
7
7
  * Part of PIE Assessment Toolkit.
8
8
  */
9
- export type { ToolProviderApi, ToolCategory, ToolProviderCapabilities, } from "./ToolProviderApi.js";
10
9
  export { ToolProviderRegistry } from "./ToolProviderRegistry.js";
11
- export type { ToolProviderConfig } from "./ToolProviderRegistry.js";
12
- export { DesmosToolProvider } from "./DesmosToolProvider.js";
13
- export type { DesmosToolProviderConfig } from "./DesmosToolProvider.js";
14
- export { CortexToolProvider } from "./CortexToolProvider.js";
15
- export type { CortexToolProviderConfig } from "./CortexToolProvider.js";
16
- export { GeoGebraToolProvider } from "./GeoGebraToolProvider.js";
17
- export type { GeoGebraToolProviderConfig } from "./GeoGebraToolProvider.js";
18
10
  export { TTSToolProvider } from "./TTSToolProvider.js";
19
- export type { TTSToolProviderConfig, TTSBackend, } from "./TTSToolProvider.js";
@@ -8,8 +8,6 @@
8
8
  */
9
9
  // Registry
10
10
  export { ToolProviderRegistry } from "./ToolProviderRegistry.js";
11
- // Concrete providers
12
- export { DesmosToolProvider } from "./DesmosToolProvider.js";
13
- export { CortexToolProvider } from "./CortexToolProvider.js";
14
- export { GeoGebraToolProvider } from "./GeoGebraToolProvider.js";
11
+ // Concrete providers. The calculator adapters live in the composition layer,
12
+ // `@pie-players/pie-default-tool-loaders`, which owns the engine imports.
15
13
  export { TTSToolProvider } from "./TTSToolProvider.js";
@@ -42,17 +42,26 @@ export interface ToolOpenRequest {
42
42
  * to match a placement made elsewhere is a step it has no reason to expect.
43
43
  *
44
44
  * At `"item"` and `"passage"` a section holds one target per card, and the first
45
- * registered one that hosts the tool claims the request. A requester that needs a
46
- * particular card's instance cannot express that here, and the gateway PIE ships
47
- * does not need to: the strip is a section-scoped singleton acting on passage
48
- * selections, so the selection belongs to no card, and what opens is a floating
49
- * shell rather than anything rendered inside one.
45
+ * registered one that hosts the tool claims the request unless `scopeId` names
46
+ * the card.
50
47
  */
51
48
  level?: ToolPlacementLevel;
49
+ /**
50
+ * Scope of the toolbar that should open the tool: the item's canonical id for
51
+ * an item toolbar, the section id for a section toolbar. A constraint like
52
+ * `level`, for a requester that belongs to one card, as an inline control in an
53
+ * item does. Only a target registered with the same `scopeId` claims it.
54
+ */
55
+ scopeId?: string;
52
56
  }
53
57
  export interface ToolRequestTarget {
54
58
  /** The placement level this toolbar renders. */
55
59
  level: ToolPlacementLevel;
60
+ /**
61
+ * The scope this toolbar renders for, matched against a request's `scopeId`.
62
+ * A target without one claims only requests that name no scope.
63
+ */
64
+ readonly scopeId?: string;
56
65
  /** Whether this toolbar currently renders the tool, per its own policy pass. */
57
66
  hostsTool: (toolId: string) => boolean;
58
67
  /**
@@ -80,16 +89,17 @@ export declare class ToolRequestRegistry {
80
89
  * nothing is worse than an absent one, and availability moves with policy —
81
90
  * hence {@link onTargetsChange}.
82
91
  */
83
- canRequest(toolId: string, level?: ToolPlacementLevel): boolean;
92
+ canRequest(toolId: string, level?: ToolPlacementLevel, scopeId?: string): boolean;
84
93
  /** Returns whether a target claimed the request. */
85
94
  request(request: ToolOpenRequest): boolean;
86
95
  /**
87
- * Fires when a toolbar registers or unregisters.
96
+ * Fires when a toolbar registers or unregisters, and on
97
+ * {@link notifyTargetsChange}.
88
98
  *
89
- * Not when a registered toolbar's own visible set changes: `hostsTool` is read
90
- * live, so a caller re-asking `canRequest` gets the current answer. A surface
91
- * that needs to notice a policy change should also follow the policy signal it
92
- * already has.
99
+ * Not when a policy change moves a registered toolbar's visible set: `hostsTool`
100
+ * is read live, so a caller re-asking `canRequest` gets the current answer. A
101
+ * surface that needs to notice a policy change should also follow the policy
102
+ * signal it already has.
93
103
  */
94
104
  onTargetsChange(listener: () => void): () => void;
95
105
  /**
@@ -102,6 +112,11 @@ export declare class ToolRequestRegistry {
102
112
  private findTarget;
103
113
  private findTargetAtLevel;
104
114
  private hostsTool;
105
- private notifyChange;
115
+ /**
116
+ * Tell change listeners that what the targets can claim changed. Registration
117
+ * calls it, and so does the coordinator when a target stops hosting a tool
118
+ * outside a policy change, as when the tool's module fails to load.
119
+ */
120
+ notifyTargetsChange(): void;
106
121
  dispose(): void;
107
122
  }
@@ -32,11 +32,11 @@ export class ToolRequestRegistry {
32
32
  changeListeners = new Set();
33
33
  registerTarget(target) {
34
34
  this.targets.add(target);
35
- this.notifyChange();
35
+ this.notifyTargetsChange();
36
36
  return () => {
37
37
  if (!this.targets.delete(target))
38
38
  return;
39
- this.notifyChange();
39
+ this.notifyTargetsChange();
40
40
  };
41
41
  }
42
42
  /**
@@ -46,12 +46,12 @@ export class ToolRequestRegistry {
46
46
  * nothing is worse than an absent one, and availability moves with policy —
47
47
  * hence {@link onTargetsChange}.
48
48
  */
49
- canRequest(toolId, level) {
50
- return this.findTarget(toolId, level) !== null;
49
+ canRequest(toolId, level, scopeId) {
50
+ return this.findTarget(toolId, level, scopeId) !== null;
51
51
  }
52
52
  /** Returns whether a target claimed the request. */
53
53
  request(request) {
54
- const target = this.findTarget(request.toolId, request.level);
54
+ const target = this.findTarget(request.toolId, request.level, request.scopeId);
55
55
  if (!target)
56
56
  return false;
57
57
  try {
@@ -64,12 +64,13 @@ export class ToolRequestRegistry {
64
64
  return true;
65
65
  }
66
66
  /**
67
- * Fires when a toolbar registers or unregisters.
67
+ * Fires when a toolbar registers or unregisters, and on
68
+ * {@link notifyTargetsChange}.
68
69
  *
69
- * Not when a registered toolbar's own visible set changes: `hostsTool` is read
70
- * live, so a caller re-asking `canRequest` gets the current answer. A surface
71
- * that needs to notice a policy change should also follow the policy signal it
72
- * already has.
70
+ * Not when a policy change moves a registered toolbar's visible set: `hostsTool`
71
+ * is read live, so a caller re-asking `canRequest` gets the current answer. A
72
+ * surface that needs to notice a policy change should also follow the policy
73
+ * signal it already has.
73
74
  */
74
75
  onTargetsChange(listener) {
75
76
  this.changeListeners.add(listener);
@@ -84,22 +85,26 @@ export class ToolRequestRegistry {
84
85
  * and still have a section-scoped gateway reach it. Requesting a level explicitly
85
86
  * does not fall back, because a requester that named one meant it.
86
87
  */
87
- findTarget(toolId, level) {
88
- const preferred = this.findTargetAtLevel(toolId, level ?? DEFAULT_TOOL_REQUEST_LEVEL);
88
+ findTarget(toolId, level, scopeId) {
89
+ const preferred = this.findTargetAtLevel(toolId, level ?? DEFAULT_TOOL_REQUEST_LEVEL, scopeId);
89
90
  if (preferred || level !== undefined)
90
91
  return preferred;
91
92
  for (const target of this.targets) {
92
93
  if (target.level === DEFAULT_TOOL_REQUEST_LEVEL)
93
94
  continue;
95
+ if (scopeId !== undefined && target.scopeId !== scopeId)
96
+ continue;
94
97
  if (this.hostsTool(target, toolId))
95
98
  return target;
96
99
  }
97
100
  return null;
98
101
  }
99
- findTargetAtLevel(toolId, level) {
102
+ findTargetAtLevel(toolId, level, scopeId) {
100
103
  for (const target of this.targets) {
101
104
  if (target.level !== level)
102
105
  continue;
106
+ if (scopeId !== undefined && target.scopeId !== scopeId)
107
+ continue;
103
108
  if (this.hostsTool(target, toolId))
104
109
  return target;
105
110
  }
@@ -114,7 +119,12 @@ export class ToolRequestRegistry {
114
119
  return false;
115
120
  }
116
121
  }
117
- notifyChange() {
122
+ /**
123
+ * Tell change listeners that what the targets can claim changed. Registration
124
+ * calls it, and so does the coordinator when a target stops hosting a tool
125
+ * outside a policy change, as when the tool's module fails to load.
126
+ */
127
+ notifyTargetsChange() {
118
128
  for (const listener of this.changeListeners) {
119
129
  try {
120
130
  listener();
@@ -0,0 +1,15 @@
1
+ import type { ToolPolicyDecision } from "../policy/core/decision-types.js";
2
+ /** What a toolbar renders from in a policy decision, per visible tool. */
3
+ export interface DecidedToolbarTool {
4
+ toolId: string;
5
+ required: boolean;
6
+ alwaysAvailable: boolean;
7
+ }
8
+ /**
9
+ * A toolbar's decided tools across re-decisions. The tracker returns its
10
+ * previous list while `renderVersion` is unchanged and the decision shows the
11
+ * same tools, so a re-decision that changes nothing the toolbar renders from
12
+ * keeps the rendered tools: rendering swaps in fresh tool elements, whose state
13
+ * starts over. A new `renderVersion` always yields a new list.
14
+ */
15
+ export declare function createDecidedToolsTracker(): (decision: ToolPolicyDecision | null, renderVersion: number) => readonly DecidedToolbarTool[] | null;
@@ -0,0 +1,28 @@
1
+ import { structurallyEqual } from "../utils/structural-equality.js";
2
+ /**
3
+ * A toolbar's decided tools across re-decisions. The tracker returns its
4
+ * previous list while `renderVersion` is unchanged and the decision shows the
5
+ * same tools, so a re-decision that changes nothing the toolbar renders from
6
+ * keeps the rendered tools: rendering swaps in fresh tool elements, whose state
7
+ * starts over. A new `renderVersion` always yields a new list.
8
+ */
9
+ export function createDecidedToolsTracker() {
10
+ let last = null;
11
+ return (decision, renderVersion) => {
12
+ if (!decision) {
13
+ last = null;
14
+ return null;
15
+ }
16
+ const tools = decision.visibleTools.map((entry) => ({
17
+ toolId: entry.toolId,
18
+ required: entry.required,
19
+ alwaysAvailable: entry.alwaysAvailable,
20
+ }));
21
+ if (last?.renderVersion === renderVersion &&
22
+ structurallyEqual(last.tools, tools)) {
23
+ return last.tools;
24
+ }
25
+ last = { renderVersion, tools };
26
+ return tools;
27
+ };
28
+ }