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