@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,604 +0,0 @@
1
- import type {
2
- AggregateUsage,
3
- AIProviderRegistryStatusEntry,
4
- AIProviderType,
5
- ChatInputDuringExecutionMode,
6
- ChatMessage,
7
- } from '@genesislcap/foundation-ai';
8
- import type { PayloadAction } from '@genesislcap/foundation-redux';
9
- import { createSlice } from '@genesislcap/foundation-redux';
10
- import type { AgentConfig } from '../config/config';
11
- import type { AiAssistantAnimation, AiAssistantState, SuggestionsState } from '../main/main.types';
12
- import { emptyUsage, sumUsage } from '../utils/sum-usage';
13
-
14
- /**
15
- * A single in-flight per-call chat-input override pushed by a `requestSubAgent`
16
- * or `requestInteraction` call. Tracked as an array (not a counter) so the
17
- * slice can survive pop-in/pop-out and so a listener that connects
18
- * mid-execution can compute the effective mode without having seen the start
19
- * event.
20
- *
21
- * @internal
22
- */
23
- export interface InputOverride {
24
- /** Unique per-invocation id, paired with the start/stop events. */
25
- id: string;
26
- /** The mode requested for this invocation. */
27
- mode: ChatInputDuringExecutionMode;
28
- }
29
-
30
- /**
31
- * One vendor's AI-spend figures, as fed by the host from the budget endpoint
32
- * (GENC-1464 — the settings-modal budget meter).
33
- *
34
- * Dollars, not a percentage: the meter's text shows the real figures (spend CAN
35
- * exceed the cap — the last allowed call may overshoot it), and only the bar
36
- * clamps. Storing a pre-computed percentage would destroy the overshoot.
37
- *
38
- * @beta
39
- */
40
- export interface VendorBudgetFigures {
41
- /** The vendor's budget cap in USD. Finite and `>= 0`. */
42
- budgetUsd: number;
43
- /** Metered spend against that cap in USD. Finite and `>= 0`; may exceed the cap. */
44
- spentUsd: number;
45
- }
46
-
47
- /**
48
- * Shape of a single AI assistant session's serializable state.
49
- *
50
- * @internal
51
- */
52
- export interface AiAssistantSessionState {
53
- messages: ChatMessage[];
54
- state: AiAssistantState;
55
- showToolCalls: boolean;
56
- showThinkingSteps: boolean;
57
- showNarration: boolean;
58
- showAgentSwitchIndicator: boolean;
59
- enabledAnimations: AiAssistantAnimation[];
60
- suggestionsState: SuggestionsState;
61
- contextTokens: number | undefined;
62
- contextLimit: number | undefined;
63
- /**
64
- * Cost and per-bucket token totals across every chat turn in this session,
65
- * including sub-agent turns and spend banked by a compaction.
66
- *
67
- * One field rather than a cost scalar plus a token scalar: they are derived from
68
- * the same walk of the transcript, so keeping them together makes it impossible
69
- * for the two to disagree about which turns they counted.
70
- */
71
- sessionUsage: AggregateUsage;
72
- /** Active model id (e.g. `claude-sonnet-4-6`), resolved on connect. */
73
- activeModel: string | undefined;
74
- /**
75
- * Name of the AI provider used on the most recent turn — drives "current"
76
- * marker in the settings panel. `undefined` until the first turn runs.
77
- */
78
- activeProviderName: string | undefined;
79
- /**
80
- * Snapshot of every registered provider's status, in registration order.
81
- * Populated on connect (or on settings-open if not yet loaded) and reused
82
- * for the lifetime of the session — provider registration is static.
83
- *
84
- * Describes the registry rather than the conversation, so it survives
85
- * {@link resetSession} ("Clear" / "New chat"); see the reasoning there. Only
86
- * a registry change (via the observable-registry subscription) rewrites it.
87
- */
88
- providerStatuses: AIProviderRegistryStatusEntry[];
89
- activeAgent: Omit<AgentConfig, 'toolHandlers'> | undefined;
90
- /**
91
- * Name of the agent the user has pinned via the picker. `null` means the
92
- * orchestrator chooses each turn (Auto). Survives lifecycle events but
93
- * resets on page refresh. Only changed by explicit user picker action (or
94
- * the host's `setAgent` API) — never by the orchestrator's auto-pin on
95
- * stateful activation. See {@link flowOwnerAgentName} for that.
96
- */
97
- pinnedAgentName: string | null;
98
- /**
99
- * Name of the agent that currently owns a guided flow — set by the
100
- * orchestrator when a stateful agent's `onActivate` fires, cleared by
101
- * `releaseAgent` or session teardown. While non-null, routing precedence
102
- * goes to this agent and the picker is locked. Independent of
103
- * {@link pinnedAgentName}: a flow can wrap up and release without
104
- * disturbing the user's sticky picker choice.
105
- */
106
- flowOwnerAgentName: string | null;
107
- /**
108
- * The user prompt that started the current guided flow (GENC-1351 §5.9) —
109
- * captured when a flow-owner locks in, cleared when it releases. Persisted only
110
- * into a *non-resumable tombstone* (not the normal snapshot), where it pre-fills
111
- * the composer on return so the user can re-run the task. Slice-backed (like
112
- * {@link flowOwnerAgentName}) so both element instances agree and it survives
113
- * pop-in/out.
114
- */
115
- flowActivationPrompt: string | null;
116
- /**
117
- * Whether the agent picker slide-out panel is currently open. Lives in the
118
- * slice so the bubble-dialog and popout-panel instances stay in sync as the
119
- * user pops in and out.
120
- */
121
- agentPickerOpen: boolean;
122
- /**
123
- * Whether the lifecycle (session) menu slide-out panel is open. Mirrors
124
- * `agentPickerOpen` — kept in the slice so panel state survives pop-in/out.
125
- */
126
- sessionMenuOpen: boolean;
127
- /**
128
- * Whether a manual compaction's async summary is in flight (GENC-1351 §5.7).
129
- * In the slice (not element-local) so the "Compacting…" transcript view and
130
- * input-disable stay in sync across pop-in/out, like the panel flags above.
131
- */
132
- compacting: boolean;
133
- /**
134
- * Whether a page-reload restore is in flight — `provider.load()` hasn't
135
- * resolved yet (GENC-1351 §6). In the slice (like `compacting`) so the
136
- * "Restoring…" transcript view + input-disable survive pop-in/out. Invisible
137
- * for synchronous providers (resolves before paint); matters for server-backed
138
- * ones (S3 latency), where the disable also **prevents a mid-load send from
139
- * being clobbered** when the restored history loads.
140
- */
141
- restoring: boolean;
142
- /**
143
- * Whether the assistant is **blocked by a backend condition** and cannot send
144
- * at all — today, an exhausted AI-spend budget (GENC-1464). Unlike `compacting`
145
- * and `restoring`, which are short-lived in-flight flags that clear
146
- * themselves, this is a **latched** state: nothing the user or the element
147
- * does clears it, because nothing they can do fixes it. It stays set until the
148
- * host writes `blocked = false` (typically after a fresh pre-flight shows
149
- * budget available again).
150
- *
151
- * Also unlike those two, it does **not** replace the transcript — the user
152
- * keeps their conversation and gets a banner above a disabled composer.
153
- *
154
- * In the slice so the banner + input-disable stay in sync across pop-in/out.
155
- * Preserved across `resetSession` ("Clear" / "New chat"): starting a new chat
156
- * does not refill the budget.
157
- *
158
- * **Vendor-agnostic.** Under per-vendor budgets this means "blocked whatever
159
- * vendor you use" — a host-set block, or a wall that could not be attributed
160
- * to a vendor. A wall that WAS attributed lands in
161
- * {@link AiAssistantSessionState.blockedVendors} instead, and the element
162
- * derives its public `blocked` from the two together.
163
- */
164
- blocked: boolean;
165
- /**
166
- * Host- or driver-supplied explanation shown in the blocked banner. `null`
167
- * falls back to the element's default copy. Meaningless while
168
- * {@link AiAssistantSessionState.blocked} is false.
169
- */
170
- blockedReason: string | null;
171
- /**
172
- * Vendors walled by the AI-spend budget (GENC-1464 per-vendor), in the order they
173
- * were walled.
174
- *
175
- * An **array**, not a `Set` and not one `Record<AIProviderType, {...}>`:
176
- * a `Set` is neither plain JSON for the redux store nor structured-cloneable
177
- * across the tab channel; the order is what the multi-vendor banner copy
178
- * ("Anthropic and Gemini") reads; and a combined record would conflate "not
179
- * walled" with "walled, no reason".
180
- *
181
- * Empty for every host that never adopts the per-vendor API — which is what
182
- * makes the element's derived `blocked` byte-identical to the old stored one.
183
- */
184
- blockedVendors: AIProviderType[];
185
- /**
186
- * The subset of {@link AiAssistantSessionState.blockedVendors} walled by the
187
- * `otherVendorAvailable: false` SWEEP rather than by a refusal of their own.
188
- * Provenance, not a second wall list: for locking, `blockedVendors` remains
189
- * the whole truth. The distinction exists for the banner's unknown-reachability
190
- * fallback — a refusal is a fact about a vendor the user just used and is
191
- * nameable anywhere; a sweep entry is a headroom fact about the PROXY, and
192
- * naming it before the statuses reveal whether the host even ships that vendor
193
- * put a vendor in front of the user that may not be theirs.
194
- */
195
- sweptVendors: AIProviderType[];
196
- /**
197
- * Per-vendor banner copy, keyed by a member of
198
- * {@link AiAssistantSessionState.blockedVendors}. A missing entry falls back to
199
- * copy composed from the vendor's display name.
200
- */
201
- blockedVendorReasons: Partial<Record<AIProviderType, string>>;
202
- /**
203
- * Per-vendor AI-spend figures for the settings-modal budget meter (GENC-1464),
204
- * fed by the host via the element's `setVendorBudget`. A vendor with no entry
205
- * renders no meter row — which is also how "unlimited" is expressed: the host
206
- * simply never feeds a vendor the server marks unlimited.
207
- *
208
- * NOT persisted to the session snapshot, deliberately. The server is
209
- * authoritative for spend and the host re-feeds on boot (and after each turn),
210
- * so a persisted copy could only ever show stale dollars across a reload —
211
- * wrong money is worse than a beat of no meter. The snapshot assembler
212
- * (`session-snapshot.ts`) builds from an explicit field list, so absence is
213
- * structural, not an accident of serialization.
214
- */
215
- vendorBudgets: Partial<Record<AIProviderType, VendorBudgetFigures>>;
216
- /** Draft text in the input box — preserved across pop-in/pop-out cycles. */
217
- inputValue: string;
218
- /** Live trace from a currently-executing sub-agent. Cleared on sub-agent-stop. */
219
- liveSubAgentTrace: ChatMessage[];
220
- /** Name of the currently-executing sub-agent, or null when idle. */
221
- liveSubAgentName: string | null;
222
- /**
223
- * In-flight per-call chat-input overrides pushed by `requestSubAgent` or
224
- * `requestInteraction` calls. The most restrictive entry (`'hidden'` >
225
- * `'disabled'`) wins; an empty list means the agent-level
226
- * `chatInputDuringExecution` applies.
227
- */
228
- inputOverrides: InputOverride[];
229
- }
230
-
231
- /**
232
- * The subset of session state restored verbatim from a persisted session
233
- * (page-reload restore). Agent *identity* (`activeAgent`) and per-agent machine
234
- * snapshots are applied separately by the host — resolve the name against the
235
- * live agents array, then hand the snapshot to the agent's `init({ restored })`.
236
- *
237
- * @internal
238
- */
239
- export interface LoadSessionPayload {
240
- messages: ChatMessage[];
241
- pinnedAgentName: string | null;
242
- flowOwnerAgentName: string | null;
243
- contextTokens: number | undefined;
244
- contextLimit: number | undefined;
245
- activeModel: string | undefined;
246
- activeProviderName: string | undefined;
247
- }
248
-
249
- /**
250
- * Build a fresh default session state. A factory (not just a shared const) so
251
- * each call returns independent collections — `resetSession` can hand back a
252
- * clean slate without aliasing the initial state's arrays.
253
- */
254
- export function createDefaultSessionState(): AiAssistantSessionState {
255
- return {
256
- messages: [],
257
- state: 'idle',
258
- showToolCalls: false,
259
- showThinkingSteps: false,
260
- showNarration: false,
261
- showAgentSwitchIndicator: false,
262
- enabledAnimations: [],
263
- suggestionsState: { status: 'idle' },
264
- contextTokens: undefined,
265
- contextLimit: undefined,
266
- sessionUsage: emptyUsage(),
267
- activeModel: undefined,
268
- activeProviderName: undefined,
269
- providerStatuses: [],
270
- activeAgent: undefined,
271
- pinnedAgentName: null,
272
- flowOwnerAgentName: null,
273
- flowActivationPrompt: null,
274
- agentPickerOpen: false,
275
- sessionMenuOpen: false,
276
- compacting: false,
277
- restoring: false,
278
- blocked: false,
279
- blockedReason: null,
280
- blockedVendors: [],
281
- sweptVendors: [],
282
- blockedVendorReasons: {},
283
- vendorBudgets: {},
284
- inputValue: '',
285
- liveSubAgentTrace: [],
286
- liveSubAgentName: null,
287
- inputOverrides: [],
288
- };
289
- }
290
-
291
- export const defaultSessionState: AiAssistantSessionState = createDefaultSessionState();
292
-
293
- export const aiAssistantSlice = createSlice({
294
- name: 'aiAssistant',
295
- initialState: defaultSessionState,
296
- reducers: {
297
- setMessages(state, action: PayloadAction<ChatMessage[]>) {
298
- state.messages = action.payload;
299
- },
300
- setState(state, action: PayloadAction<AiAssistantState>) {
301
- state.state = action.payload;
302
- },
303
- setShowToolCalls(state, action: PayloadAction<boolean>) {
304
- state.showToolCalls = action.payload;
305
- },
306
- setShowThinkingSteps(state, action: PayloadAction<boolean>) {
307
- state.showThinkingSteps = action.payload;
308
- },
309
- setShowNarration(state, action: PayloadAction<boolean>) {
310
- state.showNarration = action.payload;
311
- },
312
- setShowAgentSwitchIndicator(state, action: PayloadAction<boolean>) {
313
- state.showAgentSwitchIndicator = action.payload;
314
- },
315
- setEnabledAnimations(state, action: PayloadAction<AiAssistantAnimation[]>) {
316
- state.enabledAnimations = action.payload;
317
- },
318
- setSuggestionsState(state, action: PayloadAction<SuggestionsState>) {
319
- state.suggestionsState = action.payload;
320
- },
321
- setContextTokens(state, action: PayloadAction<number | undefined>) {
322
- state.contextTokens = action.payload;
323
- },
324
- setContextLimit(state, action: PayloadAction<number | undefined>) {
325
- state.contextLimit = action.payload;
326
- },
327
- setSessionUsage(state, action: PayloadAction<AggregateUsage>) {
328
- state.sessionUsage = action.payload;
329
- },
330
- setActiveModel(state, action: PayloadAction<string | undefined>) {
331
- state.activeModel = action.payload;
332
- },
333
- setActiveProviderName(state, action: PayloadAction<string | undefined>) {
334
- state.activeProviderName = action.payload;
335
- },
336
- setProviderStatuses(state, action: PayloadAction<AIProviderRegistryStatusEntry[]>) {
337
- state.providerStatuses = action.payload;
338
- },
339
- setActiveAgent(state, action: PayloadAction<Omit<AgentConfig, 'toolHandlers'> | undefined>) {
340
- state.activeAgent = action.payload;
341
- },
342
- setPinnedAgentName(state, action: PayloadAction<string | null>) {
343
- state.pinnedAgentName = action.payload;
344
- },
345
- setFlowOwnerAgentName(state, action: PayloadAction<string | null>) {
346
- state.flowOwnerAgentName = action.payload;
347
- },
348
- setFlowActivationPrompt(state, action: PayloadAction<string | null>) {
349
- state.flowActivationPrompt = action.payload;
350
- },
351
- setAgentPickerOpen(state, action: PayloadAction<boolean>) {
352
- state.agentPickerOpen = action.payload;
353
- },
354
- setSessionMenuOpen(state, action: PayloadAction<boolean>) {
355
- state.sessionMenuOpen = action.payload;
356
- },
357
- setCompacting(state, action: PayloadAction<boolean>) {
358
- state.compacting = action.payload;
359
- },
360
- setRestoring(state, action: PayloadAction<boolean>) {
361
- state.restoring = action.payload;
362
- },
363
- /**
364
- * Latch (or release) the backend-blocked state.
365
- *
366
- * `reason` is only meaningful while blocking, and it distinguishes three
367
- * cases rather than two:
368
- *
369
- * - **omitted** — a re-latch that leaves the existing explanation intact.
370
- * Load-bearing: the block has two writers (the host, and the driver's
371
- * automatic latch), and the driver's write lands AFTER a bus-subscribed
372
- * host has already set its own detailed copy. Treating "no reason" as
373
- * "clear the reason" made that sequence replace the host's figures with
374
- * generic copy every time, deterministically.
375
- * - **a string** — replaces the explanation.
376
- * - **`null`** — explicitly clears the explanation while staying blocked;
377
- * the banner falls back to the element's default copy.
378
- *
379
- * Unblocking always clears it, so a stale explanation can never outlive the
380
- * condition it described.
381
- *
382
- * Unblocking **also clears every per-vendor latch**, and that is the single
383
- * rule that keeps per-vendor budgets non-breaking. A host asserting "the wall
384
- * is gone" (the documented pre-flight, migration-GENC-1464 Option B) must not be
385
- * silently overruled by a driver latch it cannot see; without this, the same
386
- * `setBlocked(false)` that used to unblock would leave the composer locked.
387
- */
388
- setBlocked(state, action: PayloadAction<{ blocked: boolean; reason?: string | null }>) {
389
- state.blocked = action.payload.blocked;
390
- if (!action.payload.blocked) {
391
- state.blockedReason = null;
392
- state.blockedVendors = [];
393
- state.sweptVendors = [];
394
- state.blockedVendorReasons = {};
395
- } else if (action.payload.reason !== undefined) {
396
- state.blockedReason = action.payload.reason;
397
- }
398
- },
399
- /**
400
- * Write the banner explanation without touching the flag.
401
- *
402
- * Separate from {@link setBlocked} rather than expressed as
403
- * `setBlocked({ blocked: current, reason })`, because the element's public
404
- * `blocked` is now DERIVED: re-latching with the derived value would pass
405
- * `false` for a partially-walled session and so clear every per-vendor latch
406
- * as a side effect of setting a string.
407
- */
408
- setBlockedReason(state, action: PayloadAction<string | null>) {
409
- state.blockedReason = action.payload;
410
- },
411
- /**
412
- * Wall (or release) a single vendor's budget.
413
- *
414
- * The `reason` trichotomy mirrors {@link setBlocked}'s exactly, and for the
415
- * same reason: the same two-writer race (the driver's latch landing after a
416
- * bus-subscribed host has written its own copy) applies per vendor.
417
- *
418
- * Adding an already-walled vendor is idempotent — the array is a set with an
419
- * order, so the vendor keeps the position it was first walled at.
420
- */
421
- setVendorBlocked(
422
- state,
423
- action: PayloadAction<{
424
- vendor: AIProviderType;
425
- blocked: boolean;
426
- reason?: string | null;
427
- /**
428
- * Marks the wall as SWEEP-derived (see `sweptVendors`). Only the
429
- * element's `otherVendorAvailable: false` sweep passes `true`; a host
430
- * call or a refusal latch leaves it unset, which also UPGRADES a
431
- * previously swept vendor — a refusal (or an explicit host statement)
432
- * is nameable evidence, and first-hand evidence beats the verdict.
433
- */
434
- swept?: boolean;
435
- }>,
436
- ) {
437
- const { vendor, blocked, reason, swept } = action.payload;
438
- const index = state.blockedVendors.indexOf(vendor);
439
- const sweptIndex = state.sweptVendors.indexOf(vendor);
440
- if (blocked) {
441
- if (index < 0) state.blockedVendors.push(vendor);
442
- if (swept) {
443
- // Provenance is FIRST-writer-wins: marking swept only records a wall
444
- // this call itself created. The sweep site already skips walled
445
- // vendors, so this guard is belt-and-braces against a future caller
446
- // downgrading a refused wall to "just the proxy's word".
447
- if (index < 0 && sweptIndex < 0) state.sweptVendors.push(vendor);
448
- } else if (sweptIndex >= 0) {
449
- state.sweptVendors.splice(sweptIndex, 1);
450
- }
451
- if (reason === null) {
452
- delete state.blockedVendorReasons[vendor];
453
- } else if (reason !== undefined) {
454
- state.blockedVendorReasons[vendor] = reason;
455
- }
456
- } else {
457
- if (index >= 0) state.blockedVendors.splice(index, 1);
458
- if (sweptIndex >= 0) state.sweptVendors.splice(sweptIndex, 1);
459
- delete state.blockedVendorReasons[vendor];
460
- }
461
- },
462
- /**
463
- * Store (or clear, with `null`) one vendor's budget figures for the meter.
464
- *
465
- * Validation lives at the element boundary (`setVendorBudget` on the
466
- * element), not here — the reducer is the storage layer and trusts its one
467
- * caller, exactly as `setVendorBlocked` above trusts its.
468
- *
469
- * Idempotent by VALUE, not just by effect: re-feeding the figures a vendor
470
- * already holds leaves the stored object untouched. The documented feed
471
- * re-runs after every turn, so without this every turn would publish a new
472
- * store reference for the same dollars and re-render a meter that has not
473
- * changed.
474
- */
475
- setVendorBudget(
476
- state,
477
- action: PayloadAction<{ vendor: AIProviderType; figures: VendorBudgetFigures | null }>,
478
- ) {
479
- const { vendor, figures } = action.payload;
480
- if (figures === null) {
481
- delete state.vendorBudgets[vendor];
482
- return;
483
- }
484
- const existing = state.vendorBudgets[vendor];
485
- if (
486
- existing &&
487
- existing.budgetUsd === figures.budgetUsd &&
488
- existing.spentUsd === figures.spentUsd
489
- ) {
490
- return;
491
- }
492
- // A fresh object rather than the caller's: the store may freeze what it
493
- // holds, and adopting the host's object would freeze state the host still
494
- // owns (and let later host mutations bypass the store).
495
- state.vendorBudgets[vendor] = { budgetUsd: figures.budgetUsd, spentUsd: figures.spentUsd };
496
- },
497
- setInputValue(state, action: PayloadAction<string>) {
498
- state.inputValue = action.payload;
499
- },
500
- setLiveSubAgentTrace(state, action: PayloadAction<ChatMessage[]>) {
501
- state.liveSubAgentTrace = action.payload;
502
- },
503
- setLiveSubAgentName(state, action: PayloadAction<string | null>) {
504
- state.liveSubAgentName = action.payload;
505
- },
506
- addInputOverride(state, action: PayloadAction<InputOverride>) {
507
- state.inputOverrides.push(action.payload);
508
- },
509
- removeInputOverride(state, action: PayloadAction<{ id: string }>) {
510
- state.inputOverrides = state.inputOverrides.filter((o) => o.id !== action.payload.id);
511
- },
512
- /**
513
- * Bulk-hydrate the restorable fields of a persisted session (page-reload
514
- * restore) and set `state` back to `idle`. Agent identity and the per-agent
515
- * machine snapshot are applied separately by the host — see
516
- * `LoadSessionPayload`.
517
- */
518
- loadSession(state, action: PayloadAction<LoadSessionPayload>) {
519
- const p = action.payload;
520
- state.messages = p.messages;
521
- state.pinnedAgentName = p.pinnedAgentName;
522
- state.flowOwnerAgentName = p.flowOwnerAgentName;
523
- // Derive the totals from the restored transcript rather than trusting a stored
524
- // counter. Restore dispatches this action directly, bypassing the host's
525
- // `messages` setter — the thing that normally recomputes — so a stored total
526
- // would sit stale (historically at 0) until the next turn arrived. The
527
- // transcript is the source of truth: per-message usage survives the snapshot,
528
- // and a compacted summary carries the usage of the turns it replaced.
529
- state.sessionUsage = sumUsage(p.messages);
530
- state.contextTokens = p.contextTokens;
531
- state.contextLimit = p.contextLimit;
532
- state.activeModel = p.activeModel;
533
- state.activeProviderName = p.activeProviderName;
534
- state.state = 'idle';
535
- },
536
- /**
537
- * Wipe the session back to a fresh default — the "Clear" / "New chat" action.
538
- *
539
- * Three categories survive, for three different reasons — keep them distinct:
540
- *
541
- * 1. The user's **display preferences** (tool-call / thinking / narration /
542
- * agent-switch visibility + animations). They are settings, not
543
- * conversation state, and are persisted independently of the snapshot
544
- * (GENC-1351). Without this they'd flip to their all-off defaults in the
545
- * live store on Clear, since `clearSession` re-applies no baseline and the
546
- * idempotent setters suppress a re-apply.
547
- * 2. The **backend block** (`blocked` / `blockedReason`, and the per-vendor
548
- * latches beside them). Not a preference at
549
- * all — a standing backend condition that Clear cannot change. It is here
550
- * because starting a new chat does not refill the AI budget, so clearing
551
- * it would hand the user a working-looking composer that fails on the very
552
- * next send.
553
- * 3. The **provider statuses**. They describe the REGISTRY, not the
554
- * conversation — provider registration is static for the store's lifetime
555
- * and this snapshot is refreshed from the registry, never from a turn. They
556
- * are session state only because that is where the element's store lives.
557
- *
558
- * Dropping them on Clear is not cosmetic: the element derives `blocked`
559
- * from "every REACHABLE vendor is walled", and an empty reachable set fails
560
- * safe to "blocked". So wiping the statuses while keeping the per-vendor
561
- * latches (category 2) escalated one walled vendor out of two into a fully
562
- * locked composer, and nothing reloads them — `loadProviderStatuses()` runs
563
- * only on connect/session-switch and from the observable-registry
564
- * subscription, so a host with a static registry stayed locked until a
565
- * remount. Preserving is the fix rather than re-loading after reset,
566
- * because the reload is async: it would still flash a fully locked composer
567
- * for a turn of the event loop, and would break a host whose registry is
568
- * immutable (nothing to re-listen to) or whose `listStatuses()` rejects.
569
- *
570
- * Stated separately so a future contributor deciding what belongs in this
571
- * list does not "tidy" the block out on the grounds that it is not a display
572
- * preference.
573
- */
574
- resetSession(state) {
575
- // `satisfies` gives this literal excess-property checking and per-field type
576
- // checking, neither of which `Object.assign` provides — its source parameter
577
- // is structurally permissive, so a typo (`blockedResaon`) would compile
578
- // clean, silently stop preserving the real key, AND add a junk key to the
579
- // draft that then leaks into serialised state.
580
- const preservedAcrossReset = {
581
- showToolCalls: state.showToolCalls,
582
- showThinkingSteps: state.showThinkingSteps,
583
- showNarration: state.showNarration,
584
- showAgentSwitchIndicator: state.showAgentSwitchIndicator,
585
- enabledAnimations: state.enabledAnimations,
586
- providerStatuses: state.providerStatuses,
587
- blocked: state.blocked,
588
- blockedReason: state.blockedReason,
589
- blockedVendors: state.blockedVendors,
590
- sweptVendors: state.sweptVendors,
591
- blockedVendorReasons: state.blockedVendorReasons,
592
- // Category 2, beside the blocked latch: the budget figures are a fact
593
- // about the USER'S DEPLOYMENT (what the server meters), not about this
594
- // conversation — "Clear" refills no budget, so wiping them would blank
595
- // the meter until the host's next re-feed while the dollars are
596
- // unchanged. Opposite side from `contextTokens`, which IS conversation
597
- // state (a new chat genuinely has an empty context window).
598
- vendorBudgets: state.vendorBudgets,
599
- } satisfies Partial<AiAssistantSessionState>;
600
- Object.assign(state, createDefaultSessionState(), preservedAcrossReset);
601
- },
602
- },
603
- selectors: {},
604
- });