@genesislcap/ai-assistant 15.19.0 → 15.19.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 (288) hide show
  1. package/package.json +17 -18
  2. package/api-extractor.json +0 -11
  3. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
  4. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
  5. package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
  6. package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
  7. package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
  8. package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
  9. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
  10. package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
  11. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
  12. package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
  13. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
  14. package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
  15. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
  16. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
  17. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
  18. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
  19. package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
  20. package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
  21. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
  22. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
  23. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
  24. package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
  25. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
  26. package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
  27. package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
  28. package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
  29. package/dist/dts/config/validate-providers.test.d.ts +0 -2
  30. package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
  31. package/dist/dts/main/blocked-state.test.d.ts +0 -2
  32. package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
  33. package/dist/dts/main/budget-meter.test.d.ts +0 -2
  34. package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
  35. package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
  36. package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
  37. package/dist/dts/main/file-attachments.test.d.ts +0 -2
  38. package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
  39. package/dist/dts/main/interaction-cost.test.d.ts +0 -2
  40. package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
  41. package/dist/dts/main/main.styles.test.d.ts +0 -2
  42. package/dist/dts/main/main.styles.test.d.ts.map +0 -1
  43. package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
  44. package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
  45. package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
  46. package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
  47. package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
  48. package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
  49. package/dist/dts/state/debug-event-log.test.d.ts +0 -2
  50. package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
  51. package/dist/dts/state/driver-registry.test.d.ts +0 -2
  52. package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
  53. package/dist/dts/state/interaction-context.test.d.ts +0 -2
  54. package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
  55. package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
  56. package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
  57. package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
  58. package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
  59. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
  60. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
  61. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
  62. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
  63. package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
  64. package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
  65. package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
  66. package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
  67. package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
  68. package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
  69. package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
  70. package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
  71. package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
  72. package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
  73. package/dist/dts/utils/condense-history.test.d.ts +0 -2
  74. package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
  75. package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
  76. package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
  77. package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
  78. package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
  79. package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
  80. package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
  81. package/dist/dts/utils/history-transform.test.d.ts +0 -2
  82. package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
  83. package/dist/dts/utils/message-partition.test.d.ts +0 -2
  84. package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
  85. package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
  86. package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
  87. package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
  88. package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
  89. package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
  90. package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
  91. package/dist/dts/utils/sum-costs.test.d.ts +0 -2
  92. package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
  93. package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
  94. package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
  95. package/dist/dts/utils/sum-usage.test.d.ts +0 -2
  96. package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
  97. package/dist/dts/utils/usage-rows.test.d.ts +0 -2
  98. package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
  99. package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
  100. package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
  101. package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
  102. package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
  103. package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
  104. package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
  105. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
  106. package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
  107. package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
  108. package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
  109. package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
  110. package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
  111. package/dist/esm/config/define-stateful-agent.test.js +0 -53
  112. package/dist/esm/config/validate-providers.test.js +0 -100
  113. package/dist/esm/main/blocked-state.test.js +0 -1065
  114. package/dist/esm/main/budget-meter.test.js +0 -359
  115. package/dist/esm/main/cost-session-banking.test.js +0 -538
  116. package/dist/esm/main/file-attachments.test.js +0 -164
  117. package/dist/esm/main/interaction-cost.test.js +0 -107
  118. package/dist/esm/main/main.styles.test.js +0 -161
  119. package/dist/esm/main/persistence-broken-sources.test.js +0 -180
  120. package/dist/esm/main/popout-interaction-gate.test.js +0 -54
  121. package/dist/esm/state/ai-assistant-slice.test.js +0 -229
  122. package/dist/esm/state/debug-event-log.test.js +0 -117
  123. package/dist/esm/state/driver-registry.test.js +0 -55
  124. package/dist/esm/state/interaction-context.test.js +0 -78
  125. package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
  126. package/dist/esm/state/persistence/diagnostics.test.js +0 -280
  127. package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
  128. package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
  129. package/dist/esm/state/persistence/session-persister.test.js +0 -613
  130. package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
  131. package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
  132. package/dist/esm/utils/animation-exclusivity.test.js +0 -52
  133. package/dist/esm/utils/collect-session-models.test.js +0 -53
  134. package/dist/esm/utils/condense-history.test.js +0 -547
  135. package/dist/esm/utils/cost-session-history.test.js +0 -174
  136. package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
  137. package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
  138. package/dist/esm/utils/history-transform.test.js +0 -158
  139. package/dist/esm/utils/message-partition.test.js +0 -90
  140. package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
  141. package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
  142. package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
  143. package/dist/esm/utils/sum-costs.test.js +0 -114
  144. package/dist/esm/utils/sum-tokens.test.js +0 -51
  145. package/dist/esm/utils/sum-usage.test.js +0 -164
  146. package/dist/esm/utils/usage-rows.test.js +0 -189
  147. package/dist/tsconfig.tsbuildinfo +0 -1
  148. package/license.txt +0 -46
  149. package/sandbox/tsconfig.json +0 -8
  150. package/src/channel/ai-activity-bus.ts +0 -209
  151. package/src/channel/ai-activity-channel.ts +0 -146
  152. package/src/chat-driver-node.ts +0 -159
  153. package/src/components/activity-halo/activity-halo.ts +0 -106
  154. package/src/components/agent-picker/agent-picker.constants.ts +0 -2
  155. package/src/components/agent-picker/agent-picker.styles.ts +0 -24
  156. package/src/components/agent-picker/agent-picker.template.ts +0 -54
  157. package/src/components/agent-picker/agent-picker.ts +0 -99
  158. package/src/components/agent-picker/index.ts +0 -1
  159. package/src/components/ai-driver/ai-driver.ts +0 -166
  160. package/src/components/ai-driver/index.ts +0 -1
  161. package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
  162. package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
  163. package/src/components/chat-bubble/chat-bubble.ts +0 -436
  164. package/src/components/chat-bubble/index.ts +0 -1
  165. package/src/components/chat-driver/align-event-globals.ts +0 -23
  166. package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
  167. package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
  168. package/src/components/chat-driver/chat-driver.test.ts +0 -3679
  169. package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
  170. package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
  171. package/src/components/chat-driver/chat-driver.ts +0 -3925
  172. package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
  173. package/src/components/chat-driver/index.ts +0 -1
  174. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
  175. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
  176. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
  177. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
  178. package/src/components/chat-interaction-wrapper/index.ts +0 -3
  179. package/src/components/chat-markdown/chat-markdown.ts +0 -89
  180. package/src/components/chat-markdown/index.ts +0 -1
  181. package/src/components/flowing-waves-indicator.ts +0 -286
  182. package/src/components/halo-overlay.ts +0 -184
  183. package/src/components/orchestrating-driver/index.ts +0 -1
  184. package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
  185. package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
  186. package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
  187. package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
  188. package/src/components/plasma-orb-indicator.ts +0 -281
  189. package/src/components/popout-manager/index.ts +0 -1
  190. package/src/components/popout-manager/popout-manager.ts +0 -212
  191. package/src/components/settings-modal/index.ts +0 -2
  192. package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
  193. package/src/components/settings-modal/settings-modal.styles.ts +0 -853
  194. package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
  195. package/src/components/settings-modal/settings-modal.template.ts +0 -943
  196. package/src/components/waves-indicator.ts +0 -241
  197. package/src/config/config.ts +0 -608
  198. package/src/config/define-stateful-agent.test.ts +0 -64
  199. package/src/config/define-stateful-agent.ts +0 -642
  200. package/src/config/fallback-agents.ts +0 -29
  201. package/src/config/index.ts +0 -2
  202. package/src/config/validate-providers.test.ts +0 -148
  203. package/src/config/validate-providers.ts +0 -58
  204. package/src/index.ts +0 -29
  205. package/src/main/blocked-state.test.ts +0 -1444
  206. package/src/main/budget-meter.test.ts +0 -477
  207. package/src/main/cost-session-banking.test.ts +0 -679
  208. package/src/main/file-attachments.test.ts +0 -215
  209. package/src/main/index.ts +0 -3
  210. package/src/main/interaction-cost.test.ts +0 -140
  211. package/src/main/main.styles.test.ts +0 -216
  212. package/src/main/main.styles.ts +0 -995
  213. package/src/main/main.template.ts +0 -1119
  214. package/src/main/main.ts +0 -5432
  215. package/src/main/main.types.ts +0 -225
  216. package/src/main/persistence-broken-sources.test.ts +0 -219
  217. package/src/main/popout-interaction-gate.test.ts +0 -87
  218. package/src/provider/ai-provider-switcher.ts +0 -40
  219. package/src/provider/assistant-app-settings.ts +0 -84
  220. package/src/state/ai-assistant-slice.test.ts +0 -336
  221. package/src/state/ai-assistant-slice.ts +0 -604
  222. package/src/state/debug-event-log.test.ts +0 -154
  223. package/src/state/debug-event-log.ts +0 -358
  224. package/src/state/driver-registry.test.ts +0 -70
  225. package/src/state/driver-registry.ts +0 -79
  226. package/src/state/interaction-context.test.ts +0 -90
  227. package/src/state/interaction-context.ts +0 -101
  228. package/src/state/persistence/build-timeline-entries.ts +0 -66
  229. package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
  230. package/src/state/persistence/diagnostics-cursors.ts +0 -80
  231. package/src/state/persistence/diagnostics.test.ts +0 -346
  232. package/src/state/persistence/diagnostics.ts +0 -220
  233. package/src/state/persistence/index.ts +0 -10
  234. package/src/state/persistence/persister-registry.ts +0 -55
  235. package/src/state/persistence/session-persistence-provider.test.ts +0 -136
  236. package/src/state/persistence/session-persistence-provider.ts +0 -226
  237. package/src/state/persistence/session-persistence.integration.test.ts +0 -141
  238. package/src/state/persistence/session-persister.test.ts +0 -787
  239. package/src/state/persistence/session-persister.ts +0 -917
  240. package/src/state/persistence/session-snapshot.test.ts +0 -352
  241. package/src/state/persistence/session-snapshot.ts +0 -344
  242. package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
  243. package/src/state/session-store.ts +0 -56
  244. package/src/styles/ai-colours.ts +0 -30
  245. package/src/styles/menu.ts +0 -187
  246. package/src/styles/settings-section.ts +0 -40
  247. package/src/suggestions/chat-suggestions.ts +0 -186
  248. package/src/tags/index.ts +0 -1
  249. package/src/types/ai-chat-widget.ts +0 -67
  250. package/src/types/interaction-context.ts +0 -82
  251. package/src/utils/animated-panel-toggle.ts +0 -62
  252. package/src/utils/animation-exclusivity.test.ts +0 -72
  253. package/src/utils/animation-exclusivity.ts +0 -40
  254. package/src/utils/banked-usage-baselines.ts +0 -90
  255. package/src/utils/collect-session-models.test.ts +0 -82
  256. package/src/utils/collect-session-models.ts +0 -65
  257. package/src/utils/condense-history.test.ts +0 -746
  258. package/src/utils/condense-history.ts +0 -448
  259. package/src/utils/cost-session-history.test.ts +0 -226
  260. package/src/utils/cost-session-history.ts +0 -218
  261. package/src/utils/derive-cost-session-title.test.ts +0 -63
  262. package/src/utils/derive-cost-session-title.ts +0 -76
  263. package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
  264. package/src/utils/flatten-sub-agent-messages.ts +0 -88
  265. package/src/utils/format-usd.ts +0 -26
  266. package/src/utils/history-transform.test.ts +0 -228
  267. package/src/utils/history-transform.ts +0 -163
  268. package/src/utils/index.ts +0 -3
  269. package/src/utils/logger.ts +0 -3
  270. package/src/utils/message-partition.test.ts +0 -129
  271. package/src/utils/message-partition.ts +0 -115
  272. package/src/utils/resolve-cost-history-config.test.ts +0 -38
  273. package/src/utils/resolve-cost-history-config.ts +0 -38
  274. package/src/utils/resolve-preference-baseline.test.ts +0 -84
  275. package/src/utils/resolve-preference-baseline.ts +0 -80
  276. package/src/utils/strip-agent-handlers.test.ts +0 -99
  277. package/src/utils/strip-agent-handlers.ts +0 -53
  278. package/src/utils/sum-costs.test.ts +0 -136
  279. package/src/utils/sum-costs.ts +0 -18
  280. package/src/utils/sum-tokens.test.ts +0 -93
  281. package/src/utils/sum-tokens.ts +0 -68
  282. package/src/utils/sum-usage.test.ts +0 -191
  283. package/src/utils/sum-usage.ts +0 -208
  284. package/src/utils/tool-fold.ts +0 -181
  285. package/src/utils/usage-rows.test.ts +0 -237
  286. package/src/utils/usage-rows.ts +0 -187
  287. package/src/utils/with-timeout.ts +0 -24
  288. package/tsconfig.json +0 -9
