@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,154 +0,0 @@
1
- import type { SubAgentFailureReason, TurnFailureReason } from '@genesislcap/foundation-ai';
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import {
4
- clearMetaEventRegistry,
5
- clearSession,
6
- DEBUG_LOG_README,
7
- getMetaEvents,
8
- type MetaEvent,
9
- mergeMetaEvents,
10
- recordMetaEvent,
11
- } from './debug-event-log';
12
-
13
- const event = (overrides: Partial<MetaEvent> = {}): MetaEvent => ({
14
- index: 0,
15
- timestamp: '2026-06-19T16:00:00.000Z',
16
- type: 'turn.retry',
17
- importance: 'normal',
18
- ...overrides,
19
- });
20
-
21
- const suite = createLogicSuite('debug-event-log merge/clear');
22
-
23
- suite('mergeMetaEvents is a no-op for an empty batch', () => {
24
- clearMetaEventRegistry();
25
- mergeMetaEvents('k', []);
26
- assert.is(getMetaEvents('k').length, 0);
27
- });
28
-
29
- suite('mergeMetaEvents preserves each event timestamp and re-indexes monotonically', () => {
30
- clearMetaEventRegistry();
31
- mergeMetaEvents('k', [
32
- event({ index: 7, timestamp: '2026-06-19T16:00:01.000Z', detail: { a: 1 } }),
33
- event({ index: 99, timestamp: '2026-06-19T16:00:02.000Z', detail: { b: 2 } }),
34
- ]);
35
- const out = getMetaEvents('k');
36
- assert.is(out.length, 2);
37
- // original timestamps kept (not re-stamped) ...
38
- assert.is(out[0].timestamp, '2026-06-19T16:00:01.000Z');
39
- assert.is(out[1].timestamp, '2026-06-19T16:00:02.000Z');
40
- // ... indices re-assigned from the target's counter, not carried from source.
41
- assert.is(out[0].index, 0);
42
- assert.is(out[1].index, 1);
43
- assert.equal(out[0].detail, { a: 1 });
44
- });
45
-
46
- suite('mergeMetaEvents appends after existing events with a continuing index', () => {
47
- clearMetaEventRegistry();
48
- recordMetaEvent('k', 'turn.start'); // index 0, real timestamp
49
- mergeMetaEvents('k', [event({ type: 'turn.error', importance: 'high' })]);
50
- const out = getMetaEvents('k');
51
- assert.is(out.length, 2);
52
- assert.is(out[0].type, 'turn.start');
53
- assert.is(out[1].type, 'turn.error');
54
- assert.is(out[1].index, 1); // continues the monotonic counter
55
- });
56
-
57
- suite('mergeMetaEvents into a fresh key creates the bucket', () => {
58
- clearMetaEventRegistry();
59
- mergeMetaEvents('brand-new', [event()]);
60
- assert.is(getMetaEvents('brand-new').length, 1);
61
- });
62
-
63
- suite('mergeMetaEvents evicts oldest non-high events past the cap but keeps every high', () => {
64
- clearMetaEventRegistry();
65
- const highStamp = '2026-06-19T16:30:00.000Z';
66
- // One `high` failure first, then a flood of `normal` events well past the cap.
67
- const batch: MetaEvent[] = [
68
- event({ type: 'turn.error', importance: 'high', timestamp: highStamp }),
69
- ];
70
- for (let i = 0; i < 2000; i += 1) {
71
- batch.push(event({ type: 'turn.retry', importance: 'normal' }));
72
- }
73
- mergeMetaEvents('k', batch);
74
- const out = getMetaEvents('k');
75
- // Eviction ran (bounded below what we merged) ...
76
- assert.ok(out.length < 2001);
77
- // ... and the lone high-importance failure was never dropped.
78
- assert.is(out.filter((e) => e.importance === 'high').length, 1);
79
- assert.ok(out.some((e) => e.timestamp === highStamp));
80
- });
81
-
82
- suite('clearSession drops one key without touching others', () => {
83
- clearMetaEventRegistry();
84
- recordMetaEvent('child', 'turn.retry');
85
- recordMetaEvent('parent', 'turn.start');
86
- clearSession('child');
87
- assert.is(getMetaEvents('child').length, 0);
88
- assert.is(getMetaEvents('parent').length, 1);
89
- });
90
-
91
- // ── README ↔ taxonomy sync (GENC-1464) ─────────────────────────────────────────
92
- // DEBUG_LOG_README is emitted as the FIRST key of the exported debug log
93
- // specifically so an agent reading the JSON does not have to reverse-engineer it.
94
- // Its reason enumerations therefore read as exhaustive and will be trusted — but
95
- // they had already drifted twice (`refusal` was missing from both) before anyone
96
- // noticed, because "the doc lives next to the catalogue" is not enforcement.
97
- //
98
- // The `Record<..., true>` below is the enforcement: adding a union member without
99
- // listing it here is a COMPILE error, and listing it without documenting it is a
100
- // test failure. So the next added reason fails in the one place the drift keeps
101
- // happening.
102
-
103
- const ALL_TURN_FAILURE_REASONS = Object.keys({
104
- exception: true,
105
- 'malformed-function-call': true,
106
- 'empty-response': true,
107
- 'unknown-tool-limit': true,
108
- 'max-iterations': true,
109
- 'response-truncated': true,
110
- refusal: true,
111
- 'budget-exhausted': true,
112
- 'provider-refused': true,
113
- } satisfies Record<TurnFailureReason, true>) as TurnFailureReason[];
114
-
115
- const ALL_SUB_AGENT_FAILURE_REASONS = Object.keys({
116
- max_iterations: true,
117
- malformed_tool_call: true,
118
- empty_response: true,
119
- unknown_tool_limit: true,
120
- timeout: true,
121
- response_truncated: true,
122
- refusal: true,
123
- budget_exhausted: true,
124
- provider_refused: true,
125
- } satisfies Record<SubAgentFailureReason, true>) as SubAgentFailureReason[];
126
-
127
- const README_TEXT = DEBUG_LOG_README.join('\n');
128
-
129
- suite('DEBUG_LOG_README enumerates every TurnFailureReason', () => {
130
- for (const reason of ALL_TURN_FAILURE_REASONS) {
131
- assert.ok(
132
- README_TEXT.includes(reason),
133
- `turn.error reason '${reason}' is missing from DEBUG_LOG_README`,
134
- );
135
- }
136
- });
137
-
138
- suite('DEBUG_LOG_README enumerates every SubAgentFailureReason', () => {
139
- for (const reason of ALL_SUB_AGENT_FAILURE_REASONS) {
140
- assert.ok(
141
- README_TEXT.includes(reason),
142
- `subagent.failed reason '${reason}' is missing from DEBUG_LOG_README`,
143
- );
144
- }
145
- });
146
-
147
- suite('DEBUG_LOG_README documents the budget diagnostics that ride turn.error', () => {
148
- // `recordTurnError('budget-exhausted', …)` attaches these; an agent triaging a
149
- // support ticket needs to know to look for them.
150
- assert.ok(README_TEXT.includes('budgetUsd'));
151
- assert.ok(README_TEXT.includes('spentUsd'));
152
- });
153
-
154
- suite.run();
@@ -1,358 +0,0 @@
1
- /**
2
- * Module-level append-only timeline of meta/lifecycle events, keyed by session
3
- * identity (the same `stateKey` space used by the driver registry and the
4
- * session store).
5
- *
6
- * Why a module registry rather than the redux store or the driver:
7
- * - **Survives the pop-in/pop-out element churn.** A single session can be
8
- * emitted from up to two element instances (an `expand` bubble + a `collapse`
9
- * panel) sharing one driver; keying by `stateKey` unifies them into one
10
- * timeline.
11
- * - **Survives driver rebuilds.** The driver is torn down and recreated when the
12
- * host swaps the agents array (see `agentsChanged` / `deleteDriver`), so a
13
- * driver-owned buffer would lose the timeline mid-session. The `stateKey`
14
- * outlives the driver.
15
- * - **Stays out of redux reactivity.** These appends never drive the UI, so
16
- * there's no reason to pay immutable-reducer cost or flood the Redux DevTools
17
- * action log with debug noise.
18
- *
19
- * Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
20
- * turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
21
- * while `high`-importance failure events are retained — so a long-lived session
22
- * stays bounded in normal use without ever dropping a failure signal.
23
- *
24
- * @internal
25
- */
26
-
27
- import type { TurnFailureReason } from '@genesislcap/foundation-ai';
28
-
29
- /**
30
- * Catalogue of meta event names. This is the documented surface — extend it as
31
- * new events are wired in (Tier 2/3 lifecycle, interaction, provider events).
32
- */
33
- export type MetaEventType =
34
- // Element lifecycle + window placement
35
- | 'assistant.connected'
36
- | 'assistant.disconnected'
37
- | 'assistant.popout'
38
- | 'assistant.popin'
39
- // Driver lifecycle / wiring
40
- | 'driver.created'
41
- | 'driver.wired'
42
- | 'driver.unwired'
43
- // Turn lifecycle
44
- | 'state.changed'
45
- | 'turn.start'
46
- | 'turn.end'
47
- | 'turn.retry'
48
- | 'turn.error'
49
- | 'tool.failed'
50
- | 'tool.unresolved'
51
- | 'subagent.started'
52
- | 'subagent.completed'
53
- | 'subagent.failed'
54
- // Routing / providers
55
- | 'agent.handoff'
56
- | 'agent.pinned'
57
- | 'agent.unpinned'
58
- | 'provider.selected'
59
- // Blocking interactions
60
- | 'interaction.requested'
61
- | 'interaction.resolved'
62
- | 'interaction.external-cost'
63
- | 'interaction.external-cost-dropped'
64
- // External / unified diagnostics (GENC-1461) — an out-of-band engine's log folded into this session
65
- | 'external-diagnostics.folded'
66
- // Context / cost
67
- | 'context.updated'
68
- | 'context.threshold-crossed'
69
- | 'context.compacted'
70
- | 'context.condensed'
71
- // UI + input
72
- | 'panel.toggled'
73
- | 'attachment.added'
74
- | 'file.read-failed'
75
- | 'suggestions.failed'
76
- // Session lifecycle (GENC-1351)
77
- | 'session.cleared'
78
- | 'session.switched'
79
- | 'session.switch-deferred'
80
- | 'persistence.toggled'
81
- | 'persistence.saved'
82
- | 'persistence.save-failed'
83
- | 'persistence.suppressed'
84
- | 'persistence.not-resumable'
85
- | 'persistence.restored'
86
- | 'persistence.restore-orphan';
87
-
88
- /**
89
- * How much a reader should care about an event — lets a consumer (or an AI
90
- * agent) filter the timeline: skip `low` UI/bookkeeping noise, skim `normal`
91
- * session flow, or jump straight to `high` problem/limit signals when triaging.
92
- */
93
- export type MetaEventImportance = 'high' | 'normal' | 'low';
94
-
95
- /**
96
- * Importance is intrinsic to the event *type*, not the instance, so it's
97
- * defined once here and stamped automatically by {@link recordMetaEvent} — call
98
- * sites never pass it. The `Record` is exhaustive: adding a `MetaEventType`
99
- * without a level here is a compile error.
100
- *
101
- * - `high` — failures and hard limits you almost always want to see.
102
- * - `normal` — meaningful session flow you read to follow what happened.
103
- * - `low` — frequent UI / bookkeeping noise, usually safe to skip.
104
- */
105
- export const META_EVENT_IMPORTANCE: Record<MetaEventType, MetaEventImportance> = {
106
- 'turn.error': 'high',
107
- 'tool.failed': 'high',
108
- 'subagent.failed': 'high',
109
- 'file.read-failed': 'high',
110
- 'suggestions.failed': 'high',
111
- 'context.threshold-crossed': 'high',
112
- 'context.compacted': 'high',
113
- 'persistence.restore-orphan': 'high',
114
- // A save that did not land is silent data loss in progress: the transcript on screen
115
- // is no longer backed by the store, and every later turn widens the gap. GENC-1511.
116
- 'persistence.save-failed': 'high',
117
-
118
- 'assistant.connected': 'normal',
119
- 'assistant.disconnected': 'normal',
120
- 'assistant.popout': 'normal',
121
- 'assistant.popin': 'normal',
122
- 'driver.created': 'normal',
123
- 'state.changed': 'normal',
124
- 'turn.start': 'normal',
125
- 'turn.end': 'normal',
126
- 'turn.retry': 'normal',
127
- 'tool.unresolved': 'normal',
128
- 'subagent.started': 'normal',
129
- 'subagent.completed': 'normal',
130
- 'agent.handoff': 'normal',
131
- 'agent.pinned': 'normal',
132
- 'agent.unpinned': 'normal',
133
- 'provider.selected': 'normal',
134
- 'interaction.requested': 'normal',
135
- 'interaction.resolved': 'normal',
136
- // Bookkeeping about an already-finished turn; interesting when reconciling a session total.
137
- 'interaction.external-cost': 'normal',
138
- // Spend that was billed but could NOT be recorded (the message was trimmed/condensed away, or
139
- // the value was not real spend). 'high': it is the one way the session total goes quietly
140
- // wrong, so it must stand out when reconciling.
141
- 'interaction.external-cost-dropped': 'high',
142
- 'external-diagnostics.folded': 'normal',
143
- 'session.cleared': 'normal',
144
- 'session.switched': 'low',
145
- 'session.switch-deferred': 'low',
146
- 'persistence.restored': 'normal',
147
- 'persistence.suppressed': 'normal',
148
- 'persistence.not-resumable': 'normal',
149
-
150
- 'driver.wired': 'low',
151
- 'driver.unwired': 'low',
152
- 'context.updated': 'low',
153
- 'context.condensed': 'low',
154
- 'panel.toggled': 'low',
155
- 'persistence.toggled': 'low',
156
- 'persistence.saved': 'low',
157
- 'attachment.added': 'low',
158
- };
159
-
160
- /** One entry in the meta-event timeline. */
161
- export interface MetaEvent {
162
- /** Monotonic counter across the session's lifetime (does not reset on driver rebuild). */
163
- index: number;
164
- /** ISO timestamp, matching the format used elsewhere in the debug log. */
165
- timestamp: string;
166
- /** Event name — see {@link MetaEventType}. */
167
- type: MetaEventType;
168
- /** How much to care about this event — see {@link META_EVENT_IMPORTANCE}. */
169
- importance: MetaEventImportance;
170
- /** Optional structured payload. */
171
- detail?: Record<string, unknown>;
172
- }
173
-
174
- /**
175
- * Soft ring-buffer cap. Once exceeded, the oldest *non-`high`* event is evicted;
176
- * `high`-importance events (failures and hard limits) are never dropped — they're
177
- * the whole reason the log exists. So a buffer dominated by `high` events is
178
- * allowed to float above this cap rather than lose a failure signal; in normal
179
- * use the frequent `low`/`normal` events keep it near the cap. Entries are cheap.
180
- */
181
- const DEFAULT_MAX_META_EVENTS = 800;
182
-
183
- interface MetaEventBuffer {
184
- events: MetaEvent[];
185
- /** Next index to assign — kept separate from `events.length` so it stays monotonic after eviction. */
186
- next: number;
187
- }
188
-
189
- const registry = new Map<string, MetaEventBuffer>();
190
-
191
- /**
192
- * Append a meta event to the timeline for `key`. Once the buffer exceeds
193
- * {@link DEFAULT_MAX_META_EVENTS}, evicts the oldest *non-`high`* event;
194
- * `high`-importance events are never evicted. An empty `key` is bucketed under
195
- * `''`, matching how drivers/stores handle an absent session identity, so
196
- * callers never need to guard.
197
- */
198
- export function recordMetaEvent(
199
- key: string,
200
- type: MetaEventType,
201
- detail?: Record<string, unknown>,
202
- ): void {
203
- let buffer = registry.get(key);
204
- if (!buffer) {
205
- buffer = { events: [], next: 0 };
206
- registry.set(key, buffer);
207
- }
208
- buffer.events.push({
209
- index: buffer.next,
210
- timestamp: new Date().toISOString(),
211
- type,
212
- importance: META_EVENT_IMPORTANCE[type],
213
- detail,
214
- });
215
- buffer.next += 1;
216
- if (buffer.events.length > DEFAULT_MAX_META_EVENTS) {
217
- // Drop the oldest event that isn't `high` importance. `high` events
218
- // (failures/limits) are retained even past the cap — losing a failure
219
- // signal is worse than briefly exceeding the buffer size. If every retained
220
- // event is `high` (all-failures session), nothing is evicted and the buffer
221
- // grows; in practice frequent `low`/`normal` events keep it bounded.
222
- const evictAt = buffer.events.findIndex((e) => e.importance !== 'high');
223
- if (evictAt !== -1) {
224
- buffer.events.splice(evictAt, 1);
225
- }
226
- }
227
- }
228
-
229
- /**
230
- * Why a turn failed or was retried — stamped as `detail.reason` on `turn.error`
231
- * and `turn.retry` events, and (since PTC-0) surfaced at the driver's loop
232
- * boundary via {@link ChatDriverResult.failureReason}. Re-exported from
233
- * `@genesislcap/foundation-ai` — where it lives alongside `ChatDriverResult` so
234
- * the boundary field and this log surface can never drift apart — under its
235
- * historical name so existing importers are unaffected. Enumerated so the set
236
- * stays in sync with the README and call sites can't drift to ad-hoc strings.
237
- */
238
- export type { TurnFailureReason };
239
-
240
- /**
241
- * Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
242
- * is typed so the high-importance triage surface stays consistent; pass any
243
- * diagnostic specifics (attempt counts, offending tool names, etc.) in `detail`.
244
- */
245
- export function recordTurnError(
246
- key: string,
247
- reason: TurnFailureReason,
248
- detail?: Record<string, unknown>,
249
- ): void {
250
- recordMetaEvent(key, 'turn.error', { reason, ...detail });
251
- }
252
-
253
- /**
254
- * Record a recoverable retry *within* a turn (`turn.retry`, importance `normal`)
255
- * — e.g. one malformed/empty attempt that will be retried, as distinct from the
256
- * final attempt that bails out as a `turn.error`. Include `attempt`/`maxAttempts`
257
- * in `detail` so the timeline shows which try this was.
258
- */
259
- export function recordTurnRetry(
260
- key: string,
261
- reason: TurnFailureReason,
262
- detail?: Record<string, unknown>,
263
- ): void {
264
- recordMetaEvent(key, 'turn.retry', { reason, ...detail });
265
- }
266
-
267
- /** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
268
- export function getMetaEvents(key: string): ReadonlyArray<MetaEvent> {
269
- return registry.get(key)?.events ?? [];
270
- }
271
-
272
- /**
273
- * Merge pre-built meta events into the timeline for `targetKey`, **preserving each
274
- * event's original `timestamp`** (so it interleaves chronologically on export)
275
- * while re-indexing onto the target buffer's monotonic counter. Used to fold a
276
- * sub-agent's harvested events into the parent session — see
277
- * `ChatDriver.invokeSubAgent`. Unlike {@link recordMetaEvent} it does not stamp a
278
- * fresh timestamp; the same non-`high` eviction policy is applied once after the
279
- * batch so the buffer stays bounded.
280
- */
281
- export function mergeMetaEvents(targetKey: string, events: readonly MetaEvent[]): void {
282
- if (events.length === 0) return;
283
- let buffer = registry.get(targetKey);
284
- if (!buffer) {
285
- buffer = { events: [], next: 0 };
286
- registry.set(targetKey, buffer);
287
- }
288
- for (const event of events) {
289
- buffer.events.push({ ...event, index: buffer.next });
290
- buffer.next += 1;
291
- }
292
- // Evict oldest non-`high` events until back under the cap — same policy as
293
- // recordMetaEvent; `high` events (failures/limits) are never dropped. Single
294
- // pass: take the overflow count, then drop that many of the oldest non-`high`
295
- // events in order (if there aren't enough non-`high`, the buffer floats above
296
- // the cap rather than losing a failure signal).
297
- let toEvict = buffer.events.length - DEFAULT_MAX_META_EVENTS;
298
- if (toEvict > 0) {
299
- buffer.events = buffer.events.filter((e) => {
300
- if (toEvict > 0 && e.importance !== 'high') {
301
- toEvict -= 1;
302
- return false;
303
- }
304
- return true;
305
- });
306
- }
307
- }
308
-
309
- /**
310
- * Drop the entire timeline for `key`. Used to discard a sub-agent's transient
311
- * per-invocation session once its events have been harvested into the parent, so
312
- * the module registry doesn't accumulate one orphaned bucket per sub-agent run.
313
- */
314
- export function clearSession(key: string): void {
315
- registry.delete(key);
316
- }
317
-
318
- /**
319
- * Human/agent-facing guide emitted as the first key of the exported debug log,
320
- * so whoever opens the JSON (often an AI agent) knows how to read it without
321
- * reverse-engineering the shape. Kept here next to the event catalogue it
322
- * describes — but proximity alone demonstrably did NOT keep the two in sync
323
- * (`refusal` was missing from both reason lists for a whole release), so
324
- * `debug-event-log.test.ts` now asserts that every `TurnFailureReason` and every
325
- * `SubAgentFailureReason` is named here, and fails the build when one is added
326
- * without a matching sentence. Enumerations below read as exhaustive and WILL be
327
- * trusted by an agent reading the JSON — keep them so.
328
- */
329
- export const DEBUG_LOG_README: readonly string[] = [
330
- 'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
331
- "`sessionUsage` is what this session spent: `costUsd` (USD, provider-reported per request and summed, cache discounts already applied) plus the four TOKEN BUCKETS — `uncachedInputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `outputTokens`. Those four are disjoint and safe to add up for a total-tokens figure; the per-message fields further down are NOT (see kind:'message'). Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a big token count against a small cost means the prompt was mostly cache hits, not a missing charge. It covers sub-agent turns and spend a compaction banked, so for the CONVERSATION it is the authoritative total: prefer it over re-summing the timeline, which can be short by whatever the ring buffers evicted. The same figures appear under `meta.context`. It is as current as the snapshot it came from: the assistant's own download stamps a fresh one, but a log stitched by another tool can carry an older one — compare `meta.timestamp` with the last timeline entry, and if it is well behind, read these totals as historical and fall back to summing the timeline. One scope caveat: it is derived from the transcript, so it excludes billed calls that produced no message (a blank or refused attempt that was retried — see kind:'turn'.`usage`). Summing the turns can therefore come out HIGHER than this, and the difference is exactly that discarded spend, not a double-count.",
332
- "What 'session' means for those figures: the CURRENT conversation under this session key (a host-supplied per-project id, or the element id + header title when the host supplies none) — which is not necessarily everything in `timeline`. It is re-derived from the live transcript, so it spans page loads only when the chat is being remembered, and a Clear resets it to zero. `timeline` is append-only regardless: it keeps every page load and the pre-Clear conversation, with the session.cleared event as the boundary. So on a long-lived log, read these totals as the latest conversation's and the earlier sections as history — not as a total of the whole file.",
333
- '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
334
- 'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',
335
- "kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, where `inputTokens` is the WHOLE prompt for that request and `cacheReadTokens`/`cacheWriteTokens` BREAK IT DOWN rather than add to it — uncached input is `inputTokens` minus those two, and adding the cache fields to `inputTokens` counts the prompt twice. The cache fields are absent on providers that report no cache split (Gemini reports reads only, since implicit caching bills no write) and on messages persisted before they existed, so read them as 0 when missing. Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a large token count at a small cost means the prompt was mostly cache hits. `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.",
336
- "Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheWriteTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\"<parent> › <sub-agent>\"` breadcrumb (composing when nested, e.g. `\"UI Builder › Planner › Grounding\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.",
337
- "kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.",
338
- "kind:'turn'.`model`/`providerName`/`provider` — which model ran that call: the concrete model id, the registry slot it resolved under (a tier name like 'high'/'low', or the default), and the vendor. Recorded per CALL, so an agent whose `provider` selector varies by state — a flow that plans on a high tier and executes on a low one — has every step attributed, including calls that produced no message. `model` is the SERVING model where the provider reports one, so a turn answered by a server-side fallback names the model that answered rather than the one requested; it always matches the `model` on the message that call produced. Absent when the provider exposes no `getStatus` and the transport stamped nothing.",
339
- "kind:'turn'.`usage` — what that ONE call cost, in the same four-bucket + `costUsd` shape as `sessionUsage`. Absent while a call is in flight and on providers that report no usage. This is the SAME money as the message the call produced, not extra money: never add turn usage to message usage, and read a turn plus its message as one charge. Its distinct value is the calls that produced NO message — a blank or refused response is billed and then discarded before the retry, so the turn entry is the only record of that spend, and a turn with `usage` but no message after it is exactly that. `costUsd: 0` alongside a nonzero token count means the provider reported no price, not that the call was free.",
340
- "kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.",
341
- "kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.",
342
- "Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated, context.condensed). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
343
- 'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated/refusal/budget-exhausted/provider-refused, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, for budget-exhausted the budgetUsd + spentUsd figures reported by the proxy plus the resolved vendor, for provider-refused the kind (spend|auth) plus the upstream status and error type — the vendor refusing the ACCOUNT rather than us refusing to spend, so the remedy is a top-up or a key rotation rather than a raised cap, and the kind is the only place that distinction survives because the user-facing copy is deliberately cause-free, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated/refusal/budget_exhausted/provider_refused; budget_exhausted and provider_refused are both terminal for the PARENT turn too — the parent stops rather than calling the model again into the same wall), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), provider.selected (the resolved provider for the upcoming turns — detail.provider is the registry SLOT/tier name, detail.model the concrete model behind it and detail.vendor its vendor; emitted only when the slot CHANGES, so read the per-turn `model` for the model of any given call rather than assuming the nearest event still applies), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
344
- 'Sub-agent meta events: a sub-agent\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `"<parent> › <sub-agent>"` breadcrumb that composes when nested (e.g. `"UI Builder › Planner › Grounding"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\'s span.',
345
- "`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, contextTokens/contextLimit/contextUsagePercent for the last call, and the session totals — sessionCostUsd, sessionTokensConsumed, and the four-bucket sessionUsage lifted to the top of this log), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.",
346
- 'Note the two different scopes in `meta.context`: `contextTokens` is the prompt size of the LAST call (against `contextLimit`, the model context window), while `sessionUsage`/`sessionTokensConsumed` are cumulative BILLED throughput. Every turn resends the conversation, so the cumulative figure counts each turn’s prompt again in the next turn’s and is expected to dwarf the context size — that is not double-counting.',
347
- 'To debug a failure: find the last turn.error or tool.failed, then read upward for the user message, the turn(s), and the agent/provider/state events that led into it.',
348
- ];
349
-
350
- /**
351
- * Removes all entries. Exposed for test isolation only — not part of the
352
- * public API.
353
- *
354
- * @internal
355
- */
356
- export function clearMetaEventRegistry(): void {
357
- registry.clear();
358
- }
@@ -1,70 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import type { AiDriver } from '../components/ai-driver/ai-driver';
3
- import {
4
- clearDriverRegistry,
5
- deleteDriver,
6
- getDriver,
7
- getDriverAgentsKey,
8
- getOrCreateDriver,
9
- } from './driver-registry';
10
-
11
- // Minimal stub — the registry only stores and returns the reference; it never
12
- // invokes driver methods, so an opaque object is sufficient.
13
- const stub = (id: string): AiDriver => ({ id }) as unknown as AiDriver;
14
-
15
- const suite = createLogicSuite('driver-registry agents-key tracking');
16
-
17
- suite('getOrCreateDriver creates once and records the agents key', () => {
18
- clearDriverRegistry();
19
- const d = stub('a');
20
- const created = getOrCreateDriver('k', 'agentsKeyA', () => d);
21
- assert.is(created, d);
22
- assert.is(getDriver('k'), d);
23
- assert.is(getDriverAgentsKey('k'), 'agentsKeyA');
24
- });
25
-
26
- suite('reuse returns the existing driver and keeps its original agents key', () => {
27
- clearDriverRegistry();
28
- const first = stub('first');
29
- getOrCreateDriver('k', 'agentsKeyA', () => first);
30
- // A second component sharing the key, even with a different desired agents
31
- // fingerprint, must NOT replace the driver or overwrite the recorded key.
32
- let factoryCalled = false;
33
- const reused = getOrCreateDriver('k', 'agentsKeyB', () => {
34
- factoryCalled = true;
35
- return stub('second');
36
- });
37
- assert.is(reused, first);
38
- assert.is(factoryCalled, false);
39
- assert.is(getDriverAgentsKey('k'), 'agentsKeyA');
40
- });
41
-
42
- suite('deleteDriver clears both the driver and its recorded agents key', () => {
43
- clearDriverRegistry();
44
- getOrCreateDriver('k', 'agentsKeyA', () => stub('a'));
45
- deleteDriver('k');
46
- assert.is(getDriver('k'), undefined);
47
- assert.is(getDriverAgentsKey('k'), undefined);
48
- });
49
-
50
- suite('a rebuild under the same key records the new agents key (the adopt signal)', () => {
51
- clearDriverRegistry();
52
- // Initial driver, built for the old agent set.
53
- getOrCreateDriver('k', 'old', () => stub('d-old'));
54
- // Rebuild: the host deletes then recreates with the new fingerprint.
55
- deleteDriver('k');
56
- const rebuilt = stub('d-new');
57
- getOrCreateDriver('k', 'new', () => rebuilt);
58
- // GENC-1388: a sibling sharing the key can now detect that the registry driver
59
- // already matches its desired agents and adopt `rebuilt`, rather than
60
- // recreating (which would orphan a driver mid-turn).
61
- assert.is(getDriver('k'), rebuilt);
62
- assert.is(getDriverAgentsKey('k'), 'new');
63
- });
64
-
65
- suite('getDriverAgentsKey is undefined for an unknown key', () => {
66
- clearDriverRegistry();
67
- assert.is(getDriverAgentsKey('missing'), undefined);
68
- });
69
-
70
- suite.run();