@stigmer/react 0.3.3 → 0.4.0

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 (442) hide show
  1. package/billing/AutoRechargeCard.d.ts +38 -0
  2. package/billing/AutoRechargeCard.d.ts.map +1 -0
  3. package/billing/AutoRechargeCard.js +90 -0
  4. package/billing/AutoRechargeCard.js.map +1 -0
  5. package/billing/BillingSection.d.ts +32 -0
  6. package/billing/BillingSection.d.ts.map +1 -0
  7. package/billing/BillingSection.js +81 -0
  8. package/billing/BillingSection.js.map +1 -0
  9. package/billing/CreditBalanceCard.d.ts +25 -0
  10. package/billing/CreditBalanceCard.d.ts.map +1 -0
  11. package/billing/CreditBalanceCard.js +28 -0
  12. package/billing/CreditBalanceCard.js.map +1 -0
  13. package/billing/CreditLedgerTable.d.ts +22 -0
  14. package/billing/CreditLedgerTable.d.ts.map +1 -0
  15. package/billing/CreditLedgerTable.js +75 -0
  16. package/billing/CreditLedgerTable.js.map +1 -0
  17. package/billing/CreditPackGrid.d.ts +31 -0
  18. package/billing/CreditPackGrid.d.ts.map +1 -0
  19. package/billing/CreditPackGrid.js +35 -0
  20. package/billing/CreditPackGrid.js.map +1 -0
  21. package/billing/LowBalanceBanner.d.ts +26 -0
  22. package/billing/LowBalanceBanner.d.ts.map +1 -0
  23. package/billing/LowBalanceBanner.js +33 -0
  24. package/billing/LowBalanceBanner.js.map +1 -0
  25. package/billing/PaymentMethodCard.d.ts +35 -0
  26. package/billing/PaymentMethodCard.d.ts.map +1 -0
  27. package/billing/PaymentMethodCard.js +48 -0
  28. package/billing/PaymentMethodCard.js.map +1 -0
  29. package/billing/credit-packs.d.ts +25 -0
  30. package/billing/credit-packs.d.ts.map +1 -0
  31. package/billing/credit-packs.js +39 -0
  32. package/billing/credit-packs.js.map +1 -0
  33. package/billing/format.d.ts +39 -0
  34. package/billing/format.d.ts.map +1 -0
  35. package/billing/format.js +90 -0
  36. package/billing/format.js.map +1 -0
  37. package/billing/index.d.ts +32 -0
  38. package/billing/index.d.ts.map +1 -0
  39. package/billing/index.js +21 -0
  40. package/billing/index.js.map +1 -0
  41. package/billing/useBillingAccount.d.ts +40 -0
  42. package/billing/useBillingAccount.d.ts.map +1 -0
  43. package/billing/useBillingAccount.js +35 -0
  44. package/billing/useBillingAccount.js.map +1 -0
  45. package/billing/useBillingUsageReport.d.ts +42 -0
  46. package/billing/useBillingUsageReport.d.ts.map +1 -0
  47. package/billing/useBillingUsageReport.js +43 -0
  48. package/billing/useBillingUsageReport.js.map +1 -0
  49. package/billing/useCreateBillingPortalSession.d.ts +35 -0
  50. package/billing/useCreateBillingPortalSession.d.ts.map +1 -0
  51. package/billing/useCreateBillingPortalSession.js +50 -0
  52. package/billing/useCreateBillingPortalSession.js.map +1 -0
  53. package/billing/useCreateCheckoutSession.d.ts +54 -0
  54. package/billing/useCreateCheckoutSession.d.ts.map +1 -0
  55. package/billing/useCreateCheckoutSession.js +58 -0
  56. package/billing/useCreateCheckoutSession.js.map +1 -0
  57. package/billing/useCreditLedger.d.ts +48 -0
  58. package/billing/useCreditLedger.d.ts.map +1 -0
  59. package/billing/useCreditLedger.js +39 -0
  60. package/billing/useCreditLedger.js.map +1 -0
  61. package/billing/useCustomerModelPricing.d.ts +41 -0
  62. package/billing/useCustomerModelPricing.d.ts.map +1 -0
  63. package/billing/useCustomerModelPricing.js +37 -0
  64. package/billing/useCustomerModelPricing.js.map +1 -0
  65. package/billing/useSetAutoRechargeConfig.d.ts +50 -0
  66. package/billing/useSetAutoRechargeConfig.d.ts.map +1 -0
  67. package/billing/useSetAutoRechargeConfig.js +53 -0
  68. package/billing/useSetAutoRechargeConfig.js.map +1 -0
  69. package/composer/ComposerToolbar.js +1 -1
  70. package/composer/ComposerToolbar.js.map +1 -1
  71. package/composer/SessionComposer.d.ts +1 -1
  72. package/composer/SessionComposer.d.ts.map +1 -1
  73. package/composer/SessionComposer.js +19 -4
  74. package/composer/SessionComposer.js.map +1 -1
  75. package/composer/__tests__/SessionComposer-memo.test.d.ts +2 -0
  76. package/composer/__tests__/SessionComposer-memo.test.d.ts.map +1 -0
  77. package/composer/__tests__/SessionComposer-memo.test.js +23 -0
  78. package/composer/__tests__/SessionComposer-memo.test.js.map +1 -0
  79. package/execution/ApprovalCard.d.ts +5 -1
  80. package/execution/ApprovalCard.d.ts.map +1 -1
  81. package/execution/ApprovalCard.js +7 -3
  82. package/execution/ApprovalCard.js.map +1 -1
  83. package/execution/ExecutionPhaseBadge.d.ts +1 -1
  84. package/execution/ExecutionPhaseBadge.d.ts.map +1 -1
  85. package/execution/ExecutionPhaseBadge.js +3 -2
  86. package/execution/ExecutionPhaseBadge.js.map +1 -1
  87. package/execution/MessageEntry.d.ts +7 -3
  88. package/execution/MessageEntry.d.ts.map +1 -1
  89. package/execution/MessageEntry.js +19 -8
  90. package/execution/MessageEntry.js.map +1 -1
  91. package/execution/MessageThread.d.ts +84 -3
  92. package/execution/MessageThread.d.ts.map +1 -1
  93. package/execution/MessageThread.js +113 -65
  94. package/execution/MessageThread.js.map +1 -1
  95. package/execution/SetupProgress.d.ts +1 -1
  96. package/execution/SetupProgress.d.ts.map +1 -1
  97. package/execution/SetupProgress.js +3 -3
  98. package/execution/SetupProgress.js.map +1 -1
  99. package/execution/SubAgentSection.d.ts +5 -1
  100. package/execution/SubAgentSection.d.ts.map +1 -1
  101. package/execution/SubAgentSection.js +13 -7
  102. package/execution/SubAgentSection.js.map +1 -1
  103. package/execution/ThreadSkeleton.d.ts +22 -0
  104. package/execution/ThreadSkeleton.d.ts.map +1 -0
  105. package/execution/ThreadSkeleton.js +26 -0
  106. package/execution/ThreadSkeleton.js.map +1 -0
  107. package/execution/ToolCallGroup.d.ts +16 -1
  108. package/execution/ToolCallGroup.d.ts.map +1 -1
  109. package/execution/ToolCallGroup.js +31 -3
  110. package/execution/ToolCallGroup.js.map +1 -1
  111. package/execution/UsageWidget.d.ts +1 -1
  112. package/execution/__tests__/message-entry.test.d.ts +2 -0
  113. package/execution/__tests__/message-entry.test.d.ts.map +1 -0
  114. package/execution/__tests__/message-entry.test.js +178 -0
  115. package/execution/__tests__/message-entry.test.js.map +1 -0
  116. package/execution/__tests__/thread-keys.test.d.ts +2 -0
  117. package/execution/__tests__/thread-keys.test.d.ts.map +1 -0
  118. package/execution/__tests__/thread-keys.test.js +289 -0
  119. package/execution/__tests__/thread-keys.test.js.map +1 -0
  120. package/execution/__tests__/thread-memoization.test.d.ts +2 -0
  121. package/execution/__tests__/thread-memoization.test.d.ts.map +1 -0
  122. package/execution/__tests__/thread-memoization.test.js +262 -0
  123. package/execution/__tests__/thread-memoization.test.js.map +1 -0
  124. package/execution/__tests__/thread-skeleton.test.d.ts +2 -0
  125. package/execution/__tests__/thread-skeleton.test.d.ts.map +1 -0
  126. package/execution/__tests__/thread-skeleton.test.js +35 -0
  127. package/execution/__tests__/thread-skeleton.test.js.map +1 -0
  128. package/execution/__tests__/useExecutionStream.test.js +73 -10
  129. package/execution/__tests__/useExecutionStream.test.js.map +1 -1
  130. package/execution/__tests__/useSessionVariables-stability.test.d.ts +2 -0
  131. package/execution/__tests__/useSessionVariables-stability.test.d.ts.map +1 -0
  132. package/execution/__tests__/useSessionVariables-stability.test.js +69 -0
  133. package/execution/__tests__/useSessionVariables-stability.test.js.map +1 -0
  134. package/execution/__tests__/virtualized-thread.test.d.ts +2 -0
  135. package/execution/__tests__/virtualized-thread.test.d.ts.map +1 -0
  136. package/execution/__tests__/virtualized-thread.test.js +274 -0
  137. package/execution/__tests__/virtualized-thread.test.js.map +1 -0
  138. package/execution/index.d.ts +2 -0
  139. package/execution/index.d.ts.map +1 -1
  140. package/execution/index.js +1 -0
  141. package/execution/index.js.map +1 -1
  142. package/execution/useExecutionStream.d.ts +35 -10
  143. package/execution/useExecutionStream.d.ts.map +1 -1
  144. package/execution/useExecutionStream.js +79 -40
  145. package/execution/useExecutionStream.js.map +1 -1
  146. package/execution/useSessionVariables.d.ts.map +1 -1
  147. package/execution/useSessionVariables.js +4 -3
  148. package/execution/useSessionVariables.js.map +1 -1
  149. package/github/useGitHubConnection.d.ts.map +1 -1
  150. package/github/useGitHubConnection.js +5 -4
  151. package/github/useGitHubConnection.js.map +1 -1
  152. package/identity-account/index.d.ts +2 -0
  153. package/identity-account/index.d.ts.map +1 -0
  154. package/identity-account/index.js +2 -0
  155. package/identity-account/index.js.map +1 -0
  156. package/identity-account/useIdentityAccountGate.d.ts +81 -0
  157. package/identity-account/useIdentityAccountGate.d.ts.map +1 -0
  158. package/identity-account/useIdentityAccountGate.js +100 -0
  159. package/identity-account/useIdentityAccountGate.js.map +1 -0
  160. package/index.d.ts +10 -4
  161. package/index.d.ts.map +1 -1
  162. package/index.js +8 -2
  163. package/index.js.map +1 -1
  164. package/internal/FetchCacheProvider.d.ts +44 -0
  165. package/internal/FetchCacheProvider.d.ts.map +1 -0
  166. package/internal/FetchCacheProvider.js +61 -0
  167. package/internal/FetchCacheProvider.js.map +1 -0
  168. package/internal/JumpToLatestButton.d.ts +14 -0
  169. package/internal/JumpToLatestButton.d.ts.map +1 -0
  170. package/internal/JumpToLatestButton.js +19 -0
  171. package/internal/JumpToLatestButton.js.map +1 -0
  172. package/internal/ThreadItemWrapper.d.ts +20 -0
  173. package/internal/ThreadItemWrapper.d.ts.map +1 -0
  174. package/internal/ThreadItemWrapper.js +44 -0
  175. package/internal/ThreadItemWrapper.js.map +1 -0
  176. package/internal/VirtualizedThread.d.ts +25 -0
  177. package/internal/VirtualizedThread.d.ts.map +1 -0
  178. package/internal/VirtualizedThread.js +58 -0
  179. package/internal/VirtualizedThread.js.map +1 -0
  180. package/internal/__tests__/fetch-cache.test.d.ts +2 -0
  181. package/internal/__tests__/fetch-cache.test.d.ts.map +1 -0
  182. package/internal/__tests__/fetch-cache.test.js +182 -0
  183. package/internal/__tests__/fetch-cache.test.js.map +1 -0
  184. package/internal/__tests__/stream-controller.test.d.ts +2 -0
  185. package/internal/__tests__/stream-controller.test.d.ts.map +1 -0
  186. package/internal/__tests__/stream-controller.test.js +294 -0
  187. package/internal/__tests__/stream-controller.test.js.map +1 -0
  188. package/internal/__tests__/thread-animation.test.d.ts +2 -0
  189. package/internal/__tests__/thread-animation.test.d.ts.map +1 -0
  190. package/internal/__tests__/thread-animation.test.js +79 -0
  191. package/internal/__tests__/thread-animation.test.js.map +1 -0
  192. package/internal/__tests__/useAutoScroll.test.d.ts +2 -0
  193. package/internal/__tests__/useAutoScroll.test.d.ts.map +1 -0
  194. package/internal/__tests__/useAutoScroll.test.js +188 -0
  195. package/internal/__tests__/useAutoScroll.test.js.map +1 -0
  196. package/internal/__tests__/useFetch-cache.test.d.ts +2 -0
  197. package/internal/__tests__/useFetch-cache.test.d.ts.map +1 -0
  198. package/internal/__tests__/useFetch-cache.test.js +137 -0
  199. package/internal/__tests__/useFetch-cache.test.js.map +1 -0
  200. package/internal/dev/__tests__/use-key-stability.test.d.ts +2 -0
  201. package/internal/dev/__tests__/use-key-stability.test.d.ts.map +1 -0
  202. package/internal/dev/__tests__/use-key-stability.test.js +72 -0
  203. package/internal/dev/__tests__/use-key-stability.test.js.map +1 -0
  204. package/internal/dev/__tests__/use-render-tracer.test.d.ts +2 -0
  205. package/internal/dev/__tests__/use-render-tracer.test.d.ts.map +1 -0
  206. package/internal/dev/__tests__/use-render-tracer.test.js +55 -0
  207. package/internal/dev/__tests__/use-render-tracer.test.js.map +1 -0
  208. package/internal/dev/dom-counter.d.ts +14 -0
  209. package/internal/dev/dom-counter.d.ts.map +1 -0
  210. package/internal/dev/dom-counter.js +39 -0
  211. package/internal/dev/dom-counter.js.map +1 -0
  212. package/internal/dev/index.d.ts +6 -0
  213. package/internal/dev/index.d.ts.map +1 -0
  214. package/internal/dev/index.js +6 -0
  215. package/internal/dev/index.js.map +1 -0
  216. package/internal/dev/profiler-wrapper.d.ts +16 -0
  217. package/internal/dev/profiler-wrapper.d.ts.map +1 -0
  218. package/internal/dev/profiler-wrapper.js +31 -0
  219. package/internal/dev/profiler-wrapper.js.map +1 -0
  220. package/internal/dev/use-key-stability.d.ts +22 -0
  221. package/internal/dev/use-key-stability.d.ts.map +1 -0
  222. package/internal/dev/use-key-stability.js +67 -0
  223. package/internal/dev/use-key-stability.js.map +1 -0
  224. package/internal/dev/use-render-tracer.d.ts +13 -0
  225. package/internal/dev/use-render-tracer.d.ts.map +1 -0
  226. package/internal/dev/use-render-tracer.js +57 -0
  227. package/internal/dev/use-render-tracer.js.map +1 -0
  228. package/internal/dev/use-stream-rate.d.ts +23 -0
  229. package/internal/dev/use-stream-rate.d.ts.map +1 -0
  230. package/internal/dev/use-stream-rate.js +94 -0
  231. package/internal/dev/use-stream-rate.js.map +1 -0
  232. package/internal/fetch-cache.d.ts +72 -0
  233. package/internal/fetch-cache.d.ts.map +1 -0
  234. package/internal/fetch-cache.js +118 -0
  235. package/internal/fetch-cache.js.map +1 -0
  236. package/internal/store/__tests__/conversation-store.test.d.ts +2 -0
  237. package/internal/store/__tests__/conversation-store.test.d.ts.map +1 -0
  238. package/internal/store/__tests__/conversation-store.test.js +200 -0
  239. package/internal/store/__tests__/conversation-store.test.js.map +1 -0
  240. package/internal/store/__tests__/structural-share.test.d.ts +2 -0
  241. package/internal/store/__tests__/structural-share.test.d.ts.map +1 -0
  242. package/internal/store/__tests__/structural-share.test.js +368 -0
  243. package/internal/store/__tests__/structural-share.test.js.map +1 -0
  244. package/internal/store/conversation-store.d.ts +62 -0
  245. package/internal/store/conversation-store.d.ts.map +1 -0
  246. package/internal/store/conversation-store.js +95 -0
  247. package/internal/store/conversation-store.js.map +1 -0
  248. package/internal/store/index.d.ts +31 -0
  249. package/internal/store/index.d.ts.map +1 -0
  250. package/internal/store/index.js +54 -0
  251. package/internal/store/index.js.map +1 -0
  252. package/internal/store/structural-share.d.ts +13 -0
  253. package/internal/store/structural-share.d.ts.map +1 -0
  254. package/internal/store/structural-share.js +240 -0
  255. package/internal/store/structural-share.js.map +1 -0
  256. package/internal/stream-controller.d.ts +85 -0
  257. package/internal/stream-controller.d.ts.map +1 -0
  258. package/internal/stream-controller.js +146 -0
  259. package/internal/stream-controller.js.map +1 -0
  260. package/internal/useAutoScroll.d.ts +32 -0
  261. package/internal/useAutoScroll.d.ts.map +1 -0
  262. package/internal/useAutoScroll.js +97 -0
  263. package/internal/useAutoScroll.js.map +1 -0
  264. package/internal/useFetch.d.ts +14 -0
  265. package/internal/useFetch.d.ts.map +1 -1
  266. package/internal/useFetch.js +32 -2
  267. package/internal/useFetch.js.map +1 -1
  268. package/package.json +7 -5
  269. package/session/__tests__/useNewSessionFlow.test.js +16 -0
  270. package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
  271. package/session/__tests__/usePersistedModel.test.d.ts +2 -0
  272. package/session/__tests__/usePersistedModel.test.d.ts.map +1 -0
  273. package/session/__tests__/usePersistedModel.test.js +82 -0
  274. package/session/__tests__/usePersistedModel.test.js.map +1 -0
  275. package/session/__tests__/useSession.test.d.ts +2 -0
  276. package/session/__tests__/useSession.test.d.ts.map +1 -0
  277. package/session/__tests__/useSession.test.js +130 -0
  278. package/session/__tests__/useSession.test.js.map +1 -0
  279. package/session/useNewSessionFlow.d.ts.map +1 -1
  280. package/session/useNewSessionFlow.js +12 -6
  281. package/session/useNewSessionFlow.js.map +1 -1
  282. package/session/usePersistedModel.d.ts +3 -0
  283. package/session/usePersistedModel.d.ts.map +1 -1
  284. package/session/usePersistedModel.js +27 -2
  285. package/session/usePersistedModel.js.map +1 -1
  286. package/session/useSession.d.ts.map +1 -1
  287. package/session/useSession.js +1 -1
  288. package/session/useSession.js.map +1 -1
  289. package/session/useSessionConversation.d.ts.map +1 -1
  290. package/session/useSessionConversation.js +9 -1
  291. package/session/useSessionConversation.js.map +1 -1
  292. package/session/useSessionExecutions.d.ts.map +1 -1
  293. package/session/useSessionExecutions.js +1 -1
  294. package/session/useSessionExecutions.js.map +1 -1
  295. package/session/useSessionPageFlow.js +1 -1
  296. package/session/useSessionPageFlow.js.map +1 -1
  297. package/session/useSessionUsage.d.ts +24 -40
  298. package/session/useSessionUsage.d.ts.map +1 -1
  299. package/session/useSessionUsage.js +64 -97
  300. package/session/useSessionUsage.js.map +1 -1
  301. package/settings/BillingSection.d.ts +3 -0
  302. package/settings/BillingSection.d.ts.map +1 -0
  303. package/settings/BillingSection.js +3 -0
  304. package/settings/BillingSection.js.map +1 -0
  305. package/settings/index.d.ts +2 -0
  306. package/settings/index.d.ts.map +1 -1
  307. package/settings/index.js +1 -0
  308. package/settings/index.js.map +1 -1
  309. package/settings/settings-nav.js +1 -1
  310. package/settings/settings-nav.js.map +1 -1
  311. package/src/billing/AutoRechargeCard.tsx +274 -0
  312. package/src/billing/BillingSection.tsx +255 -0
  313. package/src/billing/CreditBalanceCard.tsx +81 -0
  314. package/src/billing/CreditLedgerTable.tsx +281 -0
  315. package/src/billing/CreditPackGrid.tsx +132 -0
  316. package/src/billing/LowBalanceBanner.tsx +67 -0
  317. package/src/billing/PaymentMethodCard.tsx +133 -0
  318. package/src/billing/credit-packs.ts +54 -0
  319. package/src/billing/format.ts +97 -0
  320. package/src/billing/index.ts +51 -0
  321. package/src/billing/useBillingAccount.ts +64 -0
  322. package/src/billing/useBillingUsageReport.ts +73 -0
  323. package/src/billing/useCreateBillingPortalSession.ts +76 -0
  324. package/src/billing/useCreateCheckoutSession.ts +101 -0
  325. package/src/billing/useCreditLedger.ts +79 -0
  326. package/src/billing/useCustomerModelPricing.ts +67 -0
  327. package/src/billing/useSetAutoRechargeConfig.ts +90 -0
  328. package/src/composer/ComposerToolbar.tsx +1 -1
  329. package/src/composer/SessionComposer.tsx +22 -4
  330. package/src/composer/__tests__/SessionComposer-memo.test.ts +26 -0
  331. package/src/execution/ApprovalCard.tsx +7 -3
  332. package/src/execution/ExecutionPhaseBadge.tsx +3 -2
  333. package/src/execution/MessageEntry.tsx +27 -16
  334. package/src/execution/MessageThread.tsx +308 -131
  335. package/src/execution/SetupProgress.tsx +3 -3
  336. package/src/execution/SubAgentSection.tsx +14 -6
  337. package/src/execution/ThreadSkeleton.tsx +73 -0
  338. package/src/execution/ToolCallGroup.tsx +36 -3
  339. package/src/execution/UsageWidget.tsx +1 -1
  340. package/src/execution/__tests__/message-entry.test.tsx +236 -0
  341. package/src/execution/__tests__/thread-keys.test.ts +409 -0
  342. package/src/execution/__tests__/thread-memoization.test.ts +320 -0
  343. package/src/execution/__tests__/thread-skeleton.test.tsx +44 -0
  344. package/src/execution/__tests__/useExecutionStream.test.tsx +109 -12
  345. package/src/execution/__tests__/useSessionVariables-stability.test.ts +95 -0
  346. package/src/execution/__tests__/virtualized-thread.test.tsx +401 -0
  347. package/src/execution/index.ts +3 -0
  348. package/src/execution/useExecutionStream.ts +123 -48
  349. package/src/execution/useSessionVariables.ts +17 -12
  350. package/src/github/useGitHubConnection.ts +18 -13
  351. package/src/identity-account/index.ts +5 -0
  352. package/src/identity-account/useIdentityAccountGate.ts +163 -0
  353. package/src/index.ts +73 -0
  354. package/src/internal/FetchCacheProvider.tsx +74 -0
  355. package/src/internal/JumpToLatestButton.tsx +61 -0
  356. package/src/internal/ThreadItemWrapper.tsx +65 -0
  357. package/src/internal/VirtualizedThread.tsx +162 -0
  358. package/src/internal/__tests__/fetch-cache.test.ts +230 -0
  359. package/src/internal/__tests__/stream-controller.test.ts +395 -0
  360. package/src/internal/__tests__/thread-animation.test.tsx +121 -0
  361. package/src/internal/__tests__/useAutoScroll.test.tsx +261 -0
  362. package/src/internal/__tests__/useFetch-cache.test.ts +214 -0
  363. package/src/internal/dev/__tests__/use-key-stability.test.ts +124 -0
  364. package/src/internal/dev/__tests__/use-render-tracer.test.ts +78 -0
  365. package/src/internal/dev/dom-counter.ts +47 -0
  366. package/src/internal/dev/index.ts +5 -0
  367. package/src/internal/dev/profiler-wrapper.tsx +52 -0
  368. package/src/internal/dev/use-key-stability.ts +86 -0
  369. package/src/internal/dev/use-render-tracer.ts +70 -0
  370. package/src/internal/dev/use-stream-rate.ts +138 -0
  371. package/src/internal/fetch-cache.ts +155 -0
  372. package/src/internal/store/__tests__/conversation-store.test.ts +257 -0
  373. package/src/internal/store/__tests__/structural-share.test.ts +454 -0
  374. package/src/internal/store/conversation-store.ts +128 -0
  375. package/src/internal/store/index.ts +68 -0
  376. package/src/internal/store/structural-share.ts +318 -0
  377. package/src/internal/stream-controller.ts +201 -0
  378. package/src/internal/useAutoScroll.ts +121 -0
  379. package/src/internal/useFetch.ts +51 -2
  380. package/src/session/__tests__/useNewSessionFlow.test.tsx +22 -0
  381. package/src/session/__tests__/usePersistedModel.test.tsx +117 -0
  382. package/src/session/__tests__/useSession.test.tsx +187 -0
  383. package/src/session/useNewSessionFlow.ts +12 -6
  384. package/src/session/usePersistedModel.ts +28 -2
  385. package/src/session/useSession.ts +1 -0
  386. package/src/session/useSessionConversation.ts +11 -2
  387. package/src/session/useSessionExecutions.ts +1 -0
  388. package/src/session/useSessionPageFlow.ts +1 -1
  389. package/src/session/useSessionUsage.ts +102 -123
  390. package/src/settings/BillingSection.tsx +4 -0
  391. package/src/settings/index.ts +2 -0
  392. package/src/settings/settings-nav.ts +1 -1
  393. package/src/styles.css +31 -0
  394. package/src/usage/AgentBreakdownList.tsx +147 -0
  395. package/src/usage/CreditRunwayIndicator.tsx +71 -0
  396. package/src/usage/ExportButton.tsx +115 -0
  397. package/src/usage/HarnessSplitCard.tsx +103 -0
  398. package/src/usage/OrgUsagePanel.tsx +109 -45
  399. package/src/usage/index.ts +15 -0
  400. package/src/usage/useExportCSV.ts +115 -0
  401. package/src/usage/useOrgUsageReport.ts +2 -1
  402. package/src/workspace/__tests__/useWorkspaceEntries-stability.test.ts +76 -0
  403. package/src/workspace/useWorkspaceEntries.ts +16 -11
  404. package/styles.css +1 -1
  405. package/usage/AgentBreakdownList.d.ts +21 -0
  406. package/usage/AgentBreakdownList.d.ts.map +1 -0
  407. package/usage/AgentBreakdownList.js +44 -0
  408. package/usage/AgentBreakdownList.js.map +1 -0
  409. package/usage/CreditRunwayIndicator.d.ts +21 -0
  410. package/usage/CreditRunwayIndicator.d.ts.map +1 -0
  411. package/usage/CreditRunwayIndicator.js +38 -0
  412. package/usage/CreditRunwayIndicator.js.map +1 -0
  413. package/usage/ExportButton.d.ts +20 -0
  414. package/usage/ExportButton.d.ts.map +1 -0
  415. package/usage/ExportButton.js +36 -0
  416. package/usage/ExportButton.js.map +1 -0
  417. package/usage/HarnessSplitCard.d.ts +17 -0
  418. package/usage/HarnessSplitCard.d.ts.map +1 -0
  419. package/usage/HarnessSplitCard.js +38 -0
  420. package/usage/HarnessSplitCard.js.map +1 -0
  421. package/usage/OrgUsagePanel.d.ts.map +1 -1
  422. package/usage/OrgUsagePanel.js +30 -22
  423. package/usage/OrgUsagePanel.js.map +1 -1
  424. package/usage/index.d.ts +10 -0
  425. package/usage/index.d.ts.map +1 -1
  426. package/usage/index.js +5 -0
  427. package/usage/index.js.map +1 -1
  428. package/usage/useExportCSV.d.ts +23 -0
  429. package/usage/useExportCSV.d.ts.map +1 -0
  430. package/usage/useExportCSV.js +81 -0
  431. package/usage/useExportCSV.js.map +1 -0
  432. package/usage/useOrgUsageReport.d.ts +2 -1
  433. package/usage/useOrgUsageReport.d.ts.map +1 -1
  434. package/usage/useOrgUsageReport.js +2 -1
  435. package/usage/useOrgUsageReport.js.map +1 -1
  436. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts +2 -0
  437. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts.map +1 -0
  438. package/workspace/__tests__/useWorkspaceEntries-stability.test.js +57 -0
  439. package/workspace/__tests__/useWorkspaceEntries-stability.test.js.map +1 -0
  440. package/workspace/useWorkspaceEntries.d.ts.map +1 -1
  441. package/workspace/useWorkspaceEntries.js +5 -4
  442. package/workspace/useWorkspaceEntries.js.map +1 -1
