@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,54 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { FoundationAiAssistant } from './main';
3
- // Hold a reference so the custom-element registration isn't tree-shaken.
4
- FoundationAiAssistant;
5
- // GENC-1351 §5.11 (PR review): the single-element popout refactor removed the
6
- // `claimSideEffect` first-writer latch, so the only thing preventing a pending
7
- // interaction widget from being re-instantiated (and re-firing its side effect) on a
8
- // dock/undock move is that the popout control is disabled while an interaction is
9
- // pending — the template binds `?disabled=${x.hasActivePendingInteraction}`
10
- // (`main.template.ts`). This suite pins the GATE LOGIC (`hasActivePendingInteraction`)
11
- // that binding depends on, so a change to when the gate opens/closes is caught.
12
- //
13
- // Note: this deliberately does NOT mount the assistant. Mounting runs `connectedCallback`,
14
- // which subscribes to `agenticActivityBus` — opening a cross-tab `BroadcastChannel` in a browser
15
- // env and leaving the runner's event loop open. A getter-level test needs no mount and stays fast
16
- // + reliable. `document.createElement` only upgrades the element (constructor) — it does not
17
- // connect — so nothing subscribes, and the bus opens its channel lazily on first use, so none is
18
- // ever created here (no teardown needed).
19
- const Suite = createLogicSuite('FoundationAiAssistant popout interaction gate');
20
- /** A fresh (unconnected) element with a fake session store wired in. */
21
- function elementWith(state, messages) {
22
- const el = document.createElement('foundation-ai-assistant');
23
- el._sessionRef = {
24
- store: { aiAssistant: { state, messages } },
25
- };
26
- return el;
27
- }
28
- const withInteraction = (resolved) => [
29
- { role: 'user', content: 'hi' },
30
- {
31
- role: 'assistant',
32
- content: resolved ? 'done' : '',
33
- interaction: { interactionId: '1', componentName: 'x', data: {}, resolved },
34
- },
35
- ];
36
- Suite('is true only while a turn is running AND a pending interaction is the last message', () => {
37
- // Gated: a turn is running and the last message is an unresolved interaction. This is
38
- // the state during which the popout/dock control MUST be disabled.
39
- assert.is(elementWith('loading', withInteraction(false)).hasActivePendingInteraction, true, 'busy + unresolved interaction → gated');
40
- // `cancelling` counts as busy too (a stop was requested but the turn is winding down).
41
- assert.is(elementWith('cancelling', withInteraction(false)).hasActivePendingInteraction, true, 'cancelling + unresolved interaction → gated');
42
- });
43
- Suite('is false when idle, when the interaction is resolved, or when there is none', () => {
44
- // Not busy → never gated, even with an unresolved interaction present.
45
- assert.is(elementWith('idle', withInteraction(false)).hasActivePendingInteraction, false, 'idle → not gated');
46
- // Busy but the interaction has resolved → the widget is answered, nothing to strand.
47
- assert.is(elementWith('loading', withInteraction({ value: 'ok' })).hasActivePendingInteraction, false, 'resolved interaction → not gated');
48
- // Busy but the last message is not an interaction at all.
49
- assert.is(elementWith('loading', [{ role: 'assistant', content: 'thinking' }])
50
- .hasActivePendingInteraction, false, 'no interaction → not gated');
51
- // No session / empty transcript.
52
- assert.is(elementWith('loading', []).hasActivePendingInteraction, false, 'empty transcript → not gated');
53
- });
54
- Suite.run();
@@ -1,229 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { aiAssistantSlice, createDefaultSessionState, } from './ai-assistant-slice';
3
- // GENC-1351 §5.6 / Phase 1: the new bulk loadSession + resetSession reducers.
4
- const { loadSession, resetSession, setBlocked, setBlockedReason, setVendorBlocked, setInputValue, setMessages, setState, setSessionMenuOpen, setShowToolCalls, setShowThinkingSteps, setShowNarration, setShowAgentSwitchIndicator, setEnabledAnimations, } = aiAssistantSlice.actions;
5
- const reduce = aiAssistantSlice.reducer;
6
- const Suite = createLogicSuite('ai-assistant-slice-persistence');
7
- Suite('loadSession hydrates the restorable fields and forces idle', () => {
8
- let start = createDefaultSessionState();
9
- start = reduce(start, setState('loading')); // prove it overrides any prior state
10
- const payload = {
11
- // Usage rides on the message: `loadSession` derives the session totals from the
12
- // restored transcript rather than from a stored counter, so the cost has to be in
13
- // the messages for the assertion below to see it.
14
- messages: [
15
- { role: 'user', content: 'hi', cost: 1.25, inputTokens: 100, outputTokens: 20 },
16
- ],
17
- pinnedAgentName: 'Guided Booking',
18
- flowOwnerAgentName: 'Guided Booking',
19
- contextTokens: 1200,
20
- contextLimit: 200000,
21
- activeModel: 'claude-sonnet-4-6',
22
- activeProviderName: 'anthropic',
23
- };
24
- const next = reduce(start, loadSession(payload));
25
- assert.is(next.messages.length, 1);
26
- assert.is(next.pinnedAgentName, 'Guided Booking');
27
- assert.is(next.flowOwnerAgentName, 'Guided Booking');
28
- assert.is(next.sessionUsage.costUsd, 1.25);
29
- // Derived from the same walk: 100 prompt tokens with no cache split reported, so all
30
- // of it is uncached input, plus the 20 generated.
31
- assert.is(next.sessionUsage.uncachedInputTokens, 100);
32
- assert.is(next.sessionUsage.outputTokens, 20);
33
- assert.is(next.contextTokens, 1200);
34
- assert.is(next.contextLimit, 200000);
35
- assert.is(next.activeModel, 'claude-sonnet-4-6');
36
- assert.is(next.activeProviderName, 'anthropic');
37
- assert.is(next.state, 'idle');
38
- });
39
- Suite('resetSession wipes back to a fresh default', () => {
40
- let state = createDefaultSessionState();
41
- state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
42
- state = reduce(state, loadSession({
43
- messages: [],
44
- pinnedAgentName: 'A',
45
- flowOwnerAgentName: 'A',
46
- contextTokens: 5,
47
- contextLimit: 10,
48
- activeModel: 'm',
49
- activeProviderName: 'p',
50
- }));
51
- const cleared = reduce(state, resetSession());
52
- assert.is(cleared.messages.length, 0);
53
- assert.is(cleared.sessionUsage.costUsd, 0);
54
- assert.is(cleared.pinnedAgentName, null);
55
- assert.is(cleared.flowOwnerAgentName, null);
56
- assert.is(cleared.contextTokens, undefined);
57
- assert.is(cleared.state, 'idle');
58
- });
59
- Suite('resetSession preserves the user display preferences (they survive Clear)', () => {
60
- // Display prefs are settings, not conversation state — Clear/New-chat must keep them
61
- // (GENC-1351 #10). Turn several on, plus an animation, then reset.
62
- let state = createDefaultSessionState();
63
- state = reduce(state, setShowToolCalls(true));
64
- state = reduce(state, setShowThinkingSteps(true));
65
- state = reduce(state, setShowNarration(true));
66
- state = reduce(state, setShowAgentSwitchIndicator(true));
67
- state = reduce(state, setEnabledAnimations(['halo']));
68
- state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
69
- const cleared = reduce(state, resetSession());
70
- // Conversation wiped…
71
- assert.is(cleared.messages.length, 0, 'conversation cleared');
72
- // …but the display prefs kept.
73
- assert.is(cleared.showToolCalls, true);
74
- assert.is(cleared.showThinkingSteps, true);
75
- assert.is(cleared.showNarration, true);
76
- assert.is(cleared.showAgentSwitchIndicator, true);
77
- assert.equal(cleared.enabledAnimations, ['halo']);
78
- });
79
- Suite('createDefaultSessionState returns independent collections', () => {
80
- // No aliasing between instances — resetSession must hand back a clean slate.
81
- assert.ok(createDefaultSessionState().messages !== createDefaultSessionState().messages);
82
- });
83
- // GENC-1351 Phase 3: the lifecycle (session) menu open/close flag.
84
- Suite('setSessionMenuOpen toggles the lifecycle-menu flag; resetSession clears it', () => {
85
- let state = createDefaultSessionState();
86
- assert.is(state.sessionMenuOpen, false);
87
- state = reduce(state, setSessionMenuOpen(true));
88
- assert.is(state.sessionMenuOpen, true);
89
- const cleared = reduce(state, resetSession());
90
- assert.is(cleared.sessionMenuOpen, false);
91
- });
92
- // GENC-1464: the backend-blocked latch (exhausted AI budget).
93
- Suite('setBlocked latches the flag and its reason', () => {
94
- let state = createDefaultSessionState();
95
- assert.is(state.blocked, false);
96
- assert.is(state.blockedReason, null);
97
- state = reduce(state, setBlocked({ blocked: true }));
98
- assert.is(state.blocked, true);
99
- assert.is(state.blockedReason, null, 'no reason given → null, so the element uses its default');
100
- state = reduce(state, setBlocked({ blocked: true, reason: 'Budget exhausted — contact billing' }));
101
- assert.is(state.blockedReason, 'Budget exhausted — contact billing');
102
- });
103
- Suite('unblocking always clears the reason', () => {
104
- // A stale explanation must never outlive the condition it described.
105
- let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'gone' }));
106
- state = reduce(state, setBlocked({ blocked: false }));
107
- assert.is(state.blocked, false);
108
- assert.is(state.blockedReason, null);
109
- });
110
- Suite('resetSession preserves the block — "New chat" does not refill the budget', () => {
111
- // Deliberately unlike sessionMenuOpen/compacting above: this is a backend
112
- // condition, not conversation state, so Clear must not hand the user a
113
- // working-looking composer that fails on the next send.
114
- let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'no funds' }));
115
- state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
116
- const cleared = reduce(state, resetSession());
117
- assert.is(cleared.messages.length, 0, 'the transcript is still cleared');
118
- assert.is(cleared.blocked, true, 'the block survives Clear');
119
- assert.is(cleared.blockedReason, 'no funds', 'and so does its reason');
120
- });
121
- Suite('re-latching without a reason preserves the host explanation', () => {
122
- // The race this fixes is deterministic, not theoretical: `tool-loop-end` is
123
- // published from the driver's `finally`, i.e. BEFORE `sendMessage()` resolves.
124
- // So a bus-subscribed host (what the migration guide's Option C recommends)
125
- // writes its detailed copy first, and the element's own latch — which may have
126
- // no figures to offer — lands a moment later. Treating an OMITTED reason as
127
- // "clear it" replaced the host's copy with generic copy every single time.
128
- let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'contact ops@acme.com' }));
129
- state = reduce(state, setBlocked({ blocked: true }));
130
- assert.is(state.blocked, true);
131
- assert.is(state.blockedReason, 'contact ops@acme.com', 'the host copy survives the re-latch');
132
- });
133
- Suite('reason:null clears the explanation while staying blocked', () => {
134
- // The explicit escape hatch the omitted case gives up: `null` still clears.
135
- let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'stale copy' }));
136
- state = reduce(state, setBlocked({ blocked: true, reason: null }));
137
- assert.is(state.blocked, true);
138
- assert.is(state.blockedReason, null, 'the banner falls back to the default copy');
139
- });
140
- Suite('resetSession still resets everything NOT on the preserved list', () => {
141
- // Pins the boundary from the other side, so the preserved list cannot quietly
142
- // grow: these are conversation/UI state and Clear must wipe them.
143
- let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'r' }));
144
- state = reduce(state, setSessionMenuOpen(true));
145
- state = reduce(state, setInputValue('half-typed message'));
146
- const cleared = reduce(state, resetSession());
147
- assert.is(cleared.inputValue, '', 'the composer draft is reset');
148
- assert.is(cleared.sessionMenuOpen, false, 'the panel flag is reset');
149
- assert.is(cleared.blocked, true, '…while the block is still preserved');
150
- });
151
- // ── Per-vendor latches (GENC-1464 per-vendor budgets) ──────────────────────────
152
- Suite('setVendorBlocked walls one vendor and keeps the wall order', () => {
153
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'anthropic copy' }));
154
- assert.equal(state.blockedVendors, ['anthropic']);
155
- assert.equal(state.blockedVendorReasons, { anthropic: 'anthropic copy' });
156
- assert.is(state.blocked, false, 'the vendor-agnostic flag is untouched');
157
- state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true }));
158
- assert.equal(state.blockedVendors, ['anthropic', 'gemini'], 'order is the order they were walled');
159
- });
160
- Suite('walling an already-walled vendor is idempotent', () => {
161
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'gemini', blocked: true, reason: 'first' }));
162
- state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true, reason: 'second' }));
163
- assert.equal(state.blockedVendors, ['gemini'], 'no duplicate entry');
164
- assert.is(state.blockedVendorReasons.gemini, 'second');
165
- });
166
- Suite('the per-vendor reason honours the same three cases as setBlocked', () => {
167
- // The same two-writer race (driver latch landing after a bus-subscribed host)
168
- // applies per vendor, so "omitted keeps" has to hold here too.
169
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'host copy' }));
170
- state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: true }));
171
- assert.is(state.blockedVendorReasons.anthropic, 'host copy', 'omitted keeps');
172
- state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: null }));
173
- assert.is(state.blockedVendorReasons.anthropic, undefined, 'null clears');
174
- });
175
- Suite('releasing a vendor drops it and its copy', () => {
176
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'r' }));
177
- state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true }));
178
- state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: false }));
179
- assert.equal(state.blockedVendors, ['gemini']);
180
- assert.is(state.blockedVendorReasons.anthropic, undefined);
181
- });
182
- Suite('setBlocked(false) also clears every per-vendor latch', () => {
183
- // THE back-compat rule. A host asserting "the wall is gone" after its own
184
- // pre-flight (migration-GENC-1464 Option B) must not be overruled by a driver
185
- // latch it cannot see — without this, per-vendor would silently make the
186
- // documented unblock stop working.
187
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'r' }));
188
- state = reduce(state, setBlocked({ blocked: true, reason: 'global' }));
189
- state = reduce(state, setBlocked({ blocked: false }));
190
- assert.is(state.blocked, false);
191
- assert.is(state.blockedReason, null);
192
- assert.equal(state.blockedVendors, []);
193
- assert.equal(state.blockedVendorReasons, {});
194
- });
195
- Suite('setBlockedReason writes the copy without disturbing any latch', () => {
196
- // It exists precisely so the element's `blockedReason` setter cannot route
197
- // through `setBlocked(false, …)` — which would now clear the vendor latches.
198
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'gemini', blocked: true }));
199
- state = reduce(state, setBlockedReason('host copy'));
200
- assert.is(state.blockedReason, 'host copy');
201
- assert.is(state.blocked, false, 'the vendor-agnostic flag is untouched');
202
- assert.equal(state.blockedVendors, ['gemini'], 'and so are the per-vendor latches');
203
- });
204
- Suite('resetSession preserves the per-vendor latches too', () => {
205
- let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'no funds' }));
206
- state = reduce(state, setMessages([{ role: 'user', content: 'hi' }]));
207
- const cleared = reduce(state, resetSession());
208
- assert.equal(cleared.messages, [], 'the conversation is wiped');
209
- assert.equal(cleared.blockedVendors, ['anthropic'], 'Clear does not refill a vendor budget');
210
- assert.equal(cleared.blockedVendorReasons, { anthropic: 'no funds' });
211
- });
212
- // `resetSession` preserves its keys through `Object.assign`, whose source parameter
213
- // is structurally permissive: a typo like `blockedResaon` would compile clean,
214
- // silently stop preserving the real key, AND leak a junk key into serialised state.
215
- // The reducer therefore annotates its literal `satisfies
216
- // Partial<AiAssistantSessionState>`, which is a COMPILE-time guarantee — so the
217
- // durable proof is a type fixture, not a runtime assertion.
218
- //
219
- // This mirrors the reducer's clause on the same typo. If `satisfies` ever stopped
220
- // rejecting excess properties (a TS behaviour change, or someone widening the
221
- // annotated type), the `@ts-expect-error` below becomes unused and the BUILD fails
222
- // — which is exactly the signal wanted. The runtime suites above are its
223
- // behavioural companions: they pin the preserved list from both sides.
224
- const _typoIsRejectedAtCompileTime = {
225
- // @ts-expect-error — an excess/misspelled key must be rejected by `satisfies`
226
- blockedResaon: null,
227
- };
228
- void _typoIsRejectedAtCompileTime;
229
- Suite.run();
@@ -1,117 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { clearMetaEventRegistry, clearSession, DEBUG_LOG_README, getMetaEvents, mergeMetaEvents, recordMetaEvent, } from './debug-event-log';
3
- const event = (overrides = {}) => (Object.assign({ index: 0, timestamp: '2026-06-19T16:00:00.000Z', type: 'turn.retry', importance: 'normal' }, overrides));
4
- const suite = createLogicSuite('debug-event-log merge/clear');
5
- suite('mergeMetaEvents is a no-op for an empty batch', () => {
6
- clearMetaEventRegistry();
7
- mergeMetaEvents('k', []);
8
- assert.is(getMetaEvents('k').length, 0);
9
- });
10
- suite('mergeMetaEvents preserves each event timestamp and re-indexes monotonically', () => {
11
- clearMetaEventRegistry();
12
- mergeMetaEvents('k', [
13
- event({ index: 7, timestamp: '2026-06-19T16:00:01.000Z', detail: { a: 1 } }),
14
- event({ index: 99, timestamp: '2026-06-19T16:00:02.000Z', detail: { b: 2 } }),
15
- ]);
16
- const out = getMetaEvents('k');
17
- assert.is(out.length, 2);
18
- // original timestamps kept (not re-stamped) ...
19
- assert.is(out[0].timestamp, '2026-06-19T16:00:01.000Z');
20
- assert.is(out[1].timestamp, '2026-06-19T16:00:02.000Z');
21
- // ... indices re-assigned from the target's counter, not carried from source.
22
- assert.is(out[0].index, 0);
23
- assert.is(out[1].index, 1);
24
- assert.equal(out[0].detail, { a: 1 });
25
- });
26
- suite('mergeMetaEvents appends after existing events with a continuing index', () => {
27
- clearMetaEventRegistry();
28
- recordMetaEvent('k', 'turn.start'); // index 0, real timestamp
29
- mergeMetaEvents('k', [event({ type: 'turn.error', importance: 'high' })]);
30
- const out = getMetaEvents('k');
31
- assert.is(out.length, 2);
32
- assert.is(out[0].type, 'turn.start');
33
- assert.is(out[1].type, 'turn.error');
34
- assert.is(out[1].index, 1); // continues the monotonic counter
35
- });
36
- suite('mergeMetaEvents into a fresh key creates the bucket', () => {
37
- clearMetaEventRegistry();
38
- mergeMetaEvents('brand-new', [event()]);
39
- assert.is(getMetaEvents('brand-new').length, 1);
40
- });
41
- suite('mergeMetaEvents evicts oldest non-high events past the cap but keeps every high', () => {
42
- clearMetaEventRegistry();
43
- const highStamp = '2026-06-19T16:30:00.000Z';
44
- // One `high` failure first, then a flood of `normal` events well past the cap.
45
- const batch = [
46
- event({ type: 'turn.error', importance: 'high', timestamp: highStamp }),
47
- ];
48
- for (let i = 0; i < 2000; i += 1) {
49
- batch.push(event({ type: 'turn.retry', importance: 'normal' }));
50
- }
51
- mergeMetaEvents('k', batch);
52
- const out = getMetaEvents('k');
53
- // Eviction ran (bounded below what we merged) ...
54
- assert.ok(out.length < 2001);
55
- // ... and the lone high-importance failure was never dropped.
56
- assert.is(out.filter((e) => e.importance === 'high').length, 1);
57
- assert.ok(out.some((e) => e.timestamp === highStamp));
58
- });
59
- suite('clearSession drops one key without touching others', () => {
60
- clearMetaEventRegistry();
61
- recordMetaEvent('child', 'turn.retry');
62
- recordMetaEvent('parent', 'turn.start');
63
- clearSession('child');
64
- assert.is(getMetaEvents('child').length, 0);
65
- assert.is(getMetaEvents('parent').length, 1);
66
- });
67
- // ── README ↔ taxonomy sync (GENC-1464) ─────────────────────────────────────────
68
- // DEBUG_LOG_README is emitted as the FIRST key of the exported debug log
69
- // specifically so an agent reading the JSON does not have to reverse-engineer it.
70
- // Its reason enumerations therefore read as exhaustive and will be trusted — but
71
- // they had already drifted twice (`refusal` was missing from both) before anyone
72
- // noticed, because "the doc lives next to the catalogue" is not enforcement.
73
- //
74
- // The `Record<..., true>` below is the enforcement: adding a union member without
75
- // listing it here is a COMPILE error, and listing it without documenting it is a
76
- // test failure. So the next added reason fails in the one place the drift keeps
77
- // happening.
78
- const ALL_TURN_FAILURE_REASONS = Object.keys({
79
- exception: true,
80
- 'malformed-function-call': true,
81
- 'empty-response': true,
82
- 'unknown-tool-limit': true,
83
- 'max-iterations': true,
84
- 'response-truncated': true,
85
- refusal: true,
86
- 'budget-exhausted': true,
87
- 'provider-refused': true,
88
- });
89
- const ALL_SUB_AGENT_FAILURE_REASONS = Object.keys({
90
- max_iterations: true,
91
- malformed_tool_call: true,
92
- empty_response: true,
93
- unknown_tool_limit: true,
94
- timeout: true,
95
- response_truncated: true,
96
- refusal: true,
97
- budget_exhausted: true,
98
- provider_refused: true,
99
- });
100
- const README_TEXT = DEBUG_LOG_README.join('\n');
101
- suite('DEBUG_LOG_README enumerates every TurnFailureReason', () => {
102
- for (const reason of ALL_TURN_FAILURE_REASONS) {
103
- assert.ok(README_TEXT.includes(reason), `turn.error reason '${reason}' is missing from DEBUG_LOG_README`);
104
- }
105
- });
106
- suite('DEBUG_LOG_README enumerates every SubAgentFailureReason', () => {
107
- for (const reason of ALL_SUB_AGENT_FAILURE_REASONS) {
108
- assert.ok(README_TEXT.includes(reason), `subagent.failed reason '${reason}' is missing from DEBUG_LOG_README`);
109
- }
110
- });
111
- suite('DEBUG_LOG_README documents the budget diagnostics that ride turn.error', () => {
112
- // `recordTurnError('budget-exhausted', …)` attaches these; an agent triaging a
113
- // support ticket needs to know to look for them.
114
- assert.ok(README_TEXT.includes('budgetUsd'));
115
- assert.ok(README_TEXT.includes('spentUsd'));
116
- });
117
- suite.run();
@@ -1,55 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { clearDriverRegistry, deleteDriver, getDriver, getDriverAgentsKey, getOrCreateDriver, } from './driver-registry';
3
- // Minimal stub — the registry only stores and returns the reference; it never
4
- // invokes driver methods, so an opaque object is sufficient.
5
- const stub = (id) => ({ id });
6
- const suite = createLogicSuite('driver-registry agents-key tracking');
7
- suite('getOrCreateDriver creates once and records the agents key', () => {
8
- clearDriverRegistry();
9
- const d = stub('a');
10
- const created = getOrCreateDriver('k', 'agentsKeyA', () => d);
11
- assert.is(created, d);
12
- assert.is(getDriver('k'), d);
13
- assert.is(getDriverAgentsKey('k'), 'agentsKeyA');
14
- });
15
- suite('reuse returns the existing driver and keeps its original agents key', () => {
16
- clearDriverRegistry();
17
- const first = stub('first');
18
- getOrCreateDriver('k', 'agentsKeyA', () => first);
19
- // A second component sharing the key, even with a different desired agents
20
- // fingerprint, must NOT replace the driver or overwrite the recorded key.
21
- let factoryCalled = false;
22
- const reused = getOrCreateDriver('k', 'agentsKeyB', () => {
23
- factoryCalled = true;
24
- return stub('second');
25
- });
26
- assert.is(reused, first);
27
- assert.is(factoryCalled, false);
28
- assert.is(getDriverAgentsKey('k'), 'agentsKeyA');
29
- });
30
- suite('deleteDriver clears both the driver and its recorded agents key', () => {
31
- clearDriverRegistry();
32
- getOrCreateDriver('k', 'agentsKeyA', () => stub('a'));
33
- deleteDriver('k');
34
- assert.is(getDriver('k'), undefined);
35
- assert.is(getDriverAgentsKey('k'), undefined);
36
- });
37
- suite('a rebuild under the same key records the new agents key (the adopt signal)', () => {
38
- clearDriverRegistry();
39
- // Initial driver, built for the old agent set.
40
- getOrCreateDriver('k', 'old', () => stub('d-old'));
41
- // Rebuild: the host deletes then recreates with the new fingerprint.
42
- deleteDriver('k');
43
- const rebuilt = stub('d-new');
44
- getOrCreateDriver('k', 'new', () => rebuilt);
45
- // GENC-1388: a sibling sharing the key can now detect that the registry driver
46
- // already matches its desired agents and adopt `rebuilt`, rather than
47
- // recreating (which would orphan a driver mid-turn).
48
- assert.is(getDriver('k'), rebuilt);
49
- assert.is(getDriverAgentsKey('k'), 'new');
50
- });
51
- suite('getDriverAgentsKey is undefined for an unknown key', () => {
52
- clearDriverRegistry();
53
- assert.is(getDriverAgentsKey('missing'), undefined);
54
- });
55
- suite.run();
@@ -1,78 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { createInteractionContext } from './interaction-context';
3
- const suite = createLogicSuite('interaction-context factory');
4
- suite('once runs the factory a single time and memoises the result', () => {
5
- const { context } = createInteractionContext('id-1');
6
- let calls = 0;
7
- const first = context.once('ws', () => {
8
- calls += 1;
9
- return { socket: true };
10
- });
11
- const second = context.once('ws', () => {
12
- calls += 1;
13
- return { socket: false };
14
- });
15
- assert.is(calls, 1, 'factory ran once');
16
- assert.is(second, first, 'the memoised value is returned on the second call');
17
- });
18
- suite('subscribe/notify fans out to listeners; unsubscribe stops them', () => {
19
- const { context } = createInteractionContext('id-2');
20
- let a = 0;
21
- let b = 0;
22
- const unsubA = context.subscribe(() => (a += 1));
23
- context.subscribe(() => (b += 1));
24
- context.notify();
25
- assert.is(a, 1);
26
- assert.is(b, 1);
27
- unsubA();
28
- context.notify();
29
- assert.is(a, 1, 'unsubscribed listener no longer fires');
30
- assert.is(b, 2, 'remaining listener still fires');
31
- });
32
- suite('a throwing subscriber does not stop the others', () => {
33
- const { context } = createInteractionContext('id-3');
34
- let reached = false;
35
- context.subscribe(() => {
36
- throw new Error('boom');
37
- });
38
- context.subscribe(() => (reached = true));
39
- context.notify();
40
- assert.ok(reached, 'later subscriber still ran after an earlier one threw');
41
- });
42
- suite('dispose runs every onDispose callback once, then is idempotent', () => {
43
- const { context, dispose } = createInteractionContext('id-4');
44
- let count = 0;
45
- context.onDispose(() => (count += 1));
46
- context.onDispose(() => (count += 1));
47
- dispose();
48
- assert.is(count, 2, 'both disposers ran');
49
- dispose();
50
- assert.is(count, 2, 'a second dispose is a no-op');
51
- });
52
- suite('onDispose registered after dispose runs immediately', () => {
53
- const { context, dispose } = createInteractionContext('id-5');
54
- dispose();
55
- let ran = false;
56
- context.onDispose(() => (ran = true));
57
- assert.ok(ran, 'late onDispose runs immediately so no resource leaks');
58
- });
59
- suite('notify after dispose is a no-op (listeners cleared)', () => {
60
- const { context, dispose } = createInteractionContext('id-6');
61
- let fired = 0;
62
- context.subscribe(() => (fired += 1));
63
- dispose();
64
- context.notify();
65
- assert.is(fired, 0, 'no listeners fire after dispose');
66
- });
67
- suite('once after dispose returns null without running the factory', () => {
68
- const { context, dispose } = createInteractionContext('id-7');
69
- dispose();
70
- let calls = 0;
71
- const result = context.once('late', () => {
72
- calls += 1;
73
- return { socket: true };
74
- });
75
- assert.is(calls, 0, 'the factory never ran on an ended interaction (no leaked resource)');
76
- assert.is(result, null, 'once returns null after dispose');
77
- });
78
- suite.run();
@@ -1,30 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { clearDiagnosticsCursorRegistry, deleteDiagnosticsCursorsFor, getDiagnosticsCursors, resetDiagnosticsCursorsFor, } from './diagnostics-cursors';
3
- // GENC-1351 §5.8/§5.12 (#9): the forward-capture cursors, and the crucial difference
4
- // between the Clear reset (keep the event high-water) and the in-place-switch delete
5
- // (fresh cursors, because the meta-event buffer is torn down and its indices restart).
6
- const KEY = 'session-1';
7
- const Suite = createLogicSuite('diagnostics-cursors');
8
- Suite.before.each(() => {
9
- clearDiagnosticsCursorRegistry();
10
- });
11
- Suite('resetDiagnosticsCursorsFor KEEPS lastEventIndex (Clear: meta buffer keeps climbing)', () => {
12
- const c = getDiagnosticsCursors(KEY);
13
- c.lastEventIndex = 40;
14
- c.emittedMsgKeys.add('m');
15
- c.restoredMsgKeys.add('r');
16
- resetDiagnosticsCursorsFor(KEY);
17
- const after = getDiagnosticsCursors(KEY);
18
- assert.is(after.lastEventIndex, 40, 'event high-water kept — Clear does not reset the meta buffer');
19
- assert.is(after.emittedMsgKeys.size, 0, 'per-conversation message cursors restarted');
20
- assert.is(after.restoredMsgKeys.size, 0, 'restore watermark restarted');
21
- });
22
- Suite('deleteDiagnosticsCursorsFor RESETS lastEventIndex (switch: meta buffer restarts at 0)', () => {
23
- const c = getDiagnosticsCursors(KEY);
24
- c.lastEventIndex = 40;
25
- deleteDiagnosticsCursorsFor(KEY);
26
- // A later return re-creates fresh cursors on first access.
27
- const after = getDiagnosticsCursors(KEY);
28
- assert.is(after.lastEventIndex, -1, 'fresh cursor — a returning session whose events restart at 0 is NOT skipped');
29
- });
30
- Suite.run();