@stigmer/react 0.3.4 → 0.4.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 (450) 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/mcp-server/McpServerDetailView.d.ts.map +1 -1
  269. package/mcp-server/McpServerDetailView.js +3 -3
  270. package/mcp-server/McpServerDetailView.js.map +1 -1
  271. package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
  272. package/mcp-server/useMcpServerOAuthConnect.js +37 -9
  273. package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
  274. package/package.json +7 -5
  275. package/session/__tests__/useNewSessionFlow.test.js +16 -0
  276. package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
  277. package/session/__tests__/usePersistedModel.test.d.ts +2 -0
  278. package/session/__tests__/usePersistedModel.test.d.ts.map +1 -0
  279. package/session/__tests__/usePersistedModel.test.js +82 -0
  280. package/session/__tests__/usePersistedModel.test.js.map +1 -0
  281. package/session/__tests__/useSession.test.d.ts +2 -0
  282. package/session/__tests__/useSession.test.d.ts.map +1 -0
  283. package/session/__tests__/useSession.test.js +130 -0
  284. package/session/__tests__/useSession.test.js.map +1 -0
  285. package/session/useNewSessionFlow.d.ts.map +1 -1
  286. package/session/useNewSessionFlow.js +12 -6
  287. package/session/useNewSessionFlow.js.map +1 -1
  288. package/session/usePersistedModel.d.ts +3 -0
  289. package/session/usePersistedModel.d.ts.map +1 -1
  290. package/session/usePersistedModel.js +27 -2
  291. package/session/usePersistedModel.js.map +1 -1
  292. package/session/useSession.d.ts.map +1 -1
  293. package/session/useSession.js +1 -1
  294. package/session/useSession.js.map +1 -1
  295. package/session/useSessionConversation.d.ts.map +1 -1
  296. package/session/useSessionConversation.js +9 -1
  297. package/session/useSessionConversation.js.map +1 -1
  298. package/session/useSessionExecutions.d.ts.map +1 -1
  299. package/session/useSessionExecutions.js +1 -1
  300. package/session/useSessionExecutions.js.map +1 -1
  301. package/session/useSessionPageFlow.js +1 -1
  302. package/session/useSessionPageFlow.js.map +1 -1
  303. package/session/useSessionUsage.d.ts +24 -40
  304. package/session/useSessionUsage.d.ts.map +1 -1
  305. package/session/useSessionUsage.js +64 -97
  306. package/session/useSessionUsage.js.map +1 -1
  307. package/settings/BillingSection.d.ts +3 -0
  308. package/settings/BillingSection.d.ts.map +1 -0
  309. package/settings/BillingSection.js +3 -0
  310. package/settings/BillingSection.js.map +1 -0
  311. package/settings/index.d.ts +2 -0
  312. package/settings/index.d.ts.map +1 -1
  313. package/settings/index.js +1 -0
  314. package/settings/index.js.map +1 -1
  315. package/settings/settings-nav.js +1 -1
  316. package/settings/settings-nav.js.map +1 -1
  317. package/src/billing/AutoRechargeCard.tsx +274 -0
  318. package/src/billing/BillingSection.tsx +255 -0
  319. package/src/billing/CreditBalanceCard.tsx +81 -0
  320. package/src/billing/CreditLedgerTable.tsx +281 -0
  321. package/src/billing/CreditPackGrid.tsx +132 -0
  322. package/src/billing/LowBalanceBanner.tsx +67 -0
  323. package/src/billing/PaymentMethodCard.tsx +133 -0
  324. package/src/billing/credit-packs.ts +54 -0
  325. package/src/billing/format.ts +97 -0
  326. package/src/billing/index.ts +51 -0
  327. package/src/billing/useBillingAccount.ts +64 -0
  328. package/src/billing/useBillingUsageReport.ts +73 -0
  329. package/src/billing/useCreateBillingPortalSession.ts +76 -0
  330. package/src/billing/useCreateCheckoutSession.ts +101 -0
  331. package/src/billing/useCreditLedger.ts +79 -0
  332. package/src/billing/useCustomerModelPricing.ts +67 -0
  333. package/src/billing/useSetAutoRechargeConfig.ts +90 -0
  334. package/src/composer/ComposerToolbar.tsx +1 -1
  335. package/src/composer/SessionComposer.tsx +22 -4
  336. package/src/composer/__tests__/SessionComposer-memo.test.ts +26 -0
  337. package/src/execution/ApprovalCard.tsx +7 -3
  338. package/src/execution/ExecutionPhaseBadge.tsx +3 -2
  339. package/src/execution/MessageEntry.tsx +27 -16
  340. package/src/execution/MessageThread.tsx +308 -131
  341. package/src/execution/SetupProgress.tsx +3 -3
  342. package/src/execution/SubAgentSection.tsx +14 -6
  343. package/src/execution/ThreadSkeleton.tsx +73 -0
  344. package/src/execution/ToolCallGroup.tsx +36 -3
  345. package/src/execution/UsageWidget.tsx +1 -1
  346. package/src/execution/__tests__/message-entry.test.tsx +236 -0
  347. package/src/execution/__tests__/thread-keys.test.ts +409 -0
  348. package/src/execution/__tests__/thread-memoization.test.ts +320 -0
  349. package/src/execution/__tests__/thread-skeleton.test.tsx +44 -0
  350. package/src/execution/__tests__/useExecutionStream.test.tsx +109 -12
  351. package/src/execution/__tests__/useSessionVariables-stability.test.ts +95 -0
  352. package/src/execution/__tests__/virtualized-thread.test.tsx +401 -0
  353. package/src/execution/index.ts +3 -0
  354. package/src/execution/useExecutionStream.ts +123 -48
  355. package/src/execution/useSessionVariables.ts +17 -12
  356. package/src/github/useGitHubConnection.ts +18 -13
  357. package/src/identity-account/index.ts +5 -0
  358. package/src/identity-account/useIdentityAccountGate.ts +163 -0
  359. package/src/index.ts +73 -0
  360. package/src/internal/FetchCacheProvider.tsx +74 -0
  361. package/src/internal/JumpToLatestButton.tsx +61 -0
  362. package/src/internal/ThreadItemWrapper.tsx +65 -0
  363. package/src/internal/VirtualizedThread.tsx +162 -0
  364. package/src/internal/__tests__/fetch-cache.test.ts +230 -0
  365. package/src/internal/__tests__/stream-controller.test.ts +395 -0
  366. package/src/internal/__tests__/thread-animation.test.tsx +121 -0
  367. package/src/internal/__tests__/useAutoScroll.test.tsx +261 -0
  368. package/src/internal/__tests__/useFetch-cache.test.ts +214 -0
  369. package/src/internal/dev/__tests__/use-key-stability.test.ts +124 -0
  370. package/src/internal/dev/__tests__/use-render-tracer.test.ts +78 -0
  371. package/src/internal/dev/dom-counter.ts +47 -0
  372. package/src/internal/dev/index.ts +5 -0
  373. package/src/internal/dev/profiler-wrapper.tsx +52 -0
  374. package/src/internal/dev/use-key-stability.ts +86 -0
  375. package/src/internal/dev/use-render-tracer.ts +70 -0
  376. package/src/internal/dev/use-stream-rate.ts +138 -0
  377. package/src/internal/fetch-cache.ts +155 -0
  378. package/src/internal/store/__tests__/conversation-store.test.ts +257 -0
  379. package/src/internal/store/__tests__/structural-share.test.ts +454 -0
  380. package/src/internal/store/conversation-store.ts +128 -0
  381. package/src/internal/store/index.ts +68 -0
  382. package/src/internal/store/structural-share.ts +318 -0
  383. package/src/internal/stream-controller.ts +201 -0
  384. package/src/internal/useAutoScroll.ts +121 -0
  385. package/src/internal/useFetch.ts +51 -2
  386. package/src/mcp-server/McpServerDetailView.tsx +15 -0
  387. package/src/mcp-server/useMcpServerOAuthConnect.ts +37 -9
  388. package/src/session/__tests__/useNewSessionFlow.test.tsx +22 -0
  389. package/src/session/__tests__/usePersistedModel.test.tsx +117 -0
  390. package/src/session/__tests__/useSession.test.tsx +187 -0
  391. package/src/session/useNewSessionFlow.ts +12 -6
  392. package/src/session/usePersistedModel.ts +28 -2
  393. package/src/session/useSession.ts +1 -0
  394. package/src/session/useSessionConversation.ts +11 -2
  395. package/src/session/useSessionExecutions.ts +1 -0
  396. package/src/session/useSessionPageFlow.ts +1 -1
  397. package/src/session/useSessionUsage.ts +102 -123
  398. package/src/settings/BillingSection.tsx +4 -0
  399. package/src/settings/index.ts +2 -0
  400. package/src/settings/settings-nav.ts +1 -1
  401. package/src/styles.css +31 -0
  402. package/src/usage/AgentBreakdownList.tsx +147 -0
  403. package/src/usage/CreditRunwayIndicator.tsx +71 -0
  404. package/src/usage/ExportButton.tsx +115 -0
  405. package/src/usage/HarnessSplitCard.tsx +103 -0
  406. package/src/usage/OrgUsagePanel.tsx +109 -45
  407. package/src/usage/index.ts +15 -0
  408. package/src/usage/useExportCSV.ts +115 -0
  409. package/src/usage/useOrgUsageReport.ts +2 -1
  410. package/src/workspace/__tests__/useWorkspaceEntries-stability.test.ts +76 -0
  411. package/src/workspace/useWorkspaceEntries.ts +16 -11
  412. package/styles.css +1 -1
  413. package/usage/AgentBreakdownList.d.ts +21 -0
  414. package/usage/AgentBreakdownList.d.ts.map +1 -0
  415. package/usage/AgentBreakdownList.js +44 -0
  416. package/usage/AgentBreakdownList.js.map +1 -0
  417. package/usage/CreditRunwayIndicator.d.ts +21 -0
  418. package/usage/CreditRunwayIndicator.d.ts.map +1 -0
  419. package/usage/CreditRunwayIndicator.js +38 -0
  420. package/usage/CreditRunwayIndicator.js.map +1 -0
  421. package/usage/ExportButton.d.ts +20 -0
  422. package/usage/ExportButton.d.ts.map +1 -0
  423. package/usage/ExportButton.js +36 -0
  424. package/usage/ExportButton.js.map +1 -0
  425. package/usage/HarnessSplitCard.d.ts +17 -0
  426. package/usage/HarnessSplitCard.d.ts.map +1 -0
  427. package/usage/HarnessSplitCard.js +38 -0
  428. package/usage/HarnessSplitCard.js.map +1 -0
  429. package/usage/OrgUsagePanel.d.ts.map +1 -1
  430. package/usage/OrgUsagePanel.js +30 -22
  431. package/usage/OrgUsagePanel.js.map +1 -1
  432. package/usage/index.d.ts +10 -0
  433. package/usage/index.d.ts.map +1 -1
  434. package/usage/index.js +5 -0
  435. package/usage/index.js.map +1 -1
  436. package/usage/useExportCSV.d.ts +23 -0
  437. package/usage/useExportCSV.d.ts.map +1 -0
  438. package/usage/useExportCSV.js +81 -0
  439. package/usage/useExportCSV.js.map +1 -0
  440. package/usage/useOrgUsageReport.d.ts +2 -1
  441. package/usage/useOrgUsageReport.d.ts.map +1 -1
  442. package/usage/useOrgUsageReport.js +2 -1
  443. package/usage/useOrgUsageReport.js.map +1 -1
  444. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts +2 -0
  445. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts.map +1 -0
  446. package/workspace/__tests__/useWorkspaceEntries-stability.test.js +57 -0
  447. package/workspace/__tests__/useWorkspaceEntries-stability.test.js.map +1 -0
  448. package/workspace/useWorkspaceEntries.d.ts.map +1 -1
  449. package/workspace/useWorkspaceEntries.js +5 -4
  450. package/workspace/useWorkspaceEntries.js.map +1 -1
