@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,228 +0,0 @@
1
- import type { ChatMessage } from '@genesislcap/foundation-ai';
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import {
4
- buildCompactionSummaryPrompt,
5
- compactionDividerLabel,
6
- COMPACT_KEEP_RECENT_MESSAGES,
7
- COMPACT_MIN_MESSAGES_TO_COMPACT,
8
- findCompactionCut,
9
- normalizeForProvider,
10
- transformHistoryForAgent,
11
- } from './history-transform';
12
-
13
- const asMsg = (o: Record<string, unknown>): ChatMessage => o as unknown as ChatMessage;
14
- const user = (content: string) => asMsg({ role: 'user', content });
15
- const assistant = (content: string) => asMsg({ role: 'assistant', content });
16
-
17
- // GENC-1351 §5.7 — the pure compaction helpers (no DOM/provider deps).
18
- const Suite = createLogicSuite('history-transform-compaction');
19
-
20
- // ── findCompactionCut ─────────────────────────────────────────────────────────
21
-
22
- Suite('findCompactionCut returns null for a transcript too short to be worth compacting', () => {
23
- assert.is(findCompactionCut([]), null);
24
- // One below the floor (keep-recent + min-to-compact) → nothing to do.
25
- const justUnder = Array.from(
26
- { length: COMPACT_KEEP_RECENT_MESSAGES + COMPACT_MIN_MESSAGES_TO_COMPACT - 1 },
27
- (_, i) => (i % 2 === 0 ? user(`u${i}`) : assistant(`a${i}`)),
28
- );
29
- assert.is(findCompactionCut(justUnder), null);
30
- });
31
-
32
- Suite('findCompactionCut cuts at a user-message boundary, keeping the recent tail', () => {
33
- // 10 messages alternating user/assistant; user at even indices.
34
- const h = Array.from({ length: 10 }, (_, i) =>
35
- i % 2 === 0 ? user(`u${i}`) : assistant(`a${i}`),
36
- );
37
- const cut = findCompactionCut(h);
38
- // target = 10 - 4 = 6; history[6] is a user message → cut there.
39
- assert.is(cut, 6);
40
- assert.is(h[cut!].role, 'user', 'tail must start on a user boundary');
41
- });
42
-
43
- Suite('findCompactionCut walks back to the nearest earlier user boundary', () => {
44
- // user only at indices 0,1,3,5,7,9 — index 6 is assistant, so it walks back to 5.
45
- const h = [
46
- user('u0'),
47
- user('u1'),
48
- assistant('a2'),
49
- user('u3'),
50
- assistant('a4'),
51
- user('u5'),
52
- assistant('a6'),
53
- user('u7'),
54
- assistant('a8'),
55
- user('u9'),
56
- ];
57
- // target = 6 (assistant) → walk back to 5 (user).
58
- assert.is(findCompactionCut(h), 5);
59
- });
60
-
61
- Suite('findCompactionCut returns null when the only user turn is the first message', () => {
62
- // No user boundary after index 0 → nothing can be summarized behind a boundary.
63
- const h = [user('u0'), ...Array.from({ length: 9 }, (_, i) => assistant(`a${i}`))];
64
- assert.is(findCompactionCut(h), null);
65
- });
66
-
67
- Suite(
68
- 'findCompactionCut includes an existing head summary in the region to compact (rolling)',
69
- () => {
70
- const h = [
71
- asMsg({ role: 'compacted-summary', content: 'prior', compaction: { compactedCount: 3 } }),
72
- user('u1'),
73
- assistant('a2'),
74
- user('u3'),
75
- assistant('a4'),
76
- user('u5'),
77
- assistant('a6'),
78
- user('u7'),
79
- assistant('a8'),
80
- user('u9'),
81
- ];
82
- const cut = findCompactionCut(h);
83
- assert.ok(
84
- cut != null && cut >= 1,
85
- 'cut should leave the head summary inside the compacted region',
86
- );
87
- assert.is(h.slice(0, cut!)[0].role, 'compacted-summary', 'prior summary is folded back in');
88
- },
89
- );
90
-
91
- // ── buildCompactionSummaryPrompt ──────────────────────────────────────────────
92
-
93
- Suite('buildCompactionSummaryPrompt wraps the transcript and labels roles', () => {
94
- const { systemPrompt, userMessage } = buildCompactionSummaryPrompt([
95
- user('book a trade'),
96
- assistant('sure, which counterparty?'),
97
- ]);
98
- assert.ok(/summar/i.test(systemPrompt), 'system prompt frames a summarization task');
99
- assert.ok(userMessage.includes('<conversation_history>'));
100
- assert.ok(userMessage.includes('</conversation_history>'));
101
- assert.ok(userMessage.includes('User: book a trade'));
102
- assert.ok(userMessage.includes('Assistant: sure, which counterparty?'));
103
- });
104
-
105
- Suite('buildCompactionSummaryPrompt renders tool activity and a prior summary', () => {
106
- const { userMessage } = buildCompactionSummaryPrompt([
107
- asMsg({
108
- role: 'compacted-summary',
109
- content: 'earlier stuff',
110
- compaction: { compactedCount: 2 },
111
- }),
112
- asMsg({
113
- role: 'assistant',
114
- content: '',
115
- toolCalls: [{ id: 't1', name: 'searchTrades', args: {} }],
116
- }),
117
- asMsg({ role: 'tool', content: '', toolResult: { toolCallId: 't1', content: '3 hits' } }),
118
- ]);
119
- assert.ok(userMessage.includes('[Earlier summary] earlier stuff'));
120
- assert.ok(userMessage.includes('searchTrades'), 'tool call name is preserved for continuity');
121
- assert.ok(userMessage.includes('Tool result: 3 hits'));
122
- });
123
-
124
- Suite(
125
- 'buildCompactionSummaryPrompt folds in reasoning + narration, labelled Thinking / Working',
126
- () => {
127
- const { userMessage } = buildCompactionSummaryPrompt([
128
- asMsg({
129
- role: 'assistant',
130
- content: 'weigh the counterparty options',
131
- category: 'reasoning',
132
- }),
133
- asMsg({ role: 'assistant', content: 'searching the trade blotter', category: 'narration' }),
134
- ]);
135
- assert.ok(userMessage.includes('Thinking: weigh the counterparty options'));
136
- assert.ok(userMessage.includes('Working: searching the trade blotter'));
137
- },
138
- );
139
-
140
- // ── normalizeForProvider (the no-leak guarantee) ──────────────────────────────
141
-
142
- Suite(
143
- 'normalizeForProvider translates compacted-summary into a <conversation_summary> user msg',
144
- () => {
145
- const out = normalizeForProvider([
146
- asMsg({ role: 'compacted-summary', content: 'the gist', compaction: { compactedCount: 9 } }),
147
- user('and now?'),
148
- ]);
149
- assert.is(out[0].role, 'user', 'the raw display-only role is gone');
150
- assert.ok(out[0].content.includes('<conversation_summary>'));
151
- assert.ok(out[0].content.includes('the gist'));
152
- assert.is(out[0].compaction, undefined, 'compaction metadata is dropped from the sent message');
153
- },
154
- );
155
-
156
- Suite('normalizeForProvider never emits a compacted-summary role (no leak to a transport)', () => {
157
- const out = normalizeForProvider([
158
- asMsg({ role: 'compacted-summary', content: 's1', compaction: { compactedCount: 1 } }),
159
- user('u'),
160
- assistant('a'),
161
- asMsg({ role: 'system-event', content: 'Switched agent' }),
162
- ]);
163
- assert.ok(
164
- out.every((m) => m.role !== 'compacted-summary'),
165
- 'no compacted-summary role survives normalization',
166
- );
167
- });
168
-
169
- Suite('normalizeForProvider passes non-summary messages through untouched (same ref)', () => {
170
- const u = user('hello');
171
- const a = assistant('hi');
172
- const out = normalizeForProvider([u, a]);
173
- assert.is(out[0], u);
174
- assert.is(out[1], a);
175
- });
176
-
177
- // ── compactionDividerLabel ────────────────────────────────────────────────────
178
-
179
- Suite('compactionDividerLabel pluralizes and falls back to 0', () => {
180
- assert.is(
181
- compactionDividerLabel(
182
- asMsg({ role: 'compacted-summary', content: '', compaction: { compactedCount: 1 } }),
183
- ),
184
- 'Compacted 1 earlier message',
185
- );
186
- assert.is(
187
- compactionDividerLabel(
188
- asMsg({ role: 'compacted-summary', content: '', compaction: { compactedCount: 12 } }),
189
- ),
190
- 'Compacted 12 earlier messages',
191
- );
192
- assert.is(
193
- compactionDividerLabel(asMsg({ role: 'compacted-summary', content: '' })),
194
- 'Compacted 0 earlier messages',
195
- );
196
- });
197
-
198
- // ── masking another agent's tool payload (GENC-1508) ─────────────────────────
199
-
200
- Suite('transformHistoryForAgent drops tool-result IMAGES, not just the text', () => {
201
- const history: ChatMessage[] = [
202
- asMsg({
203
- role: 'tool',
204
- content: '',
205
- agentName: 'other-agent',
206
- toolResult: {
207
- toolCallId: 'r1',
208
- content: 'rendered the mockup',
209
- attachments: [
210
- { kind: 'image', name: 'mockup.png', mimeType: 'image/png', data: 'a'.repeat(2000) },
211
- ],
212
- },
213
- }),
214
- ];
215
- const out = transformHistoryForAgent(history, 'this-agent');
216
- assert.ok(
217
- out[0].toolResult!.content.includes('omitted'),
218
- 'the text is stubbed, as it always was',
219
- );
220
- assert.is(
221
- out[0].toolResult!.attachments,
222
- undefined,
223
- "masking exists to keep another agent's payload out of context — an image IS the payload, " +
224
- 'and stubbing the prose while shipping the render defeats the mask where it matters most',
225
- );
226
- });
227
-
228
- Suite.run();
@@ -1,163 +0,0 @@
1
- import type { ChatMessage } from '@genesislcap/foundation-ai';
2
-
3
- /**
4
- * Masks the tool-specific payload of a single message — clears tool call args
5
- * and replaces tool result content with a placeholder. Used when passing history
6
- * to an agent that should not see another agent's implementation detail.
7
- */
8
- function maskToolPayload(msg: ChatMessage): ChatMessage {
9
- if (msg.toolCalls?.length) {
10
- return { ...msg, toolCalls: msg.toolCalls.map((tc) => ({ ...tc, args: {} })) };
11
- }
12
- if (msg.toolResult) {
13
- // `attachments` is dropped, not spread through. Masking exists to keep another agent's
14
- // payload out of this one's context, and an image is the largest payload there is — spreading
15
- // it would stub the prose while still shipping the render, defeating the mask for exactly the
16
- // case it matters most. `applyHistoryCap` masks everything past the cap for sub-agent context,
17
- // so without this every older image rides every sub-agent turn under an "omitted" label.
18
- const { attachments: _masked, ...restOfResult } = msg.toolResult;
19
- return {
20
- ...msg,
21
- toolResult: { ...restOfResult, content: "[other agent's tool result omitted]" },
22
- };
23
- }
24
- return msg;
25
- }
26
-
27
- /**
28
- * Prepares history for the LLM only: masks tool call args and results from other
29
- * agents so the active specialist is not confused by tools it does not have.
30
- * Canonical history in `ChatDriver` stays unmasked for UI and logging.
31
- */
32
- export function transformHistoryForAgent(history: ChatMessage[], agentName: string): ChatMessage[] {
33
- return history.map((msg) => {
34
- if (!msg.agentName || msg.agentName === agentName) return msg;
35
- return maskToolPayload(msg);
36
- });
37
- }
38
-
39
- /**
40
- * Applies a history cap for sub-agent context: the last `cap` messages are passed
41
- * verbatim; older messages have their tool payloads masked.
42
- */
43
- export function applyHistoryCap(history: readonly ChatMessage[], cap: number): ChatMessage[] {
44
- const cutoff = history.length - cap;
45
- return history.map((msg, i) => (i < cutoff ? maskToolPayload(msg) : msg));
46
- }
47
-
48
- // ───────────────────────────── Compaction (GENC-1351 §5.7) ─────────────────────────────
49
-
50
- /** Recent messages kept verbatim when compacting; everything earlier is summarized. */
51
- export const COMPACT_KEEP_RECENT_MESSAGES = 4;
52
-
53
- /** Don't bother compacting unless at least this many messages would be summarized. */
54
- export const COMPACT_MIN_MESSAGES_TO_COMPACT = 4;
55
-
56
- /**
57
- * Index at which the verbatim tail begins — everything before it gets summarized.
58
- * The tail starts on a clean turn boundary (a `user` message): a user message
59
- * never appears between a `tool_use` and its `tool_result`, so cutting there
60
- * guarantees the summarized region is tool-pair-balanced (compaction only runs
61
- * when idle, so history is turn-complete). Returns `null` when there is nothing
62
- * worth compacting (too short, or no earlier user boundary) — e.g. a freshly
63
- * restored session whose only user turn is the current one.
64
- */
65
- export function findCompactionCut(history: readonly ChatMessage[]): number | null {
66
- if (history.length < COMPACT_KEEP_RECENT_MESSAGES + COMPACT_MIN_MESSAGES_TO_COMPACT) {
67
- return null;
68
- }
69
- const target = history.length - COMPACT_KEEP_RECENT_MESSAGES;
70
- for (let i = target; i > 0; i -= 1) {
71
- if (history[i].role === 'user') {
72
- return i >= COMPACT_MIN_MESSAGES_TO_COMPACT ? i : null;
73
- }
74
- }
75
- return null;
76
- }
77
-
78
- /** One-line rendering of a message for the summarizer transcript. */
79
- function renderMessageForSummary(m: ChatMessage): string {
80
- switch (m.role) {
81
- case 'compacted-summary':
82
- return `[Earlier summary] ${m.content}`;
83
- case 'user':
84
- return m.content ? `User: ${m.content}` : '';
85
- case 'assistant': {
86
- // Reasoning ("Thinking") and narration ("Working") are display-only assistant splits
87
- // (GENC-1410) with no tool calls of their own — fold them into the summary too, labelled
88
- // the same as the UI so the summarizer knows they were internal, not a final answer.
89
- if (m.category === 'reasoning') {
90
- return m.content ? `Thinking: ${m.content}` : '';
91
- }
92
- if (m.category === 'narration') {
93
- return m.content ? `Working: ${m.content}` : '';
94
- }
95
- if (m.toolCalls?.length) {
96
- const calls = m.toolCalls.map((t) => t.name).join(', ');
97
- return m.content
98
- ? `Assistant: ${m.content} (called: ${calls})`
99
- : `Assistant called: ${calls}`;
100
- }
101
- return m.content ? `Assistant: ${m.content}` : '';
102
- }
103
- case 'tool':
104
- return m.toolResult ? `Tool result: ${m.toolResult.content}` : '';
105
- default:
106
- return ''; // system-event / synthetic-user — not part of the model transcript
107
- }
108
- }
109
-
110
- /**
111
- * Build the one-shot summarizer prompt for a run of older messages (used with
112
- * `AIProvider.prompt`). Mirrors the `<conversation_history>` block convention
113
- * that suggestions use. Any existing `compacted-summary` at the head is folded
114
- * in (rendered as prior-summary context), so rolling re-compaction produces one
115
- * combined summary.
116
- */
117
- export function buildCompactionSummaryPrompt(toCompact: readonly ChatMessage[]): {
118
- systemPrompt: string;
119
- userMessage: string;
120
- } {
121
- const transcript = toCompact.map(renderMessageForSummary).filter(Boolean).join('\n');
122
- const systemPrompt =
123
- 'You compress a conversation transcript into a concise summary that lets an AI ' +
124
- 'assistant continue the conversation seamlessly. Preserve key facts, decisions, ' +
125
- 'what the user wants, entity names and IDs, and any unresolved threads. Write ' +
126
- 'plain prose. Never roleplay or continue the conversation.';
127
- const userMessage =
128
- `<conversation_history>\n${transcript}\n</conversation_history>\n\n` +
129
- 'Summarize the conversation above so it can be dropped from context while ' +
130
- 'preserving everything needed to continue. Output only the summary prose.';
131
- return { systemPrompt, userMessage };
132
- }
133
-
134
- /**
135
- * Normalize history for a provider request (GENC-1351 §5.7): translate each
136
- * `compacted-summary` message into a `user` message that wraps its summary in a
137
- * `<conversation_summary>` block, so the raw display-only role never reaches a
138
- * transport. Other roles pass through untouched. Pure — returns a new array.
139
- *
140
- * Applied at the request-build chokepoint *before* per-agent masking, so the
141
- * guarantee holds for every provider/transport regardless of any optional
142
- * per-agent history transform.
143
- */
144
- export function normalizeForProvider(history: readonly ChatMessage[]): ChatMessage[] {
145
- return history.map((m) => {
146
- if (m.role !== 'compacted-summary') return m;
147
- return {
148
- ...m,
149
- role: 'user',
150
- content:
151
- `<conversation_summary>\n${m.content}\n</conversation_summary>\n\n` +
152
- 'The above summarizes earlier parts of this conversation that were compacted. ' +
153
- 'Continue naturally from it.',
154
- compaction: undefined,
155
- };
156
- });
157
- }
158
-
159
- /** Human-facing divider label for a `compacted-summary` message. */
160
- export function compactionDividerLabel(m: ChatMessage): string {
161
- const n = m.compaction?.compactedCount ?? 0;
162
- return `Compacted ${n} earlier message${n === 1 ? '' : 's'}`;
163
- }
@@ -1,3 +0,0 @@
1
- export * from './animated-panel-toggle';
2
- export * from './logger';
3
- export * from './tool-fold';
@@ -1,3 +0,0 @@
1
- import { createLogger } from '@genesislcap/foundation-logger';
2
-
3
- export const logger = createLogger('ai-assistant');
@@ -1,129 +0,0 @@
1
- import type { ChatInteraction, ChatMessage } from '@genesislcap/foundation-ai';
2
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
- import {
4
- filterVisibleMessages,
5
- type MessageFilterState,
6
- SESSION_RESTORED_LABEL,
7
- SESSION_RESTORE_FAILED_LABEL,
8
- trailingInteractionRow,
9
- } from './message-partition';
10
-
11
- const msg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
12
- role: 'assistant',
13
- content: '',
14
- ...overrides,
15
- });
16
-
17
- const interaction = (interactionId: string, resolved = false): ChatInteraction => ({
18
- interactionId,
19
- componentName: 'test-widget',
20
- data: {},
21
- ...(resolved ? { resolved: { status: 'approved' } } : {}),
22
- });
23
-
24
- const ALL_ON: MessageFilterState = {
25
- showToolCalls: true,
26
- showThinkingSteps: true,
27
- showNarration: true,
28
- showAgentSwitchIndicator: true,
29
- showRestoreNotices: true,
30
- };
31
- const ALL_OFF: MessageFilterState = {
32
- showToolCalls: false,
33
- showThinkingSteps: false,
34
- showNarration: false,
35
- showAgentSwitchIndicator: false,
36
- showRestoreNotices: false,
37
- };
38
-
39
- const trailing = createLogicSuite('trailingInteractionRow');
40
-
41
- trailing('is empty when there are no messages', () => {
42
- assert.equal(trailingInteractionRow([]), []);
43
- });
44
-
45
- trailing('is empty when the last message has no interaction', () => {
46
- assert.equal(trailingInteractionRow([msg({ content: 'hi' })]), []);
47
- });
48
-
49
- trailing('returns the last message when it carries an interaction (pending)', () => {
50
- const pending = msg({ interaction: interaction('id-a') });
51
- assert.equal(trailingInteractionRow([msg({ content: 'q' }), pending]), [pending]);
52
- });
53
-
54
- trailing('still returns the trailing interaction once it is resolved', () => {
55
- // Keeps it in the pinned slot so resolving forwards in place without a rebuild.
56
- const resolved = msg({ interaction: interaction('id-a', true) });
57
- assert.equal(trailingInteractionRow([resolved]), [resolved]);
58
- });
59
-
60
- trailing('returns nothing once a newer non-interaction message arrives', () => {
61
- // The interaction is no longer last, so it rejoins the scrolling list.
62
- const earlier = msg({ interaction: interaction('id-a', true) });
63
- assert.equal(trailingInteractionRow([earlier, msg({ content: 'next' })]), []);
64
- });
65
-
66
- trailing('returns the same message reference (lets FAST short-circuit rebind)', () => {
67
- const pending = msg({ interaction: interaction('id-a') });
68
- assert.is(trailingInteractionRow([pending])[0], pending);
69
- });
70
-
71
- const filter = createLogicSuite('filterVisibleMessages');
72
-
73
- filter('hides tool result messages regardless of toggles', () => {
74
- const msgs = [msg({ role: 'tool', toolResult: { toolCallId: 't1', content: 'r' } })];
75
- assert.equal(filterVisibleMessages(msgs, ALL_ON), []);
76
- });
77
-
78
- filter('hides tool-call messages when showToolCalls is off, shows them when on', () => {
79
- const toolCall = msg({ toolCalls: [{ id: 't1', name: 'do', args: {} } as any] });
80
- assert.equal(filterVisibleMessages([toolCall], ALL_OFF), []);
81
- assert.equal(filterVisibleMessages([toolCall], ALL_ON), [toolCall]);
82
- });
83
-
84
- filter('hides thinking messages when showThinkingSteps is off', () => {
85
- const thinking = msg({ thinking: true, content: 'hmm' });
86
- assert.equal(filterVisibleMessages([thinking], ALL_OFF), []);
87
- assert.equal(filterVisibleMessages([thinking], ALL_ON), [thinking]);
88
- });
89
-
90
- filter('hides system-event rows unless the agent-switch toggle is on', () => {
91
- const event = msg({ role: 'system-event', content: 'switched' });
92
- assert.equal(filterVisibleMessages([event], ALL_OFF), []);
93
- assert.equal(filterVisibleMessages([event], ALL_ON), [event]);
94
- });
95
-
96
- filter('drops empty assistant messages', () => {
97
- assert.equal(filterVisibleMessages([msg({ content: ' ' })], ALL_ON), []);
98
- });
99
-
100
- filter('keeps user and non-empty assistant messages with everything off', () => {
101
- const user = msg({ role: 'user', content: 'hello' });
102
- const reply = msg({ content: 'hi there' });
103
- assert.equal(filterVisibleMessages([user, reply], ALL_OFF), [user, reply]);
104
- });
105
-
106
- // GENC-1351 §6: restore notices are gated by their own (persistence) toggle, NOT
107
- // by showAgentSwitchIndicator — the two unrelated switches must not cross-control.
108
- filter(
109
- 'restore notices follow showRestoreNotices; agent-switch rows follow showAgentSwitchIndicator',
110
- () => {
111
- const restored = msg({ role: 'system-event', content: SESSION_RESTORED_LABEL });
112
- const failed = msg({ role: 'system-event', content: SESSION_RESTORE_FAILED_LABEL });
113
- const agentSwitch = msg({ role: 'system-event', content: 'Guided Booking' });
114
- const rows = [restored, failed, agentSwitch];
115
-
116
- // Persistence on, agent-switch off → only the restore notices show.
117
- assert.equal(filterVisibleMessages(rows, { ...ALL_OFF, showRestoreNotices: true }), [
118
- restored,
119
- failed,
120
- ]);
121
- // Agent-switch on, persistence off → only the agent-switch indicator shows.
122
- assert.equal(filterVisibleMessages(rows, { ...ALL_OFF, showAgentSwitchIndicator: true }), [
123
- agentSwitch,
124
- ]);
125
- },
126
- );
127
-
128
- trailing.run();
129
- filter.run();
@@ -1,115 +0,0 @@
1
- import type { ChatMessage } from '@genesislcap/foundation-ai';
2
-
3
- /**
4
- * Display-only session-restore notices (GENC-1351 §6): the "Restored…" divider on
5
- * a successful reload restore, and the "Couldn't restore…" divider when a saved
6
- * session existed but couldn't be brought back (timeout / error / unusable
7
- * snapshot). Both are `system-event` rows, stripped from the persisted snapshot so
8
- * they never become permanent history — and, unlike agent-switch indicators, they
9
- * belong to the persistence ("remember chat") feature, so `filterVisibleMessages`
10
- * gates them by `showRestoreNotices` rather than `showAgentSwitchIndicator`.
11
- *
12
- * @internal
13
- */
14
- export const SESSION_RESTORED_LABEL = 'Restored previous conversation';
15
- /** @internal */
16
- export const SESSION_RESTORE_FAILED_LABEL = "Couldn't restore the previous conversation";
17
- /** @internal */
18
- export const isSessionRestoredMarker = (m: ChatMessage): boolean =>
19
- m.role === 'system-event' && m.content === SESSION_RESTORED_LABEL;
20
- /** @internal */
21
- export const isSessionRestoreFailedMarker = (m: ChatMessage): boolean =>
22
- m.role === 'system-event' && m.content === SESSION_RESTORE_FAILED_LABEL;
23
- /** Either restore notice — both display-only, stripped before persist. @internal */
24
- export const isSessionNoticeMarker = (m: ChatMessage): boolean =>
25
- isSessionRestoredMarker(m) || isSessionRestoreFailedMarker(m);
26
-
27
- /**
28
- * Toggle state that drives which messages appear in the scrolling list. Mirrors
29
- * the cog-menu switches; `showAgentSwitchIndicator` is pre-resolved by the
30
- * caller (it falls back to `showToolCalls` when the host hasn't configured an
31
- * explicit agent-switch toggle).
32
- */
33
- export interface MessageFilterState {
34
- showToolCalls: boolean;
35
- showThinkingSteps: boolean;
36
- showNarration: boolean;
37
- showAgentSwitchIndicator: boolean;
38
- /**
39
- * Whether session-restore notices show — driven by the persistence ("remember
40
- * chat") toggle, since restore is that feature's concern (GENC-1351 §6). Kept
41
- * separate from `showAgentSwitchIndicator` so those two unrelated switches don't
42
- * cross-control each other.
43
- */
44
- showRestoreNotices: boolean;
45
- }
46
-
47
- /**
48
- * The trailing interaction to render in a pinned slot, separate from the
49
- * filtered list. Empty unless the most recent message carries an interaction.
50
- *
51
- * Pinning the trailing interaction keeps its widget out of the list's
52
- * reconciliation: FAST's `repeat` has no key function and reconciles by array
53
- * index, so re-filtering the list (a cog-menu toggle) would otherwise rebind an
54
- * existing row to a different message and re-create the live widget — discarding
55
- * any in-progress input. The slot's binding reads only `messages` and returns
56
- * the same message object across toggles, so the widget's DOM is preserved.
57
- *
58
- * It is the *trailing* message (resolved or not), not "unresolved only", so that
59
- * answering an interaction leaves it in the slot — `resolved` is forwarded in
60
- * place with no re-render. It rejoins the list naturally once a newer message
61
- * arrives and it is no longer last.
62
- */
63
- export function trailingInteractionRow(messages: ChatMessage[]): ChatMessage[] {
64
- const last = messages[messages.length - 1];
65
- return last?.interaction ? [last] : [];
66
- }
67
-
68
- /**
69
- * Filters messages for the scrolling list per the current cog-menu toggles.
70
- *
71
- * The trailing interaction (see `trailingInteractionRow`) must be removed
72
- * before calling, as it is rendered separately.
73
- */
74
- export function filterVisibleMessages(
75
- messages: ChatMessage[],
76
- toggles: MessageFilterState,
77
- ): ChatMessage[] {
78
- return messages.filter((m) => {
79
- // Compaction dividers are always shown — a real conversation landmark, not
80
- // optional UI noise (GENC-1351 §5.7).
81
- if (m.role === 'compacted-summary') {
82
- return true;
83
- }
84
- if (m.role === 'system-event') {
85
- // Restore notices belong to persistence ("remember chat"), agent-switch
86
- // indicators to their own toggle — gate each by its own switch.
87
- return isSessionNoticeMarker(m)
88
- ? toggles.showRestoreNotices
89
- : toggles.showAgentSwitchIndicator;
90
- }
91
- // Tool result messages are never shown to the user.
92
- if (m.role === 'tool') {
93
- return false;
94
- }
95
- // Reasoning (chain-of-thought summary) follows the thinking toggle.
96
- if (m.category === 'reasoning') {
97
- return toggles.showThinkingSteps;
98
- }
99
- // Narration (interstitial prose alongside a tool call) follows its own toggle.
100
- if (m.category === 'narration') {
101
- return toggles.showNarration;
102
- }
103
- // Legacy: sessions persisted before the reasoning/narration split tagged reasoning-ish
104
- // content with `thinking`; keep honouring the flag so their history still hides it.
105
- if (m.thinking && !toggles.showThinkingSteps) {
106
- return false;
107
- }
108
- // Drop empty assistant messages that slipped through.
109
- if (m.role === 'assistant' && !m.content?.trim() && !m.toolCalls?.length && !m.interaction) {
110
- return false;
111
- }
112
- const isToolRelated = !!(m.toolCalls?.length || m.toolResult);
113
- return !isToolRelated || toggles.showToolCalls;
114
- });
115
- }
@@ -1,38 +0,0 @@
1
- import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
2
- import { resolveCostHistoryConfig } from './resolve-cost-history-config';
3
-
4
- const Suite = createLogicSuite('resolveCostHistoryConfig');
5
-
6
- Suite('defaults to disabled with cost UI visible', () => {
7
- const resolved = resolveCostHistoryConfig({});
8
- assert.is(resolved.enabled, false);
9
- assert.is(resolved.showCost, true);
10
- assert.is(resolved.showTokens, true);
11
- assert.is(resolved.badgeLabel, 'Currently building');
12
- });
13
-
14
- Suite('enables persistence only when costHistory.enabled is true', () => {
15
- assert.is(resolveCostHistoryConfig({ costHistory: {} }).enabled, false);
16
- assert.is(resolveCostHistoryConfig({ costHistory: { enabled: true } }).enabled, true);
17
- });
18
-
19
- Suite('reads costHistory fields', () => {
20
- const resolved = resolveCostHistoryConfig({
21
- costHistory: {
22
- enabled: true,
23
- title: 'My project',
24
- scope: 'app-scope',
25
- showCost: false,
26
- showTokens: false,
27
- badgeLabel: 'Building',
28
- },
29
- });
30
-
31
- assert.is(resolved.title, 'My project');
32
- assert.is(resolved.scope, 'app-scope');
33
- assert.is(resolved.showCost, false);
34
- assert.is(resolved.showTokens, false);
35
- assert.is(resolved.badgeLabel, 'Building');
36
- });
37
-
38
- Suite.run();