@@ -0,0 +1,401 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import React from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import {
6
+ AgentExecutionSchema,
7
+ AgentExecutionStatusSchema,
8
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
9
+ import { AgentExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
10
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
11
+ import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
12
+ import {
13
+ ExecutionPhase,
14
+ MessageType,
15
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
16
+ import {
17
+ buildThreadItems,
18
+ ThreadItemRenderer,
19
+ type ThreadItem,
20
+ } from "../MessageThread";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Mock react-virtuoso — Virtuoso's internal scroll/resize machinery
24
+ // does not work in happy-dom. We mock the component to capture props
25
+ // and verify configuration.
26
+ // ---------------------------------------------------------------------------
27
+
28
+ let capturedVirtuosoProps: Record<string, unknown> = {};
29
+
30
+ vi.mock("react-virtuoso", () => ({
31
+ Virtuoso: React.forwardRef(function MockVirtuoso(
32
+ props: Record<string, unknown>,
33
+ ref: React.Ref<unknown>,
34
+ ) {
35
+ capturedVirtuosoProps = props;
36
+ const data = props.data as ThreadItem[];
37
+ const itemContent = props.itemContent as (
38
+ index: number,
39
+ item: ThreadItem,
40
+ ) => React.ReactNode;
41
+
42
+ React.useImperativeHandle(ref, () => ({
43
+ scrollToIndex: vi.fn(),
44
+ }));
45
+
46
+ const Scroller =
47
+ (
48
+ props.components as {
49
+ Scroller?: React.ComponentType<Record<string, unknown>>;
50
+ }
51
+ )?.Scroller ?? "div";
52
+
53
+ return (
54
+ <Scroller data-testid="virtuoso-scroller">
55
+ {data.map((item, i) => (
56
+ <div key={item.key} data-testid={`virtuoso-item-${i}`}>
57
+ {itemContent(i, item)}
58
+ </div>
59
+ ))}
60
+ </Scroller>
61
+ );
62
+ }),
63
+ }));
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Observer / rAF mocks (required for useAutoScroll in non-virtualized path)
67
+ // ---------------------------------------------------------------------------
68
+
69
+ beforeEach(() => {
70
+ capturedVirtuosoProps = {};
71
+
72
+ vi.stubGlobal(
73
+ "IntersectionObserver",
74
+ vi.fn(() => ({
75
+ observe: vi.fn(),
76
+ unobserve: vi.fn(),
77
+ disconnect: vi.fn(),
78
+ takeRecords: vi.fn(() => []),
79
+ root: null,
80
+ rootMargin: "",
81
+ thresholds: [0],
82
+ })),
83
+ );
84
+
85
+ vi.stubGlobal(
86
+ "ResizeObserver",
87
+ vi.fn(() => ({
88
+ observe: vi.fn(),
89
+ unobserve: vi.fn(),
90
+ disconnect: vi.fn(),
91
+ })),
92
+ );
93
+
94
+ vi.stubGlobal(
95
+ "requestAnimationFrame",
96
+ vi.fn((cb: FrameRequestCallback) => {
97
+ cb(performance.now());
98
+ return 1;
99
+ }),
100
+ );
101
+
102
+ vi.stubGlobal("cancelAnimationFrame", vi.fn());
103
+ });
104
+
105
+ afterEach(() => {
106
+ cleanup();
107
+ vi.restoreAllMocks();
108
+ });
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Helpers
112
+ // ---------------------------------------------------------------------------
113
+
114
+ function makeExecution(id: string, specMessage: string, aiContent: string) {
115
+ const exec = create(AgentExecutionSchema);
116
+
117
+ const meta = create(ApiResourceMetadataSchema);
118
+ meta.id = id;
119
+ exec.metadata = meta;
120
+
121
+ const spec = create(AgentExecutionSpecSchema);
122
+ spec.message = specMessage;
123
+ exec.spec = spec;
124
+
125
+ const status = create(AgentExecutionStatusSchema);
126
+ status.phase = ExecutionPhase.EXECUTION_COMPLETED;
127
+ const humanMsg = create(AgentMessageSchema);
128
+ humanMsg.type = MessageType.MESSAGE_HUMAN;
129
+ humanMsg.content = specMessage;
130
+ const aiMsg = create(AgentMessageSchema);
131
+ aiMsg.type = MessageType.MESSAGE_AI;
132
+ aiMsg.content = aiContent;
133
+ status.messages = [humanMsg, aiMsg];
134
+ exec.status = status;
135
+
136
+ return exec;
137
+ }
138
+
139
+ // Lazy import — must import after mocks are set up
140
+ async function importMessageThread() {
141
+ const mod = await import("../MessageThread");
142
+ return mod.MessageThread;
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Tests: ThreadItemRenderer
147
+ // ---------------------------------------------------------------------------
148
+
149
+ describe("ThreadItemRenderer", () => {
150
+ it("renders a message item", () => {
151
+ const msg = create(AgentMessageSchema);
152
+ msg.type = MessageType.MESSAGE_HUMAN;
153
+ msg.content = "Hello world";
154
+
155
+ const item: ThreadItem = { kind: "message", message: msg, key: "test-1" };
156
+
157
+ render(<ThreadItemRenderer item={item} />);
158
+ expect(screen.getByText("Hello world")).toBeTruthy();
159
+ });
160
+
161
+ it("renders a pending message with opacity", () => {
162
+ const msg = create(AgentMessageSchema);
163
+ msg.type = MessageType.MESSAGE_HUMAN;
164
+ msg.content = "Sending...";
165
+
166
+ const item: ThreadItem = {
167
+ kind: "message",
168
+ message: msg,
169
+ key: "pending",
170
+ isPending: true,
171
+ };
172
+
173
+ const { container } = render(<ThreadItemRenderer item={item} />);
174
+ const root = container.firstElementChild as HTMLElement;
175
+ expect(root?.className).toContain("opacity-70");
176
+ });
177
+
178
+ it("renders a phase badge item", () => {
179
+ const item: ThreadItem = {
180
+ kind: "phase-badge",
181
+ phase: ExecutionPhase.EXECUTION_FAILED,
182
+ key: "phase-1",
183
+ };
184
+
185
+ render(<ThreadItemRenderer item={item} />);
186
+ expect(screen.getByText(/failed/i)).toBeTruthy();
187
+ });
188
+
189
+ it("renders a setup progress item", () => {
190
+ const item: ThreadItem = {
191
+ kind: "setup-progress",
192
+ workspaceEntries: [],
193
+ key: "setup",
194
+ };
195
+
196
+ render(<ThreadItemRenderer item={item} />);
197
+ expect(document.querySelector("[class]")).toBeTruthy();
198
+ });
199
+ });
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Tests: MessageThread with virtualized=false (default, regression)
203
+ // ---------------------------------------------------------------------------
204
+
205
+ describe("MessageThread (non-virtualized)", () => {
206
+ it("renders items in a role=log container by default", async () => {
207
+ const MessageThread = await importMessageThread();
208
+ const exec = makeExecution("e1", "Hello", "Hi there");
209
+
210
+ render(<MessageThread executions={[exec]} />);
211
+
212
+ const log = screen.getByRole("log");
213
+ expect(log).toBeTruthy();
214
+ expect(log.getAttribute("aria-live")).toBe("polite");
215
+ expect(log.getAttribute("aria-relevant")).toBe("additions");
216
+ });
217
+
218
+ it("does not render Virtuoso when virtualized is false", async () => {
219
+ const MessageThread = await importMessageThread();
220
+ const exec = makeExecution("e1", "Hello", "Hi");
221
+
222
+ render(<MessageThread executions={[exec]} />);
223
+ expect(capturedVirtuosoProps.data).toBeUndefined();
224
+ });
225
+ });
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Tests: MessageThread with virtualized=true
229
+ // ---------------------------------------------------------------------------
230
+
231
+ describe("MessageThread (virtualized)", () => {
232
+ it("renders via Virtuoso when virtualized=true", async () => {
233
+ const MessageThread = await importMessageThread();
234
+ const exec = makeExecution("e1", "Hello", "Hi there");
235
+
236
+ render(<MessageThread executions={[exec]} virtualized />);
237
+
238
+ // Wait for lazy import to resolve
239
+ await vi.waitFor(() => {
240
+ expect(capturedVirtuosoProps.data).toBeDefined();
241
+ });
242
+
243
+ const data = capturedVirtuosoProps.data as ThreadItem[];
244
+ expect(data.length).toBeGreaterThan(0);
245
+ });
246
+
247
+ it("passes alignToBottom=true to Virtuoso", async () => {
248
+ const MessageThread = await importMessageThread();
249
+ const exec = makeExecution("e1", "Hello", "Hi");
250
+
251
+ render(<MessageThread executions={[exec]} virtualized />);
252
+
253
+ await vi.waitFor(() => {
254
+ expect(capturedVirtuosoProps.alignToBottom).toBe(true);
255
+ });
256
+ });
257
+
258
+ it("passes followOutput callback to Virtuoso", async () => {
259
+ const MessageThread = await importMessageThread();
260
+ const exec = makeExecution("e1", "Hello", "Hi");
261
+
262
+ render(<MessageThread executions={[exec]} virtualized />);
263
+
264
+ await vi.waitFor(() => {
265
+ expect(typeof capturedVirtuosoProps.followOutput).toBe("function");
266
+ });
267
+
268
+ const followOutput = capturedVirtuosoProps.followOutput as (
269
+ atBottom: boolean,
270
+ ) => string | false;
271
+ expect(followOutput(true)).toBe("smooth");
272
+ expect(followOutput(false)).toBe(false);
273
+ });
274
+
275
+ it("uses stable semantic keys via computeItemKey", async () => {
276
+ const MessageThread = await importMessageThread();
277
+ const exec = makeExecution("exec-42", "Hello", "Hi");
278
+
279
+ render(<MessageThread executions={[exec]} virtualized />);
280
+
281
+ await vi.waitFor(() => {
282
+ expect(capturedVirtuosoProps.computeItemKey).toBeDefined();
283
+ });
284
+
285
+ const data = capturedVirtuosoProps.data as ThreadItem[];
286
+ const computeItemKey = capturedVirtuosoProps.computeItemKey as (
287
+ index: number,
288
+ item: ThreadItem,
289
+ ) => string;
290
+
291
+ for (let i = 0; i < data.length; i++) {
292
+ expect(computeItemKey(i, data[i])).toBe(data[i].key);
293
+ }
294
+ });
295
+
296
+ it("sets atBottomThreshold matching the non-virtualized 80px margin", async () => {
297
+ const MessageThread = await importMessageThread();
298
+ const exec = makeExecution("e1", "Hello", "Hi");
299
+
300
+ render(<MessageThread executions={[exec]} virtualized />);
301
+
302
+ await vi.waitFor(() => {
303
+ expect(capturedVirtuosoProps.atBottomThreshold).toBe(80);
304
+ });
305
+ });
306
+
307
+ it("applies a11y attributes to the scroller", async () => {
308
+ const MessageThread = await importMessageThread();
309
+ const exec = makeExecution("e1", "Hello", "Hi");
310
+
311
+ render(<MessageThread executions={[exec]} virtualized />);
312
+
313
+ await vi.waitFor(() => {
314
+ const scroller = screen.getByTestId("virtuoso-scroller");
315
+ expect(scroller.getAttribute("role")).toBe("log");
316
+ expect(scroller.getAttribute("aria-live")).toBe("polite");
317
+ expect(scroller.getAttribute("aria-relevant")).toBe("additions");
318
+ });
319
+ });
320
+
321
+ it("renders actual thread item content through Virtuoso", async () => {
322
+ const MessageThread = await importMessageThread();
323
+ const exec = makeExecution("e1", "Hello from user", "Hello from AI");
324
+
325
+ render(<MessageThread executions={[exec]} virtualized />);
326
+
327
+ await vi.waitFor(() => {
328
+ expect(screen.getAllByText("Hello from user").length).toBeGreaterThan(0);
329
+ expect(screen.getByText("Hello from AI")).toBeTruthy();
330
+ });
331
+ });
332
+
333
+ it("sets increaseViewportBy for overscan", async () => {
334
+ const MessageThread = await importMessageThread();
335
+ const exec = makeExecution("e1", "Hello", "Hi");
336
+
337
+ render(<MessageThread executions={[exec]} virtualized />);
338
+
339
+ await vi.waitFor(() => {
340
+ expect(capturedVirtuosoProps.increaseViewportBy).toEqual({
341
+ top: 200,
342
+ bottom: 200,
343
+ });
344
+ });
345
+ });
346
+
347
+ it("builds the same items for both paths", async () => {
348
+ const exec = makeExecution("e1", "Hello", "Hi there");
349
+ const items = buildThreadItems([exec], null, null, false, undefined);
350
+
351
+ const MessageThread = await importMessageThread();
352
+ render(<MessageThread executions={[exec]} virtualized />);
353
+
354
+ await vi.waitFor(() => {
355
+ const data = capturedVirtuosoProps.data as ThreadItem[];
356
+ expect(data.map((d) => d.key)).toEqual(items.map((i) => i.key));
357
+ });
358
+ });
359
+
360
+ it("applies entry animation only to tail items (last 2)", async () => {
361
+ const execs = [
362
+ makeExecution("e1", "First", "Response 1"),
363
+ makeExecution("e2", "Second", "Response 2"),
364
+ makeExecution("e3", "Third", "Response 3"),
365
+ ];
366
+
367
+ const MessageThread = await importMessageThread();
368
+ render(<MessageThread executions={execs} virtualized />);
369
+
370
+ await vi.waitFor(() => {
371
+ const data = capturedVirtuosoProps.data as ThreadItem[];
372
+ expect(data.length).toBeGreaterThan(2);
373
+ });
374
+
375
+ const data = capturedVirtuosoProps.data as ThreadItem[];
376
+ const itemContent = capturedVirtuosoProps.itemContent as (
377
+ index: number,
378
+ item: ThreadItem,
379
+ ) => React.ReactNode;
380
+ const tailThreshold = data.length - 2;
381
+
382
+ // Non-tail item: no animation wrapper
383
+ const earlyResult = render(
384
+ <div data-testid="early">{itemContent(0, data[0])}</div>,
385
+ );
386
+ expect(
387
+ earlyResult.container.querySelector(".stgm-thread-item-enter"),
388
+ ).toBeNull();
389
+ earlyResult.unmount();
390
+
391
+ // Tail item: has animation wrapper
392
+ const lastIdx = data.length - 1;
393
+ const tailResult = render(
394
+ <div data-testid="tail">{itemContent(lastIdx, data[lastIdx])}</div>,
395
+ );
396
+ expect(
397
+ tailResult.container.querySelector(".stgm-thread-item-enter"),
398
+ ).toBeTruthy();
399
+ tailResult.unmount();
400
+ });
401
+ });
@@ -87,6 +87,9 @@ export type { MessageEntryProps } from "./MessageEntry";
87
87
  export { MessageThread } from "./MessageThread";
88
88
  export type { MessageThreadProps } from "./MessageThread";
89
89
 
90
+ export { ThreadSkeleton } from "./ThreadSkeleton";
91
+ export type { ThreadSkeletonProps } from "./ThreadSkeleton";
92
+
90
93
  export { FollowUpInput } from "./FollowUpInput";
91
94
  export type { FollowUpInputProps } from "./FollowUpInput";
92
95
 
@@ -1,10 +1,24 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import {
4
+ startTransition,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ useSyncExternalStore,
11
+ } from "react";
4
12
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
13
  import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
14
  import { useStigmer } from "../hooks";
7
15
  import { toError } from "../internal/toError";
16
+ import { useStreamRate } from "../internal/dev";
17
+ import {
18
+ StreamController,
19
+ type StreamControllerSink,
20
+ } from "../internal/stream-controller";
21
+ import { ConversationStore, type StreamState } from "../internal/store";
8
22
  import { isTerminalPhase } from "./execution-phases";
9
23
 
10
24
  /** Return value of {@link useExecutionStream}. */
@@ -14,9 +28,9 @@ export interface UseExecutionStreamReturn {
14
28
  /**
15
29
  * Convenience extraction of `execution.status.phase`.
16
30
  *
17
- * Derived from `execution` via `useMemo` — always consistent with the
18
- * current snapshot. Returns `EXECUTION_PHASE_UNSPECIFIED` when
19
- * `execution` is `null`.
31
+ * Derived from `execution` — always consistent with the current
32
+ * snapshot. Returns `EXECUTION_PHASE_UNSPECIFIED` when `execution`
33
+ * is `null`.
20
34
  */
21
35
  readonly phase: ExecutionPhase;
22
36
  /** `true` while receiving non-terminal updates from the server stream. */
@@ -35,17 +49,42 @@ export interface UseExecutionStreamReturn {
35
49
  readonly reconnect: () => void;
36
50
  }
37
51
 
52
+ /**
53
+ * Options for {@link useExecutionStream}.
54
+ */
55
+ export interface UseExecutionStreamOptions {
56
+ /**
57
+ * External `ConversationStore` to ingest snapshots into.
58
+ *
59
+ * When provided, the hook writes directly to this store and reads
60
+ * the execution snapshot back via `useSyncExternalStore`. This
61
+ * allows `useSessionConversation` to share a single store instance
62
+ * across the stream hook and the rendering tree.
63
+ *
64
+ * When omitted, an internal store is created automatically —
65
+ * preserving backward compatibility for standalone usage.
66
+ */
67
+ readonly store?: ConversationStore;
68
+ }
69
+
38
70
  /**
39
71
  * Behavior hook that subscribes to real-time {@link AgentExecution}
40
72
  * updates via `stigmer.agentExecution.subscribe()`.
41
73
  *
42
- * Manages the full subscription lifecycle: connection establishment,
43
- * snapshot streaming, terminal-phase detection, error handling, and
44
- * manual reconnection. Each server message replaces the previous
45
- * snapshot atomically — no delta merging.
74
+ * Manages the full subscription lifecycle through a finite state
75
+ * machine: connection establishment, rAF-coalesced snapshot streaming,
76
+ * terminal-phase detection, error handling, and manual reconnection.
77
+ *
78
+ * **Performance characteristics:**
79
+ * - Non-terminal snapshots are coalesced via `requestAnimationFrame`
80
+ * so React commits at most once per display frame (~60Hz)
81
+ * - Terminal snapshots (complete/failed/cancelled) flush immediately
82
+ * - Store updates are wrapped in `startTransition` so thread renders
83
+ * don't block urgent interactions (e.g. composer typing)
46
84
  *
47
- * Pass `null` to skip subscribing (stable no-op). When `executionId`
48
- * changes, the previous subscription is aborted and a fresh one begins.
85
+ * Pass `null` as `executionId` to skip subscribing (stable no-op).
86
+ * When `executionId` changes, the previous subscription is aborted
87
+ * and a fresh one begins.
49
88
  *
50
89
  * @example
51
90
  * ```tsx
@@ -69,82 +108,118 @@ export interface UseExecutionStreamReturn {
69
108
  */
70
109
  export function useExecutionStream(
71
110
  executionId: string | null,
111
+ options?: UseExecutionStreamOptions,
72
112
  ): UseExecutionStreamReturn {
73
113
  const stigmer = useStigmer();
74
114
 
75
- const [execution, setExecution] = useState<AgentExecution | null>(null);
76
- const [isConnecting, setIsConnecting] = useState(false);
77
- const [isStreaming, setIsStreaming] = useState(false);
78
- const [error, setError] = useState<Error | null>(null);
79
- const [connectKey, setConnectKey] = useState(0);
80
-
81
- const abortRef = useRef<AbortController | null>(null);
115
+ // -- Store setup ----------------------------------------------------------
116
+ // Use the externally provided store, or create a private one for
117
+ // standalone usage. The ref ensures the internal store is stable
118
+ // across re-renders.
119
+ const internalStoreRef = useRef<ConversationStore | null>(null);
120
+ if (!options?.store && !internalStoreRef.current) {
121
+ internalStoreRef.current = new ConversationStore();
122
+ }
123
+ const store = options?.store ?? internalStoreRef.current!;
124
+
125
+ // -- Controller setup -----------------------------------------------------
126
+ const controllerRef = useRef<StreamController | null>(null);
127
+ if (!controllerRef.current) {
128
+ const sink: StreamControllerSink = {
129
+ ingestSnapshot(snapshot) {
130
+ startTransition(() => {
131
+ store.ingestSnapshot(snapshot);
132
+ });
133
+ },
134
+ setStreamState(state) {
135
+ startTransition(() => {
136
+ store.setStreamState(state);
137
+ });
138
+ },
139
+ };
140
+ controllerRef.current = new StreamController(sink);
141
+ }
142
+ const controller = controllerRef.current;
82
143
 
144
+ // -- Reconnect ------------------------------------------------------------
145
+ const [connectKey, setConnectKey] = useState(0);
83
146
  const reconnect = useCallback(() => {
84
- setError(null);
85
147
  setConnectKey((k) => k + 1);
86
148
  }, []);
87
149
 
150
+ // -- Stream rate instrumentation ------------------------------------------
151
+ const streamRate = useStreamRate();
152
+ const streamRateRef = useRef(streamRate);
153
+ streamRateRef.current = streamRate;
154
+
155
+ // -- Subscription effect --------------------------------------------------
156
+ // Note: controller, store, and streamRate are ref-backed stable objects —
157
+ // they MUST NOT appear in the deps array. Including them would cause
158
+ // infinite re-renders because useStreamRate returns a new object per render.
88
159
  useEffect(() => {
89
160
  if (!executionId) {
90
- setExecution(null);
91
- setIsConnecting(false);
92
- setIsStreaming(false);
93
- setError(null);
161
+ controller.reset();
162
+ store.reset();
94
163
  return;
95
164
  }
96
165
 
97
- abortRef.current?.abort();
98
- const controller = new AbortController();
99
- abortRef.current = controller;
100
-
101
- setExecution(null);
102
- setIsConnecting(true);
103
- setIsStreaming(false);
104
- setError(null);
166
+ const abortController = new AbortController();
167
+ controller.start(executionId);
105
168
 
106
169
  (async () => {
107
170
  try {
108
171
  for await (const snapshot of stigmer.agentExecution.subscribe(
109
172
  executionId,
110
- controller.signal,
173
+ abortController.signal,
111
174
  )) {
112
- if (controller.signal.aborted) return;
175
+ if (abortController.signal.aborted) return;
176
+
177
+ controller.handleSnapshot(snapshot);
178
+ streamRateRef.current.tick(
179
+ snapshot.status?.messages?.length ?? 0,
180
+ );
113
181
 
114
- const currentPhase =
182
+ const phase =
115
183
  snapshot.status?.phase ??
116
184
  ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
117
- const isTerminal = isTerminalPhase(currentPhase);
118
-
119
- setExecution(snapshot);
120
- setIsConnecting(false);
121
- setIsStreaming(!isTerminal);
122
-
123
- if (isTerminal) break;
185
+ if (isTerminalPhase(phase)) break;
124
186
  }
125
187
 
126
- if (!controller.signal.aborted) {
127
- setIsStreaming(false);
188
+ if (!abortController.signal.aborted) {
189
+ controller.handleStreamEnd();
190
+ streamRateRef.current.summary();
128
191
  }
129
192
  } catch (err) {
130
- if (controller.signal.aborted) return;
131
-
132
- setError(toError(err));
133
- setIsConnecting(false);
134
- setIsStreaming(false);
193
+ if (abortController.signal.aborted) return;
194
+ controller.handleError(toError(err));
135
195
  }
136
196
  })();
137
197
 
138
198
  return () => {
139
- controller.abort();
199
+ abortController.abort();
200
+ controller.reset();
201
+ store.reset();
140
202
  };
141
203
  }, [executionId, stigmer, connectKey]);
142
204
 
205
+ // -- Read from store via useSyncExternalStore ------------------------------
206
+ const execution = useSyncExternalStore(store.subscribe, store.getExecution);
207
+ const streamState = useSyncExternalStore(
208
+ store.subscribe,
209
+ store.getStreamState,
210
+ );
211
+
212
+ // -- Derive public return values ------------------------------------------
143
213
  const phase = useMemo(
144
214
  () =>
145
215
  execution?.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED,
146
216
  [execution],
147
217
  );
148
218
 
219
+ const isStreaming = streamState.stage === "streaming";
220
+ const isConnecting = streamState.stage === "connecting";
221
+ const error =
222
+ streamState.stage === "error" ? streamState.error : null;
223
+
149
224
  return { execution, phase, isStreaming, isConnecting, error, reconnect };
150
225
  }
@@ -180,16 +180,21 @@ export function useSessionVariables(): UseSessionVariablesReturn {
180
180
  [entries],
181
181
  );
182
182
 
183
- return {
184
- entries,
185
- addEntry,
186
- removeEntry,
187
- updateEntry,
188
- clear,
189
- isEmpty: entries.length === 0,
190
- hasValidEntries,
191
- toRuntimeEnv,
192
- toSaveForFutureEnv,
193
- hasSaveForFutureEntries,
194
- };
183
+ const isEmpty = entries.length === 0;
184
+
185
+ return useMemo(
186
+ () => ({
187
+ entries,
188
+ addEntry,
189
+ removeEntry,
190
+ updateEntry,
191
+ clear,
192
+ isEmpty,
193
+ hasValidEntries,
194
+ toRuntimeEnv,
195
+ toSaveForFutureEnv,
196
+ hasSaveForFutureEntries,
197
+ }),
198
+ [entries, addEntry, removeEntry, updateEntry, clear, isEmpty, hasValidEntries, toRuntimeEnv, toSaveForFutureEnv, hasSaveForFutureEntries],
199
+ );
195
200
  }