@@ -1,104 +0,0 @@
1
- import { __awaiter } from "tslib";
2
- import { assert, createComponentSuite } from '@genesislcap/foundation-testing';
3
- import { DOM } from '@genesislcap/web-core';
4
- import { AiChatInteractionWrapper } from './chat-interaction-wrapper';
5
- // Hold a reference so the custom-element registration isn't tree-shaken.
6
- AiChatInteractionWrapper;
7
- /**
8
- * Minimal stand-in for a real interaction widget. The wrapper instantiates the
9
- * element named by `componentName` and assigns `interactionId`/`interaction` onto
10
- * it (GENC-1390); the tests read `interaction` back to verify which interaction the
11
- * rendered widget belongs to and its pending/resolved state.
12
- */
13
- const FAKE_WIDGET = 'test-interaction-widget';
14
- if (!customElements.get(FAKE_WIDGET)) {
15
- customElements.define(FAKE_WIDGET, class extends HTMLElement {
16
- });
17
- }
18
- /**
19
- * Renders a first interaction into a freshly-mounted wrapper.
20
- *
21
- * `componentName` is assigned last on purpose. The suite hands us an
22
- * already-connected element, so the only render trigger left is
23
- * `componentNameChanged`; setting it last means that render sees `data` and
24
- * `interactionId` already in place — reproducing production, where the initial
25
- * widget renders in `connectedCallback` with every bound property present. This
26
- * keeps the *first* render correct on both the patched and unpatched code so
27
- * that the id-change rebind is the only behaviour each test isolates.
28
- */
29
- function renderFirstInteraction(element) {
30
- return __awaiter(this, void 0, void 0, function* () {
31
- yield DOM.nextUpdate();
32
- element.data = { label: 'interaction-a' };
33
- element.interactionId = 'id-a';
34
- element.componentName = FAKE_WIDGET;
35
- yield DOM.nextUpdate();
36
- });
37
- }
38
- const Suite = createComponentSuite('AiChatInteractionWrapper', 'ai-chat-interaction-wrapper');
39
- /**
40
- * Regression for the cog-menu toggle misalignment: FAST's `repeat` reconciles
41
- * the message list by array index (it has no key function), so toggling "Tool
42
- * calls"/"Thinking" re-filters the visible messages and rebinds an existing
43
- * row's wrapper to a *different* interaction. When both interactions use the
44
- * same component, `componentNameChanged` doesn't fire — so the wrapper must
45
- * re-render on `interactionId` change, or the previous interaction's widget
46
- * stays mounted under the new message.
47
- */
48
- Suite('re-renders the hosted widget when interactionId changes with the same componentName', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
49
- var _b, _c;
50
- yield renderFirstInteraction(element);
51
- const firstWidget = element.container.firstElementChild;
52
- assert.ok(firstWidget, 'a widget is rendered for the first interaction');
53
- assert.equal((_b = firstWidget.interaction) === null || _b === void 0 ? void 0 : _b.data, { label: 'interaction-a' });
54
- // Rebind the reused wrapper to a different interaction that happens to use
55
- // the same component — `data` then `interactionId`, mirroring the template's
56
- // property-binding order. `componentName` is unchanged, so the only thing
57
- // that can drive a re-render is the interactionId hook.
58
- element.data = { label: 'interaction-b' };
59
- element.interactionId = 'id-b';
60
- yield DOM.nextUpdate();
61
- const secondWidget = element.container.firstElementChild;
62
- assert.is.not(secondWidget, firstWidget, 'the stale widget is replaced, not left mounted');
63
- assert.equal((_c = secondWidget.interaction) === null || _c === void 0 ? void 0 : _c.data, { label: 'interaction-b' }, 'the rendered widget reflects the new interaction, not the stale one');
64
- }));
65
- /**
66
- * Resolving an interaction in place (the user answers it) must forward the
67
- * result without tearing down and rebuilding the widget — only a genuine change
68
- * of interaction identity should rebuild.
69
- */
70
- Suite('forwards resolved in place without rebuilding the widget', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
71
- var _b, _c, _d;
72
- yield renderFirstInteraction(element);
73
- const widget = element.container.firstElementChild;
74
- assert.is((_b = widget.interaction) === null || _b === void 0 ? void 0 : _b.status, 'pending', 'the widget starts pending');
75
- element.resolved = { status: 'approved' };
76
- yield DOM.nextUpdate();
77
- assert.is(element.container.firstElementChild, widget, 'resolving in place must not rebuild the widget');
78
- assert.is((_c = widget.interaction) === null || _c === void 0 ? void 0 : _c.status, 'resolved', 'the widget transitions to resolved in place');
79
- assert.equal(((_d = widget.interaction) === null || _d === void 0 ? void 0 : _d.status) === 'resolved' ? widget.interaction.result : undefined, { status: 'approved' }, 'the resolved result is forwarded to the widget');
80
- }));
81
- /**
82
- * When no live `InteractionContext` exists for the interaction — it has resolved and the driver
83
- * dropped it (the `resolved` binding can lag a render behind during a layout rebuild), or the widget
84
- * is rendered outside the normal host flow (as here: no host on the root) — the wrapper must hand the
85
- * widget an INERT context whose `once` refuses, never a live throwaway. A live throwaway re-ran the
86
- * widget's open-once side effect on the remount following a resolve, re-opening a just-closed side
87
- * panel and looping the layout rebuild (GENC-1390).
88
- */
89
- Suite('falls back to an inert context (once refuses) when there is no live context', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
90
- var _b, _c;
91
- yield renderFirstInteraction(element);
92
- const widget = element.container.firstElementChild;
93
- assert.is((_b = widget.interaction) === null || _b === void 0 ? void 0 : _b.status, 'pending', 'the widget is pending');
94
- const ctx = ((_c = widget.interaction) === null || _c === void 0 ? void 0 : _c.status) === 'pending' ? widget.interaction.interactionContext : undefined;
95
- assert.ok(ctx, 'a context is still provided so the widget does not crash on a missing one');
96
- let ran = 0;
97
- const result = ctx.once('open-review-panel', () => {
98
- ran += 1;
99
- return 'opened';
100
- });
101
- assert.is(ran, 0, 'the inert fallback context must not run a once factory');
102
- assert.is(result, null, 'once returns null on the inert fallback context');
103
- }));
104
- Suite.run();
@@ -1,312 +0,0 @@
1
- import { __awaiter } from "tslib";
2
- import { BudgetExhaustedError } from '@genesislcap/foundation-ai';
3
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
4
- import { AgenticActivityBus } from '../../channel/ai-activity-bus';
5
- // Side-effect import — MUST precede `./orchestrating-driver` so the driver
6
- // subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
7
- import '../chat-driver/align-event-globals';
8
- import { OrchestratingDriver } from './orchestrating-driver';
9
- // GENC-1464 — the orchestrated turn's OUTCOME.
10
- //
11
- // `createDriver` returns an `OrchestratingDriver` for every agents-configured
12
- // host, and its `sendMessage` used to end with a hardcoded `return { reason:
13
- // 'done' }`. That made every typed failure unreachable for those hosts —
14
- // including `'budget-exhausted'`, which the assistant element latches its blocked
15
- // state off. A 402 therefore produced an apology over a still-enabled composer
16
- // for exactly the configurations this workstream exists to fix.
17
- //
18
- // The classifier compounds it: `classify()` is a SECOND retry ladder stacked on
19
- // the transport's, so an untyped catch re-issued the 402 `classifierRetries + 1`
20
- // times and then ran a fallback turn into the same wall.
21
- const makeRegistry = (provider) => ({
22
- get: () => provider,
23
- default: () => provider,
24
- defaultName: () => 'test',
25
- names: () => ['test'],
26
- getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
27
- listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
28
- });
29
- const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
30
- /** A provider that ends every turn immediately with a plain-text reply (no tools). */
31
- const okProvider = () => ({
32
- chat: () => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
33
- });
34
- /** A provider that counts calls and always refuses for budget. */
35
- const walledProvider = (vendorLabel = 'Anthropic') => {
36
- let n = 0;
37
- return {
38
- calls: () => n,
39
- provider: {
40
- chat: () => __awaiter(void 0, void 0, void 0, function* () {
41
- n += 1;
42
- throw new BudgetExhaustedError(vendorLabel, 25, 25.4);
43
- }),
44
- },
45
- };
46
- };
47
- const failureReasonOf = (r) => r.reason === 'done' ? r.failureReason : undefined;
48
- const Suite = createLogicSuite('orchestrating-driver budget wall');
49
- // ── 1. The showstopper: a walled AGENT turn must reach the caller ───────────
50
- Suite('a budget wall on the agent turn surfaces as the orchestrated result', () => __awaiter(void 0, void 0, void 0, function* () {
51
- // ONE specialist and no fallback, so `classify` short-circuits without calling
52
- // the provider — the wall is hit by the AGENT turn, inside a real tool loop.
53
- // Against the old hardcoded return this deep-equal fails.
54
- const { provider } = walledProvider();
55
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
56
- sessionKey: 'budget-1',
57
- });
58
- const result = yield driver.sendMessage('go');
59
- assert.equal(result, {
60
- reason: 'done',
61
- failureReason: 'budget-exhausted',
62
- budget: { budgetUsd: 25, spentUsd: 25.4, vendorLabel: 'Anthropic', vendor: 'anthropic' },
63
- });
64
- }));
65
- Suite("the vendor is the refusing transport's, not a hardcoded default", () => __awaiter(void 0, void 0, void 0, function* () {
66
- // The label rides the contract; `vendor` is its normalised key. Without a
67
- // second vendor exercised here, a constant would pass every other assertion in
68
- // this file — and per-vendor blocking would wall the wrong vendor forever.
69
- const { provider } = walledProvider('Gemini');
70
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
71
- sessionKey: 'budget-gemini',
72
- });
73
- const result = yield driver.sendMessage('go');
74
- assert.equal(result.reason === 'done' ? result.budget : undefined, {
75
- budgetUsd: 25,
76
- spentUsd: 25.4,
77
- vendorLabel: 'Gemini',
78
- vendor: 'gemini',
79
- });
80
- }));
81
- Suite('an unrecognised vendor label carries no typed vendor at all', () => __awaiter(void 0, void 0, void 0, function* () {
82
- // Attribution degrades to "unattributable" rather than to a wrong vendor —
83
- // which makes the element fall back to its vendor-agnostic block. Safe.
84
- const { provider } = walledProvider('Acme AI');
85
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
86
- sessionKey: 'budget-unknown-vendor',
87
- });
88
- const result = yield driver.sendMessage('go');
89
- assert.equal(result.reason === 'done' ? result.budget : undefined, {
90
- budgetUsd: 25,
91
- spentUsd: 25.4,
92
- vendorLabel: 'Acme AI',
93
- });
94
- }));
95
- Suite('the wall also reaches the tool-loop-end bus detail through the orchestrator', () => __awaiter(void 0, void 0, void 0, function* () {
96
- var _a;
97
- const bus = new AgenticActivityBus();
98
- const seen = [];
99
- const stop = bus.subscribe('tool-loop-end', (d) => seen.push(d));
100
- const { provider } = walledProvider();
101
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
102
- sessionKey: 'budget-2',
103
- activityBus: bus,
104
- });
105
- yield driver.sendMessage('go');
106
- assert.is(seen.length, 1, 'exactly one tool-loop-end for the one agent turn');
107
- assert.is((_a = seen[0]) === null || _a === void 0 ? void 0 : _a.failureReason, 'budget-exhausted');
108
- stop();
109
- bus.close();
110
- }));
111
- Suite('the bus detail carries the proxy figures, not just the failure reason', () => __awaiter(void 0, void 0, void 0, function* () {
112
- var _a, _b, _c;
113
- // The element latches off THIS event for an in-loop wall — the publish sits in
114
- // sendMessage's `finally`, so it lands before the return-value seam and the
115
- // idempotent latch makes the later one a no-op. If the figures rode only the
116
- // return value they would be unreachable in the common case and the banner would
117
- // show the generic copy in exactly the path they were added for.
118
- const bus = new AgenticActivityBus();
119
- const seen = [];
120
- const stop = bus.subscribe('tool-loop-end', (d) => seen.push(d));
121
- const { provider } = walledProvider();
122
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
123
- sessionKey: 'budget-figures',
124
- activityBus: bus,
125
- });
126
- const result = yield driver.sendMessage('go');
127
- assert.equal((_a = seen[0]) === null || _a === void 0 ? void 0 : _a.budget, { budgetUsd: 25, spentUsd: 25.4, vendorLabel: 'Anthropic', vendor: 'anthropic' }, 'the event carries the same figures as the return value');
128
- assert.is((_b = seen[0]) === null || _b === void 0 ? void 0 : _b.vendor, 'anthropic', 'and the top-level vendor agrees with the refusing transport, not with a stale last-resolved provider');
129
- assert.equal(result.reason === 'done' ? result.budget : undefined, (_c = seen[0]) === null || _c === void 0 ? void 0 : _c.budget, 'event and return value agree — neither is the sole carrier');
130
- stop();
131
- bus.close();
132
- }));
133
- Suite('a clean turn publishes no budget key at all', () => __awaiter(void 0, void 0, void 0, function* () {
134
- const bus = new AgenticActivityBus();
135
- const seen = [];
136
- const stop = bus.subscribe('tool-loop-end', (d) => seen.push(d));
137
- const provider = {
138
- chat: () => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
139
- };
140
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
141
- sessionKey: 'budget-clean',
142
- activityBus: bus,
143
- });
144
- yield driver.sendMessage('go');
145
- assert.ok(seen.every((d) => d === undefined || !('budget' in d)), 'no budget key on a non-budget turn — the historical detail shape is unchanged');
146
- stop();
147
- bus.close();
148
- }));
149
- // ── 2. No regression on the happy / handoff paths ───────────────────────────
150
- Suite('a clean turn still returns the bare legacy shape', () => __awaiter(void 0, void 0, void 0, function* () {
151
- const driver = new OrchestratingDriver(makeRegistry(okProvider()), [agent({ name: 'Solo' })], {
152
- sessionKey: 'budget-3',
153
- });
154
- const result = yield driver.sendMessage('go');
155
- assert.equal(result, { reason: 'done' }, 'no failureReason on a clean turn');
156
- }));
157
- Suite('a handoff-capped turn never leaks agent-handoff to the caller', () => __awaiter(void 0, void 0, void 0, function* () {
158
- // `'agent-handoff'` is an internal routing protocol. The two breaks that hold a
159
- // non-'done' inner result both carry it, so the collapse rule in `sendMessage`
160
- // is what keeps "report the turn that ended the loop" from changing the
161
- // discriminant a host matches on.
162
- let n = 0;
163
- const nextId = () => {
164
- n += 1;
165
- return n;
166
- };
167
- const handoffProvider = {
168
- chat: (_h, _u, options) => __awaiter(void 0, void 0, void 0, function* () {
169
- var _a;
170
- // The classifier call carries the routing tool; answer it with index 0.
171
- if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.some((t) => t.name === 'select_agent')) {
172
- return {
173
- role: 'assistant',
174
- content: '',
175
- toolCalls: [{ id: `c-${nextId()}`, name: 'select_agent', args: { agent_index: 0 } }],
176
- };
177
- }
178
- // Every agent turn asks to hand off, so the handoff cap is what ends it.
179
- return {
180
- role: 'assistant',
181
- content: '',
182
- toolCalls: [
183
- {
184
- id: `h-${nextId()}`,
185
- name: 'request_continuation',
186
- args: { summary: 's', remaining_task: 'more' },
187
- },
188
- ],
189
- };
190
- }),
191
- };
192
- const driver = new OrchestratingDriver(makeRegistry(handoffProvider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-4', maxHandoffs: 1 });
193
- const result = yield driver.sendMessage('go');
194
- assert.is(result.reason, 'done', 'the internal handoff protocol never escapes');
195
- }));
196
- Suite('a wall on a post-handoff classify does not duplicate the user message', () => __awaiter(void 0, void 0, void 0, function* () {
197
- // `classify()` runs at TWO seams. Before the first agent turn the user's
198
- // message is unappended (only the optimistic `history-updated` echo exists),
199
- // so the wall-catch must append it. At the END of a handoff iteration
200
- // `chatDriver.sendMessage` has already appended it — passing it again
201
- // produced ['user','assistant','tool','user','assistant'] with 'go' twice,
202
- // persisting into the snapshot and into the history sent to the model once
203
- // the budget was raised. The single-specialist narrowness guard below cannot
204
- // catch this: with one specialist, classify short-circuits and the handoff
205
- // seam never runs.
206
- let n = 0;
207
- const provider = {
208
- chat: (_h, _u, options) => __awaiter(void 0, void 0, void 0, function* () {
209
- var _a;
210
- n += 1;
211
- if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.some((t) => t.name === 'select_agent')) {
212
- // First classify routes; the post-handoff classify hits the wall.
213
- if (n > 2)
214
- throw new BudgetExhaustedError('Anthropic', 25, 25.4);
215
- return {
216
- role: 'assistant',
217
- content: '',
218
- toolCalls: [{ id: `c-${n}`, name: 'select_agent', args: { agent_index: 0 } }],
219
- };
220
- }
221
- // The agent turn asks to hand off, forcing the second classify.
222
- return {
223
- role: 'assistant',
224
- content: '',
225
- toolCalls: [
226
- {
227
- id: `h-${n}`,
228
- name: 'request_continuation',
229
- args: { summary: 's', remaining_task: 'more' },
230
- },
231
- ],
232
- };
233
- }),
234
- };
235
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-4b' });
236
- const result = yield driver.sendMessage('go');
237
- assert.is(failureReasonOf(result), 'budget-exhausted');
238
- const history = driver.getRawHistory();
239
- const userMessages = history.filter((m) => m.role === 'user' && m.content === 'go');
240
- assert.is(userMessages.length, 1, "the user's message appears exactly once");
241
- }));
242
- // ── 3. The classifier's own retry ladder ───────────────────────────────────
243
- Suite('a wall during classification costs exactly one provider call', () => __awaiter(void 0, void 0, void 0, function* () {
244
- // TWO specialists and no fallback, so `classify` really runs. Before the narrow
245
- // rethrow this was FOUR calls: `classifierRetries + 1` classifier attempts, then
246
- // a doomed agent turn.
247
- const { calls, provider } = walledProvider();
248
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-5' });
249
- const result = yield driver.sendMessage('go');
250
- assert.is(calls(), 1, 'no ladder clears a budget wall — do not climb it');
251
- assert.is(failureReasonOf(result), 'budget-exhausted');
252
- }));
253
- Suite('a classify-time wall lands on the transcript, without a routing apology', () => __awaiter(void 0, void 0, void 0, function* () {
254
- // The old ordering emitted "I'm not sure how to help with that…" from the
255
- // no-fallback exit and THEN routed anyway, so the user read a routing excuse
256
- // the classifier never earned, immediately above the real error.
257
- const { provider } = walledProvider();
258
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-6' });
259
- yield driver.sendMessage('go');
260
- const history = driver.getRawHistory();
261
- const text = history.map((m) => m.content).join('\n');
262
- assert.ok(text.includes('AI usage limit'), 'the real reason is what the user reads');
263
- assert.not.ok(text.includes("I'm not sure how to help with that"));
264
- // The user's own message must survive. `runOrchestratedTurn` dispatches it
265
- // optimistically and leaves the real append to `chatDriver.sendMessage`, which
266
- // a classify-time wall never reaches — so the driver's history held only the
267
- // assistant bubble and the next render dropped what the user had typed.
268
- assert.equal(history.map((m) => m.role), ['user', 'assistant'], 'the question stays above the answer');
269
- assert.is(history[0].content, 'go');
270
- }));
271
- Suite('an in-loop wall does not double-append the user message', () => __awaiter(void 0, void 0, void 0, function* () {
272
- // Narrowness guard for the append above: `reportBudgetExhausted` also serves
273
- // the in-loop path, where `sendMessage` has ALREADY appended the user message.
274
- // Only the classify seam passes one, so this path must be unchanged. ONE
275
- // specialist and no fallback, so `classify` short-circuits and the wall is hit
276
- // inside a real tool loop.
277
- const { provider } = walledProvider();
278
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
279
- sessionKey: 'budget-6b',
280
- });
281
- yield driver.sendMessage('go');
282
- const history = driver.getRawHistory();
283
- assert.is(history.filter((m) => m.role === 'user').length, 1, 'one user message, not two');
284
- }));
285
- Suite('a wall during classification never starts the fallback turn', () => __awaiter(void 0, void 0, void 0, function* () {
286
- const { calls, provider } = walledProvider();
287
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' }), agent({ name: 'Catch-all', fallback: true })], { sessionKey: 'budget-7' });
288
- const result = yield driver.sendMessage('go');
289
- assert.is(calls(), 1, 'the fallback agent is not run into the same wall');
290
- assert.is(failureReasonOf(result), 'budget-exhausted');
291
- }));
292
- Suite('a plain classifier failure still retries and falls back (narrowness guard)', () => __awaiter(void 0, void 0, void 0, function* () {
293
- // Proves the rethrow did not make every classify failure terminal: only the
294
- // budget wall short-circuits, because only the budget wall is unclearable.
295
- let classifierCalls = 0;
296
- const provider = {
297
- chat: (_h, _u, options) => __awaiter(void 0, void 0, void 0, function* () {
298
- var _a;
299
- if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.some((t) => t.name === 'select_agent')) {
300
- classifierCalls += 1;
301
- throw new Error('classifier is having a moment');
302
- }
303
- return { role: 'assistant', content: 'fallback handled it' };
304
- }),
305
- };
306
- const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' }), agent({ name: 'Catch-all', fallback: true })], { sessionKey: 'budget-8' });
307
- const result = yield driver.sendMessage('go');
308
- assert.is(classifierCalls, 3, 'classifierRetries (2) + the first attempt');
309
- assert.is(result.reason, 'done');
310
- assert.is(failureReasonOf(result), undefined, 'the fallback turn succeeded');
311
- }));
312
- Suite.run();
@@ -1,76 +0,0 @@
1
- import { __awaiter } from "tslib";
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- // Side-effect import — MUST precede `./orchestrating-driver` so the driver
4
- // subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
5
- import '../chat-driver/align-event-globals';
6
- import { OrchestratingDriver } from './orchestrating-driver';
7
- // GENC-1508 — `recordExternalCost` is OPTIONAL on `AiDriver`, so a missing delegation here
8
- // is not a compile error: the host's `driver?.recordExternalCost?.(…)` simply evaluates to
9
- // undefined and every post-resolve charge from every widget disappears — silently, and only
10
- // in orchestrated setups. That is the same invisible-when-broken failure the method exists to
11
- // fix, so the delegation gets a test rather than trust.
12
- const makeRegistry = (provider) => ({
13
- get: () => provider,
14
- default: () => provider,
15
- defaultName: () => 'test',
16
- names: () => ['test'],
17
- getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
18
- listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
19
- });
20
- const noToolProvider = () => ({
21
- chat: (_history, _userMessage, _options) => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
22
- });
23
- const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
24
- const makeDriver = () => new OrchestratingDriver(makeRegistry(noToolProvider()), [agent({ name: 'Genesis Assistant' })], {
25
- sessionKey: 'k',
26
- });
27
- /** The orchestrator does not expose `requestInteraction`, so the interaction is ARRANGED on the
28
- * wrapped driver. Every assertion below goes through the orchestrator's own public surface. */
29
- const wrapped = (driver) => driver.chatDriver;
30
- const Suite = createLogicSuite('orchestrating-driver external cost');
31
- Suite('post-resolve spend delegates to the wrapped driver', () => __awaiter(void 0, void 0, void 0, function* () {
32
- const driver = makeDriver();
33
- const inner = wrapped(driver);
34
- const pending = inner.requestInteraction('planning-question', { question: 'Pick one' });
35
- const id = driver.getRawHistory().at(-1).interaction.interactionId;
36
- driver.resolveInteraction(id, { status: 'approved', costUsd: 0.5 });
37
- yield pending;
38
- assert.is(driver.recordExternalCost(id, 0.25), true);
39
- const msg = driver.getRawHistory().find((m) => { var _a; return ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === id; });
40
- assert.is(msg === null || msg === void 0 ? void 0 : msg.externalCostUsd, 0.75);
41
- }));
42
- Suite('an unknown interaction is reported as unrecorded, not thrown', () => __awaiter(void 0, void 0, void 0, function* () {
43
- const driver = makeDriver();
44
- assert.is(driver.recordExternalCost('never-existed', 0.25), false);
45
- }));
46
- Suite('the announcement reaches the HOST, which only ever holds this wrapper', () => __awaiter(void 0, void 0, void 0, function* () {
47
- // The delegation test above passes with the forward missing: the money still lands on the
48
- // message. What breaks is quieter — `createDriver` hands the host an OrchestratingDriver for any
49
- // non-empty `agents`, so an event the inner driver raises and the constructor's forward list
50
- // omits is one the host never sees. The totals then never refresh and the save is never
51
- // scheduled, and because this flow's interaction is the last thing in the session, "stale until
52
- // the next history change" means permanently. Asserted on the WRAPPER for that reason.
53
- const driver = makeDriver();
54
- const inner = wrapped(driver);
55
- const pending = inner.requestInteraction('planning-question', { question: 'Pick one' });
56
- const id = driver.getRawHistory().at(-1).interaction.interactionId;
57
- driver.resolveInteraction(id, { status: 'approved' });
58
- yield pending;
59
- let seen = 0;
60
- let detail;
61
- driver.addEventListener('external-cost-recorded', (e) => {
62
- seen += 1;
63
- detail = e.detail;
64
- });
65
- // …and never on `history-updated`, which would rebuild the reporting widget.
66
- let historyUpdated = 0;
67
- driver.addEventListener('history-updated', () => {
68
- historyUpdated += 1;
69
- });
70
- driver.recordExternalCost(id, 0.25);
71
- assert.is(seen, 1);
72
- assert.is(detail === null || detail === void 0 ? void 0 : detail.costUsd, 0.25);
73
- assert.is(detail === null || detail === void 0 ? void 0 : detail.interactionId, id);
74
- assert.is(historyUpdated, 0);
75
- }));
76
- Suite.run();
@@ -1,61 +0,0 @@
1
- import { __awaiter } from "tslib";
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- // Side-effect import — MUST precede `./orchestrating-driver` so the driver
4
- // subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
5
- import '../chat-driver/align-event-globals';
6
- import { OrchestratingDriver } from './orchestrating-driver';
7
- // GENC-1351 — a set routing pin must be honoured on the next turn. This is the
8
- // invariant that broke on restore (found via the §5.8 diagnostics log): the pin
9
- // was hydrated into the store but never pushed to the driver, so the first
10
- // post-restore message fell through to the classifier and re-routed away from the
11
- // pinned agent. `Guided Booking` here is `excludeFromClassifier`, so the classifier
12
- // can NEVER pick it — a turn landing on it proves the pin (not classification)
13
- // routed it. The element-side fix (restore → `driver.setPinnedAgent`) is what makes
14
- // the pin reach the driver; this locks the driver half of the contract.
15
- const makeRegistry = (provider) => ({
16
- get: () => provider,
17
- default: () => provider,
18
- defaultName: () => 'test',
19
- names: () => ['test'],
20
- getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
21
- listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
22
- });
23
- /** A provider that ends every turn immediately with a plain-text reply (no tools). */
24
- const noToolProvider = () => ({
25
- chat: (_history, _userMessage, _options) => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
26
- });
27
- const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
28
- const makeDriver = () => new OrchestratingDriver(makeRegistry(noToolProvider()), [
29
- agent({ name: 'Genesis Assistant' }), // classifier-eligible specialist
30
- agent({ name: 'Guided Booking', excludeFromClassifier: true }), // pin-only
31
- ], { sessionKey: 'k' });
32
- const Suite = createLogicSuite('orchestrating-driver-pin');
33
- Suite('a set pin routes the next turn to the pinned agent (bypassing the classifier)', () => __awaiter(void 0, void 0, void 0, function* () {
34
- var _a;
35
- const driver = makeDriver();
36
- driver.setPinnedAgent('Guided Booking');
37
- yield driver.sendMessage('hi');
38
- assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'pinned agent ran; it is excludeFromClassifier, so only the pin could have routed it');
39
- }));
40
- Suite('the pin is sticky across turns', () => __awaiter(void 0, void 0, void 0, function* () {
41
- var _a;
42
- const driver = makeDriver();
43
- driver.setPinnedAgent('Guided Booking');
44
- yield driver.sendMessage('one');
45
- yield driver.sendMessage('two');
46
- assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'still pinned on the second turn');
47
- }));
48
- // GENC-1351 — the flow-owner lock is the *unpinned* routing path. A classifier-routed
49
- // stateful flow (flow-owned but never user-pinned) must resume on restore without
50
- // re-classifying: the persister re-applies BOTH `setPinnedAgent` and `setFlowOwner` on
51
- // restore, and `resolvePinnedAgent` reads `flowOwnerAgentName ?? pinnedAgentName`. Here
52
- // `Guided Booking` is `excludeFromClassifier` AND left unpinned, so a turn landing on it
53
- // proves the flow-owner lock (not the pin, not classification) routed it.
54
- Suite('a set flow-owner lock routes the next turn to that agent (bypassing the classifier)', () => __awaiter(void 0, void 0, void 0, function* () {
55
- var _a;
56
- const driver = makeDriver();
57
- driver.setFlowOwner('Guided Booking'); // flow-owned but NOT pinned
58
- yield driver.sendMessage('hi');
59
- assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'flow-owner routed it; it is excludeFromClassifier and unpinned, so only the flow-owner lock could have');
60
- }));
61
- Suite.run();
@@ -1,80 +0,0 @@
1
- var _a;
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import { settingsModalStyles } from './settings-modal.styles';
4
- // GENC-1464 budget meter — the styling invariants a reviewer would otherwise
5
- // have to catch by eye, following main.styles.test.ts (see there for why CSS
6
- // gets asserted from the generated text at all, and for the scoping lesson:
7
- // these assertions are about the BUDGET rules only, not a global style policy).
8
- //
9
- // 1. Design-system tokens are consumed UNFALLBACKED — the #2411/#2436 review
10
- // rule. Every token the meter reads ships a DS default, so a
11
- // `var(--token, fallback)` is dead code at best and a silently-broken
12
- // token name at worst.
13
- // 2. The meter's row anatomy is SHARED with the context indicator via grouped
14
- // selectors, not copied. The two indicators are siblings by design;
15
- // copied declarations would let them drift apart one edit at a time, which
16
- // is exactly what the grouping was chosen to make impossible.
17
- const suite = createLogicSuite('settings-modal.styles budget-meter rules');
18
- /**
19
- * The generated stylesheet text, flattened recursively — `css` composes nested
20
- * `ElementStyles`, and a shallow read can return almost nothing, making every
21
- * negative assertion below pass vacuously. The first test guards that.
22
- */
23
- const flatten = (value) => {
24
- if (typeof value === 'string')
25
- return value;
26
- if (Array.isArray(value))
27
- return value.map(flatten).join('\n');
28
- const nested = value === null || value === void 0 ? void 0 : value.styles;
29
- return nested ? flatten(nested) : '';
30
- };
31
- /**
32
- * Comments stripped before matching: the assertions below are about
33
- * DECLARATIONS, and the sheet's own prose legitimately mentions the very
34
- * pattern (`var(--x, y)`) the negative assertion hunts for.
35
- */
36
- const cssText = flatten(settingsModalStyles).replace(/\/\*[\s\S]*?\*\//g, '');
37
- /** Every declaration block whose selector mentions `.settings-budget-`, joined. */
38
- const budgetRules = ((_a = cssText.match(/[^{}]*\.settings-budget-[^{}]*\{[^}]*\}/g)) !== null && _a !== void 0 ? _a : []).join('\n');
39
- suite('the budget rules were extracted as text (guards the assertions below)', () => {
40
- assert.ok(cssText.includes('.settings-budget-usage'), 'the meter container rule is in the sheet');
41
- assert.ok(budgetRules.length > 0, 'the extraction found the budget rules');
42
- });
43
- suite('the meter consumes design-system tokens without dead fallbacks', () => {
44
- // One structural probe rather than a per-token list: ANY `var(--x,` inside a
45
- // budget rule is a fallback, and the meter reads only tokens that ship DS
46
- // defaults (--design-unit, --neutral-foreground-*), so there is no legitimate
47
- // fallback for this scope. Scoped to the budget rules on purpose — see
48
- // main.styles.test.ts for why a whole-sheet grep is the wrong test.
49
- assert.not.ok(/var\(--[a-z-]+\s*,/.test(budgetRules), 'no var(--token, fallback) in the meter');
50
- assert.ok(budgetRules.includes('var(--design-unit)'), 'and it does consume tokens');
51
- });
52
- suite('every meter row sits on one shared rhythm', () => {
53
- // The 4/2/2 fix. `.settings-modal-section-body` gaps at 4 units and the
54
- // budget meter at 2, so an unwrapped context indicator sat 4 units above the
55
- // FIRST vendor row while the vendor rows sat 2 apart — three visually
56
- // identical meter rows, unevenly spaced. Both meters now live in
57
- // `.settings-usage-meters`, which must carry the SAME gap as the meter it
58
- // wraps: asserted as ONE grouped rule, so the two cannot drift to different
59
- // rhythms. (settings-modal.template.test.ts pins the other half — that the
60
- // wrapper is conditional, so a host with no meters pays no stray gap for it.)
61
- const shared = /\.settings-usage-meters\s*,\s*\.settings-budget-usage\s*\{([^}]*)\}/.exec(cssText);
62
- assert.ok(shared, '.settings-usage-meters is grouped with .settings-budget-usage');
63
- assert.ok(shared[1].includes('flex-direction: column'), 'the shared container is a column');
64
- assert.ok(shared[1].includes('gap: calc(var(--design-unit) * 2px)'), 'gapped at the meter rhythm (2 units), not the section body 4');
65
- });
66
- suite("the meter shares the context indicator's row anatomy by grouped selector", () => {
67
- // The invariant is the SHARING, not the shape: each budget class must sit in
68
- // the same selector list as its context sibling, so restyling one restyles
69
- // both and the two indicators cannot drift apart.
70
- const sharedPairs = [
71
- ['.settings-context-header', '.settings-budget-header'],
72
- ['.settings-context-label', '.settings-budget-label'],
73
- ['.settings-context-tokens', '.settings-budget-figures'],
74
- ];
75
- for (const [contextClass, budgetClass] of sharedPairs) {
76
- const grouped = new RegExp(`${contextClass.replace(/\./g, '\\.')}\\s*,\\s*${budgetClass.replace(/\./g, '\\.')}\\s*\\{`);
77
- assert.ok(grouped.test(cssText), `${budgetClass} is grouped with ${contextClass}`);
78
- }
79
- });
80
- suite.run();