@@ -0,0 +1,318 @@
1
+ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
2
+ import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
3
+ import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
4
+ import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
5
+ import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
6
+
7
+ /**
8
+ * Compares two `AgentExecution` snapshots and returns a hybrid object
9
+ * that reuses old references for unchanged subtrees.
10
+ *
11
+ * The runner appends new messages and mutates the streaming tail.
12
+ * Messages at stable indices with unchanged content keep the previous
13
+ * reference so downstream `React.memo` can skip re-renders.
14
+ *
15
+ * When `prev` is `null` (first snapshot), returns `next` unchanged.
16
+ */
17
+ export function structuralShare(
18
+ prev: AgentExecution | null,
19
+ next: AgentExecution,
20
+ ): AgentExecution {
21
+ if (prev === null) return next;
22
+
23
+ const prevStatus = prev.status;
24
+ const nextStatus = next.status;
25
+
26
+ if (!prevStatus || !nextStatus) return next;
27
+
28
+ const sharedMessages = shareMessages(
29
+ prevStatus.messages,
30
+ nextStatus.messages,
31
+ );
32
+ const sharedSubAgents = shareSubAgents(
33
+ prevStatus.subAgentExecutions,
34
+ nextStatus.subAgentExecutions,
35
+ );
36
+ const sharedApprovals = shareApprovals(
37
+ prevStatus.pendingApprovals,
38
+ nextStatus.pendingApprovals,
39
+ );
40
+ const sharedTodos = shareTodos(prevStatus.todos, nextStatus.todos);
41
+
42
+ const messagesUnchanged = sharedMessages === prevStatus.messages;
43
+ const subAgentsUnchanged = sharedSubAgents === prevStatus.subAgentExecutions;
44
+ const approvalsUnchanged = sharedApprovals === prevStatus.pendingApprovals;
45
+ const todosUnchanged = sharedTodos === prevStatus.todos;
46
+
47
+ const statusFieldsUnchanged =
48
+ prevStatus.phase === nextStatus.phase &&
49
+ prevStatus.error === nextStatus.error &&
50
+ prevStatus.startedAt === nextStatus.startedAt &&
51
+ prevStatus.completedAt === nextStatus.completedAt &&
52
+ prevStatus.runnerId === nextStatus.runnerId;
53
+
54
+ if (
55
+ messagesUnchanged &&
56
+ subAgentsUnchanged &&
57
+ approvalsUnchanged &&
58
+ todosUnchanged &&
59
+ statusFieldsUnchanged &&
60
+ prevStatus.artifacts.length === nextStatus.artifacts.length &&
61
+ prevStatus.workspaceWriteBacks.length ===
62
+ nextStatus.workspaceWriteBacks.length
63
+ ) {
64
+ return prev;
65
+ }
66
+
67
+ const sharedStatus = Object.create(Object.getPrototypeOf(nextStatus));
68
+ Object.assign(sharedStatus, nextStatus);
69
+ sharedStatus.messages = sharedMessages;
70
+ sharedStatus.subAgentExecutions = sharedSubAgents;
71
+ sharedStatus.pendingApprovals = sharedApprovals;
72
+ sharedStatus.todos = sharedTodos;
73
+
74
+ const sharedExec = Object.create(Object.getPrototypeOf(next));
75
+ Object.assign(sharedExec, next);
76
+ sharedExec.status = sharedStatus;
77
+
78
+ return sharedExec;
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Messages — compare by index (append-only slots)
83
+ // ---------------------------------------------------------------------------
84
+
85
+ function shareMessages(
86
+ prev: readonly AgentMessage[],
87
+ next: readonly AgentMessage[],
88
+ ): readonly AgentMessage[] {
89
+ if (prev.length === 0 && next.length === 0) return prev;
90
+ if (prev.length === 0) return next;
91
+
92
+ let allSame = prev.length === next.length;
93
+ const result: AgentMessage[] = new Array(next.length);
94
+
95
+ for (let i = 0; i < next.length; i++) {
96
+ if (i < prev.length && messageEqual(prev[i], next[i])) {
97
+ result[i] = prev[i];
98
+ } else {
99
+ result[i] = shareToolCalls(
100
+ i < prev.length ? prev[i] : null,
101
+ next[i],
102
+ );
103
+ allSame = false;
104
+ }
105
+ }
106
+
107
+ return allSame ? prev : result;
108
+ }
109
+
110
+ function messageEqual(a: AgentMessage, b: AgentMessage): boolean {
111
+ return (
112
+ a.type === b.type &&
113
+ a.content === b.content &&
114
+ a.isStreaming === b.isStreaming &&
115
+ a.timestamp === b.timestamp &&
116
+ a.toolCalls.length === b.toolCalls.length &&
117
+ toolCallsEqual(a.toolCalls, b.toolCalls)
118
+ );
119
+ }
120
+
121
+ function toolCallsEqual(
122
+ a: readonly ToolCall[],
123
+ b: readonly ToolCall[],
124
+ ): boolean {
125
+ if (a.length !== b.length) return false;
126
+ for (let i = 0; i < a.length; i++) {
127
+ if (!toolCallEqual(a[i], b[i])) return false;
128
+ }
129
+ return true;
130
+ }
131
+
132
+ /**
133
+ * When the message itself has changed, try to preserve unchanged
134
+ * tool call references within it. Returns a new message with the
135
+ * tool calls array potentially reusing old references.
136
+ */
137
+ function shareToolCalls(
138
+ prev: AgentMessage | null,
139
+ next: AgentMessage,
140
+ ): AgentMessage {
141
+ if (!prev || prev.toolCalls.length === 0 || next.toolCalls.length === 0) {
142
+ return next;
143
+ }
144
+
145
+ const prevById = new Map<string, ToolCall>();
146
+ for (const tc of prev.toolCalls) {
147
+ if (tc.id) prevById.set(tc.id, tc);
148
+ }
149
+
150
+ if (prevById.size === 0) return next;
151
+
152
+ let allSame = prev.toolCalls.length === next.toolCalls.length;
153
+ const shared: ToolCall[] = new Array(next.toolCalls.length);
154
+
155
+ for (let i = 0; i < next.toolCalls.length; i++) {
156
+ const ntc = next.toolCalls[i];
157
+ const ptc = ntc.id ? prevById.get(ntc.id) : undefined;
158
+
159
+ if (ptc && toolCallEqual(ptc, ntc)) {
160
+ shared[i] = ptc;
161
+ } else {
162
+ shared[i] = ntc;
163
+ allSame = false;
164
+ }
165
+ }
166
+
167
+ if (allSame) return next;
168
+
169
+ const sharedMsg = Object.create(Object.getPrototypeOf(next));
170
+ Object.assign(sharedMsg, next);
171
+ sharedMsg.toolCalls = shared;
172
+ return sharedMsg;
173
+ }
174
+
175
+ function toolCallEqual(a: ToolCall, b: ToolCall): boolean {
176
+ return (
177
+ a.id === b.id &&
178
+ a.status === b.status &&
179
+ a.isStreaming === b.isStreaming &&
180
+ a.result === b.result &&
181
+ a.completedAt === b.completedAt &&
182
+ a.error === b.error &&
183
+ a.name === b.name &&
184
+ a.argsPreview === b.argsPreview
185
+ );
186
+ }
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Sub-agent executions — compare by `id`
190
+ // ---------------------------------------------------------------------------
191
+
192
+ function shareSubAgents(
193
+ prev: readonly SubAgentExecution[],
194
+ next: readonly SubAgentExecution[],
195
+ ): readonly SubAgentExecution[] {
196
+ if (prev.length === 0 && next.length === 0) return prev;
197
+ if (prev.length === 0) return next;
198
+
199
+ const prevById = new Map<string, SubAgentExecution>();
200
+ for (const sa of prev) {
201
+ if (sa.id) prevById.set(sa.id, sa);
202
+ }
203
+
204
+ let allSame = prev.length === next.length;
205
+ const result: SubAgentExecution[] = new Array(next.length);
206
+
207
+ for (let i = 0; i < next.length; i++) {
208
+ const nsa = next[i];
209
+ const psa = nsa.id ? prevById.get(nsa.id) : undefined;
210
+
211
+ if (psa && subAgentEqual(psa, nsa)) {
212
+ result[i] = psa;
213
+ } else if (psa) {
214
+ result[i] = shareSubAgentMessages(psa, nsa);
215
+ allSame = false;
216
+ } else {
217
+ result[i] = nsa;
218
+ allSame = false;
219
+ }
220
+ }
221
+
222
+ return allSame ? prev : result;
223
+ }
224
+
225
+ function subAgentEqual(a: SubAgentExecution, b: SubAgentExecution): boolean {
226
+ return (
227
+ a.id === b.id &&
228
+ a.status === b.status &&
229
+ a.output === b.output &&
230
+ a.error === b.error &&
231
+ a.completedAt === b.completedAt &&
232
+ a.messages.length === b.messages.length &&
233
+ a.subject === b.subject &&
234
+ subAgentMessagesEqual(a.messages, b.messages)
235
+ );
236
+ }
237
+
238
+ function subAgentMessagesEqual(
239
+ a: readonly AgentMessage[],
240
+ b: readonly AgentMessage[],
241
+ ): boolean {
242
+ if (a.length !== b.length) return false;
243
+ for (let i = 0; i < a.length; i++) {
244
+ if (!messageEqual(a[i], b[i])) return false;
245
+ }
246
+ return true;
247
+ }
248
+
249
+ function shareSubAgentMessages(
250
+ prev: SubAgentExecution,
251
+ next: SubAgentExecution,
252
+ ): SubAgentExecution {
253
+ const sharedMsgs = shareMessages(prev.messages, next.messages);
254
+ if (sharedMsgs === prev.messages && subAgentEqual(prev, next)) {
255
+ return prev;
256
+ }
257
+
258
+ const shared = Object.create(Object.getPrototypeOf(next));
259
+ Object.assign(shared, next);
260
+ shared.messages = sharedMsgs;
261
+ return shared;
262
+ }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Pending approvals — compare by `toolCallId`
266
+ // ---------------------------------------------------------------------------
267
+
268
+ function shareApprovals(
269
+ prev: readonly PendingApproval[],
270
+ next: readonly PendingApproval[],
271
+ ): readonly PendingApproval[] {
272
+ if (prev.length === 0 && next.length === 0) return prev;
273
+ if (prev.length !== next.length) return next;
274
+
275
+ const prevById = new Map<string, PendingApproval>();
276
+ for (const a of prev) {
277
+ prevById.set(a.toolCallId, a);
278
+ }
279
+
280
+ for (const na of next) {
281
+ const pa = prevById.get(na.toolCallId);
282
+ if (!pa || !approvalEqual(pa, na)) return next;
283
+ }
284
+
285
+ return prev;
286
+ }
287
+
288
+ function approvalEqual(a: PendingApproval, b: PendingApproval): boolean {
289
+ return (
290
+ a.toolCallId === b.toolCallId &&
291
+ a.toolName === b.toolName &&
292
+ a.message === b.message &&
293
+ a.argsPreview === b.argsPreview &&
294
+ a.requestedAt === b.requestedAt &&
295
+ a.fromSubAgent === b.fromSubAgent
296
+ );
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Todos — compare by map key
301
+ // ---------------------------------------------------------------------------
302
+
303
+ function shareTodos(
304
+ prev: Record<string, unknown>,
305
+ next: Record<string, unknown>,
306
+ ): Record<string, unknown> {
307
+ const prevKeys = Object.keys(prev);
308
+ const nextKeys = Object.keys(next);
309
+
310
+ if (prevKeys.length === 0 && nextKeys.length === 0) return prev;
311
+ if (prevKeys.length !== nextKeys.length) return next;
312
+
313
+ for (const key of nextKeys) {
314
+ if (!(key in prev) || prev[key] !== next[key]) return next;
315
+ }
316
+
317
+ return prev;
318
+ }
@@ -0,0 +1,201 @@
1
+ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
2
+ import type { ConversationStore } from "./store/conversation-store";
3
+ import { isTerminalPhase } from "../execution/execution-phases";
4
+ import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Types
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export type ControllerStage =
11
+ | "idle"
12
+ | "connecting"
13
+ | "streaming"
14
+ | "complete"
15
+ | "error";
16
+
17
+ export type ControllerState =
18
+ | { readonly stage: "idle" }
19
+ | { readonly stage: "connecting"; readonly executionId: string }
20
+ | { readonly stage: "streaming"; readonly executionId: string }
21
+ | { readonly stage: "complete"; readonly executionId: string }
22
+ | {
23
+ readonly stage: "error";
24
+ readonly executionId: string;
25
+ readonly error: Error;
26
+ };
27
+
28
+ const IDLE: ControllerState = { stage: "idle" };
29
+
30
+ /**
31
+ * Callback interface for the stream controller to communicate with
32
+ * its host (the React hook). All mutations to external state go
33
+ * through these callbacks.
34
+ */
35
+ export interface StreamControllerSink {
36
+ /** Ingest a snapshot into the store (applies structural sharing). */
37
+ ingestSnapshot(snapshot: AgentExecution): void;
38
+ /** Transition the store's stream lifecycle state. */
39
+ setStreamState(state: ControllerState): void;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // StreamController
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /**
47
+ * Framework-agnostic finite state machine that manages the lifecycle
48
+ * of a single execution stream subscription.
49
+ *
50
+ * Responsibilities:
51
+ * - Track FSM state transitions (idle → connecting → streaming → complete/error)
52
+ * - Buffer incoming snapshots and coalesce via `requestAnimationFrame`
53
+ * - Flush terminal snapshots immediately (no rAF delay)
54
+ * - Provide abort/reconnect semantics
55
+ *
56
+ * This class has no React dependency. It communicates outward through
57
+ * a {@link StreamControllerSink} and a `scheduleFlush` function
58
+ * (typically `requestAnimationFrame`).
59
+ */
60
+ export class StreamController {
61
+ private _state: ControllerState = IDLE;
62
+ private _bufferedSnapshot: AgentExecution | null = null;
63
+ private _rafId: number | null = null;
64
+ private _sink: StreamControllerSink;
65
+ private _scheduleFlush: (cb: () => void) => number;
66
+ private _cancelFlush: (id: number) => void;
67
+
68
+ constructor(
69
+ sink: StreamControllerSink,
70
+ scheduleFlush: (cb: () => void) => number = typeof requestAnimationFrame !== "undefined"
71
+ ? requestAnimationFrame
72
+ : (cb) => setTimeout(cb, 16) as unknown as number,
73
+ cancelFlush: (id: number) => void = typeof cancelAnimationFrame !== "undefined"
74
+ ? cancelAnimationFrame
75
+ : clearTimeout,
76
+ ) {
77
+ this._sink = sink;
78
+ this._scheduleFlush = scheduleFlush;
79
+ this._cancelFlush = cancelFlush;
80
+ }
81
+
82
+ /** Current FSM state (read-only). */
83
+ get state(): ControllerState {
84
+ return this._state;
85
+ }
86
+
87
+ /**
88
+ * Transition to `connecting` for the given execution ID.
89
+ * If already active for a different ID, resets first.
90
+ */
91
+ start(executionId: string): void {
92
+ this._cancelPendingFlush();
93
+ this._bufferedSnapshot = null;
94
+ this._transition({ stage: "connecting", executionId });
95
+ }
96
+
97
+ /**
98
+ * Handle an incoming snapshot from the gRPC stream.
99
+ * Non-terminal snapshots are buffered for rAF coalescing.
100
+ * Terminal snapshots flush immediately.
101
+ */
102
+ handleSnapshot(snapshot: AgentExecution): void {
103
+ const executionId = this._activeExecutionId();
104
+ if (!executionId) return;
105
+
106
+ const phase =
107
+ snapshot.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
108
+ const terminal = isTerminalPhase(phase);
109
+
110
+ if (terminal) {
111
+ this._cancelPendingFlush();
112
+ this._bufferedSnapshot = null;
113
+ this._sink.ingestSnapshot(snapshot);
114
+ this._transition({ stage: "complete", executionId });
115
+ } else {
116
+ if (this._state.stage === "connecting") {
117
+ this._transition({ stage: "streaming", executionId });
118
+ }
119
+ this._bufferedSnapshot = snapshot;
120
+ this._scheduleFlushOnce();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Handle stream completion (iterator exhausted without error).
126
+ * If we still have a buffered snapshot, flush it first.
127
+ */
128
+ handleStreamEnd(): void {
129
+ const executionId = this._activeExecutionId();
130
+ if (!executionId) return;
131
+
132
+ this._flushBuffer();
133
+ if (this._state.stage !== "complete") {
134
+ this._transition({ stage: "complete", executionId });
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Handle a stream error. Flushes any buffered snapshot first
140
+ * so the UI shows the last known good state alongside the error.
141
+ */
142
+ handleError(error: Error): void {
143
+ const executionId = this._activeExecutionId();
144
+ if (!executionId) return;
145
+
146
+ this._cancelPendingFlush();
147
+ this._flushBuffer();
148
+ this._transition({ stage: "error", executionId, error });
149
+ }
150
+
151
+ /** Reset to idle. Cancels any pending flush. */
152
+ reset(): void {
153
+ this._cancelPendingFlush();
154
+ this._bufferedSnapshot = null;
155
+ if (this._state.stage !== "idle") {
156
+ this._transition(IDLE);
157
+ }
158
+ }
159
+
160
+ /** Whether the controller has a buffered snapshot awaiting flush. */
161
+ get hasPendingFlush(): boolean {
162
+ return this._rafId !== null;
163
+ }
164
+
165
+ // -------------------------------------------------------------------------
166
+ // Private
167
+ // -------------------------------------------------------------------------
168
+
169
+ private _activeExecutionId(): string | null {
170
+ if (this._state.stage === "idle") return null;
171
+ return this._state.executionId;
172
+ }
173
+
174
+ private _transition(next: ControllerState): void {
175
+ this._state = next;
176
+ this._sink.setStreamState(next);
177
+ }
178
+
179
+ private _scheduleFlushOnce(): void {
180
+ if (this._rafId !== null) return;
181
+ this._rafId = this._scheduleFlush(() => {
182
+ this._rafId = null;
183
+ this._flushBuffer();
184
+ });
185
+ }
186
+
187
+ private _flushBuffer(): void {
188
+ if (this._bufferedSnapshot) {
189
+ const snap = this._bufferedSnapshot;
190
+ this._bufferedSnapshot = null;
191
+ this._sink.ingestSnapshot(snap);
192
+ }
193
+ }
194
+
195
+ private _cancelPendingFlush(): void {
196
+ if (this._rafId !== null) {
197
+ this._cancelFlush(this._rafId);
198
+ this._rafId = null;
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+
5
+ /**
6
+ * Near-bottom tolerance in pixels. The sentinel is considered visible
7
+ * (and follow mode stays engaged) when the user is within this distance
8
+ * of the bottom. Matches the threshold used by the previous scroll
9
+ * implementation.
10
+ */
11
+ const NEAR_BOTTOM_MARGIN_PX = 80;
12
+
13
+ export interface UseAutoScrollReturn {
14
+ /** Attach to the scrollable container. */
15
+ readonly scrollRef: React.RefObject<HTMLDivElement | null>;
16
+ /** Attach to a zero-height div as the last child of the scroll container. */
17
+ readonly sentinelRef: React.RefObject<HTMLDivElement | null>;
18
+ /** Attach to a wrapper div around the thread content (ResizeObserver target). */
19
+ readonly contentRef: React.RefObject<HTMLDivElement | null>;
20
+ /** True when the thread auto-scrolls to follow new content. */
21
+ readonly isFollowing: boolean;
22
+ /** Scroll to the latest content and re-engage follow mode. */
23
+ readonly jumpToLatest: () => void;
24
+ }
25
+
26
+ /**
27
+ * Two-state auto-scroll state machine for chat threads.
28
+ *
29
+ * **Following** — content is pinned to the bottom; new content
30
+ * (streaming tokens, new messages, tool panel expansion) triggers a
31
+ * `requestAnimationFrame`-batched scroll-to-bottom.
32
+ *
33
+ * **Disengaged** — the user scrolled away from the bottom; no
34
+ * automatic scrolling occurs. Call {@link UseAutoScrollReturn.jumpToLatest}
35
+ * to re-engage.
36
+ *
37
+ * State transitions are driven by an `IntersectionObserver` on a
38
+ * bottom sentinel element. A `ResizeObserver` on the content wrapper
39
+ * detects height growth and triggers rAF-batched scroll writes when
40
+ * in the Following state.
41
+ *
42
+ * @internal Not part of the public API.
43
+ */
44
+ export function useAutoScroll(): UseAutoScrollReturn {
45
+ const scrollRef = useRef<HTMLDivElement | null>(null);
46
+ const sentinelRef = useRef<HTMLDivElement | null>(null);
47
+ const contentRef = useRef<HTMLDivElement | null>(null);
48
+
49
+ const [isFollowing, setIsFollowing] = useState(true);
50
+ const isFollowingRef = useRef(true);
51
+ const rafIdRef = useRef(0);
52
+
53
+ // Keep ref in sync with state so observer callbacks read the latest
54
+ // value without re-subscribing on every state change.
55
+ useEffect(() => {
56
+ isFollowingRef.current = isFollowing;
57
+ }, [isFollowing]);
58
+
59
+ // --- IntersectionObserver: sentinel visibility drives state ---
60
+ useEffect(() => {
61
+ const sentinel = sentinelRef.current;
62
+ const scroller = scrollRef.current;
63
+ if (!sentinel || !scroller) return;
64
+
65
+ // Establish initial position at the bottom so the first IO
66
+ // callback sees the sentinel as intersecting.
67
+ scroller.scrollTop = scroller.scrollHeight;
68
+
69
+ const io = new IntersectionObserver(
70
+ (entries) => {
71
+ const entry = entries[0];
72
+ if (!entry) return;
73
+ const visible = entry.isIntersecting;
74
+ isFollowingRef.current = visible;
75
+ setIsFollowing(visible);
76
+ },
77
+ {
78
+ root: scroller,
79
+ rootMargin: `0px 0px ${NEAR_BOTTOM_MARGIN_PX}px 0px`,
80
+ threshold: 0,
81
+ },
82
+ );
83
+
84
+ io.observe(sentinel);
85
+ return () => io.disconnect();
86
+ }, []);
87
+
88
+ // --- ResizeObserver: scroll on content height growth while following ---
89
+ useEffect(() => {
90
+ const content = contentRef.current;
91
+ if (!content || !scrollRef.current) return;
92
+
93
+ const ro = new ResizeObserver(() => {
94
+ if (!isFollowingRef.current) return;
95
+ cancelAnimationFrame(rafIdRef.current);
96
+ rafIdRef.current = requestAnimationFrame(() => {
97
+ const el = scrollRef.current;
98
+ if (el) el.scrollTop = el.scrollHeight;
99
+ });
100
+ });
101
+
102
+ ro.observe(content);
103
+
104
+ return () => {
105
+ ro.disconnect();
106
+ cancelAnimationFrame(rafIdRef.current);
107
+ };
108
+ }, []);
109
+
110
+ const jumpToLatest = useCallback(() => {
111
+ const el = scrollRef.current;
112
+ if (!el) return;
113
+ el.scrollTop = el.scrollHeight;
114
+ // Eagerly set following — IO callback will confirm when the
115
+ // sentinel becomes visible after the scroll.
116
+ isFollowingRef.current = true;
117
+ setIsFollowing(true);
118
+ }, []);
119
+
120
+ return { scrollRef, sentinelRef, contentRef, isFollowing, jumpToLatest };
121
+ }