@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,320 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { create } from "@bufbuild/protobuf";
3
+ import {
4
+ AgentExecutionSchema,
5
+ AgentExecutionStatusSchema,
6
+ type AgentExecution,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
8
+ import { AgentExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
9
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
10
+ import {
11
+ AgentMessageSchema,
12
+ ToolCallSchema,
13
+ type ToolCall,
14
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
15
+ import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
16
+ import {
17
+ ExecutionPhase,
18
+ MessageType,
19
+ ToolCallStatus,
20
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
21
+ import { toolCallGroupPropsEqual, type ToolCallGroupProps } from "../ToolCallGroup";
22
+ import { buildThreadItems } from "../MessageThread";
23
+ import { structuralShare } from "../../internal/store";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ function makeMessage(
30
+ type: MessageType,
31
+ content: string,
32
+ opts?: { toolCalls?: ToolCall[]; isStreaming?: boolean },
33
+ ) {
34
+ const msg = create(AgentMessageSchema);
35
+ msg.type = type;
36
+ msg.content = content;
37
+ if (opts?.toolCalls) msg.toolCalls = opts.toolCalls;
38
+ if (opts?.isStreaming) msg.isStreaming = true;
39
+ return msg;
40
+ }
41
+
42
+ function makeToolCall(name: string, id: string) {
43
+ const tc = create(ToolCallSchema);
44
+ tc.id = id;
45
+ tc.name = name;
46
+ tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
47
+ return tc;
48
+ }
49
+
50
+ function makeSubAgent(id: string) {
51
+ const sa = create(SubAgentExecutionSchema);
52
+ sa.id = id;
53
+ sa.name = "test-agent";
54
+ return sa;
55
+ }
56
+
57
+ function makeExecution(opts: {
58
+ id: string;
59
+ specMessage?: string;
60
+ phase?: ExecutionPhase;
61
+ messages?: ReturnType<typeof makeMessage>[];
62
+ subAgents?: ReturnType<typeof makeSubAgent>[];
63
+ }): AgentExecution {
64
+ const exec = create(AgentExecutionSchema);
65
+ const meta = create(ApiResourceMetadataSchema);
66
+ meta.id = opts.id;
67
+ exec.metadata = meta;
68
+ if (opts.specMessage) {
69
+ const spec = create(AgentExecutionSpecSchema);
70
+ spec.message = opts.specMessage;
71
+ exec.spec = spec;
72
+ }
73
+ const status = create(AgentExecutionStatusSchema);
74
+ status.phase = opts.phase ?? ExecutionPhase.EXECUTION_COMPLETED;
75
+ if (opts.messages) status.messages = opts.messages;
76
+ if (opts.subAgents) status.subAgentExecutions = opts.subAgents;
77
+ exec.status = status;
78
+ return exec;
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // toolCallGroupPropsEqual
83
+ // ---------------------------------------------------------------------------
84
+
85
+ describe("toolCallGroupPropsEqual", () => {
86
+ it("returns true when both arrays share the same ToolCall references", () => {
87
+ const tc1 = makeToolCall("shell", "tc-1");
88
+ const tc2 = makeToolCall("read_file", "tc-2");
89
+ const prev: ToolCallGroupProps = { toolCalls: [tc1, tc2] };
90
+ const next: ToolCallGroupProps = { toolCalls: [tc1, tc2] };
91
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(true);
92
+ });
93
+
94
+ it("returns false when array lengths differ", () => {
95
+ const tc1 = makeToolCall("shell", "tc-1");
96
+ const prev: ToolCallGroupProps = { toolCalls: [tc1] };
97
+ const next: ToolCallGroupProps = { toolCalls: [tc1, makeToolCall("read_file", "tc-2")] };
98
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(false);
99
+ });
100
+
101
+ it("returns false when a ToolCall reference changes", () => {
102
+ const tc1 = makeToolCall("shell", "tc-1");
103
+ const tc1Copy = makeToolCall("shell", "tc-1");
104
+ const prev: ToolCallGroupProps = { toolCalls: [tc1] };
105
+ const next: ToolCallGroupProps = { toolCalls: [tc1Copy] };
106
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(false);
107
+ });
108
+
109
+ it("returns false when className changes", () => {
110
+ const tc1 = makeToolCall("shell", "tc-1");
111
+ const prev: ToolCallGroupProps = { toolCalls: [tc1], className: "mx-4" };
112
+ const next: ToolCallGroupProps = { toolCalls: [tc1], className: "mx-6" };
113
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(false);
114
+ });
115
+
116
+ it("returns false when subAgentExecutions reference changes", () => {
117
+ const tc1 = makeToolCall("shell", "tc-1");
118
+ const subs1 = [makeSubAgent("sa-1")];
119
+ const subs2 = [makeSubAgent("sa-1")];
120
+ const prev: ToolCallGroupProps = { toolCalls: [tc1], subAgentExecutions: subs1 };
121
+ const next: ToolCallGroupProps = { toolCalls: [tc1], subAgentExecutions: subs2 };
122
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(false);
123
+ });
124
+
125
+ it("returns true when subAgentExecutions is the same reference", () => {
126
+ const tc1 = makeToolCall("shell", "tc-1");
127
+ const subs = [makeSubAgent("sa-1")];
128
+ const prev: ToolCallGroupProps = { toolCalls: [tc1], subAgentExecutions: subs };
129
+ const next: ToolCallGroupProps = { toolCalls: [tc1], subAgentExecutions: subs };
130
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(true);
131
+ });
132
+
133
+ it("returns false when formatSummary reference changes", () => {
134
+ const tc1 = makeToolCall("shell", "tc-1");
135
+ const fn1 = () => "summary";
136
+ const fn2 = () => "summary";
137
+ const prev: ToolCallGroupProps = { toolCalls: [tc1], formatSummary: fn1 };
138
+ const next: ToolCallGroupProps = { toolCalls: [tc1], formatSummary: fn2 };
139
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(false);
140
+ });
141
+
142
+ it("handles empty arrays", () => {
143
+ const prev: ToolCallGroupProps = { toolCalls: [] };
144
+ const next: ToolCallGroupProps = { toolCalls: [] };
145
+ expect(toolCallGroupPropsEqual(prev, next)).toBe(true);
146
+ });
147
+ });
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // buildThreadItems — toolCalls array reuse
151
+ // ---------------------------------------------------------------------------
152
+
153
+ describe("buildThreadItems toolCalls array reuse", () => {
154
+ it("reuses msg.toolCalls reference when no task tools are present", () => {
155
+ const tc1 = makeToolCall("shell", "tc-1");
156
+ const tc2 = makeToolCall("read_file", "tc-2");
157
+ const aiMsg = makeMessage(MessageType.MESSAGE_AI, "using tools", {
158
+ toolCalls: [tc1, tc2],
159
+ });
160
+
161
+ const exec = makeExecution({ id: "exec-1", messages: [aiMsg] });
162
+ const items = buildThreadItems([exec], null, null, false, undefined);
163
+
164
+ const toolGroup = items.find((i) => i.kind === "tool-group");
165
+ expect(toolGroup).toBeDefined();
166
+ if (toolGroup?.kind === "tool-group") {
167
+ expect(toolGroup.toolCalls).toBe(aiMsg.toolCalls);
168
+ }
169
+ });
170
+
171
+ it("creates a new array when task tools are present", () => {
172
+ const tc1 = makeToolCall("shell", "tc-1");
173
+ const tc2 = makeToolCall("task", "sa-1");
174
+ const sa = makeSubAgent("sa-1");
175
+ const aiMsg = makeMessage(MessageType.MESSAGE_AI, "delegating", {
176
+ toolCalls: [tc1, tc2],
177
+ });
178
+
179
+ const exec = makeExecution({
180
+ id: "exec-1",
181
+ messages: [aiMsg],
182
+ subAgents: [sa],
183
+ });
184
+ const items = buildThreadItems([exec], null, null, false, undefined);
185
+
186
+ const toolGroup = items.find((i) => i.kind === "tool-group");
187
+ expect(toolGroup).toBeDefined();
188
+ if (toolGroup?.kind === "tool-group") {
189
+ expect(toolGroup.toolCalls).not.toBe(aiMsg.toolCalls);
190
+ expect(toolGroup.toolCalls).toHaveLength(1);
191
+ expect(toolGroup.toolCalls[0]).toBe(tc1);
192
+ }
193
+
194
+ const subAgentItem = items.find((i) => i.kind === "sub-agent");
195
+ expect(subAgentItem).toBeDefined();
196
+ });
197
+ });
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Structural sharing + memoization integration
201
+ // ---------------------------------------------------------------------------
202
+
203
+ describe("structural sharing preserves references for memo boundaries", () => {
204
+ it("completed messages keep the same reference across stream updates", () => {
205
+ const humanMsg = makeMessage(MessageType.MESSAGE_HUMAN, "Hello");
206
+ const completedAi = makeMessage(MessageType.MESSAGE_AI, "Full response");
207
+
208
+ const prev = makeExecution({
209
+ id: "exec-1",
210
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
211
+ messages: [
212
+ humanMsg,
213
+ completedAi,
214
+ makeMessage(MessageType.MESSAGE_AI, "streaming par", { isStreaming: true }),
215
+ ],
216
+ });
217
+
218
+ const next = makeExecution({
219
+ id: "exec-1",
220
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
221
+ messages: [
222
+ makeMessage(MessageType.MESSAGE_HUMAN, "Hello"),
223
+ makeMessage(MessageType.MESSAGE_AI, "Full response"),
224
+ makeMessage(MessageType.MESSAGE_AI, "streaming partial text grow", {
225
+ isStreaming: true,
226
+ }),
227
+ ],
228
+ });
229
+
230
+ const shared = structuralShare(prev, next);
231
+
232
+ expect(shared.status!.messages[0]).toBe(humanMsg);
233
+ expect(shared.status!.messages[1]).toBe(completedAi);
234
+ expect(shared.status!.messages[2]).not.toBe(prev.status!.messages[2]);
235
+ });
236
+
237
+ it("toolCalls within completed messages keep their references", () => {
238
+ const tc1 = makeToolCall("shell", "tc-1");
239
+ const tc2 = makeToolCall("read_file", "tc-2");
240
+ const aiMsg = makeMessage(MessageType.MESSAGE_AI, "tools done", {
241
+ toolCalls: [tc1, tc2],
242
+ });
243
+
244
+ const prev = makeExecution({
245
+ id: "exec-1",
246
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
247
+ messages: [
248
+ aiMsg,
249
+ makeMessage(MessageType.MESSAGE_AI, "now streaming", { isStreaming: true }),
250
+ ],
251
+ });
252
+
253
+ const next = makeExecution({
254
+ id: "exec-1",
255
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
256
+ messages: [
257
+ makeMessage(MessageType.MESSAGE_AI, "tools done", {
258
+ toolCalls: [
259
+ makeToolCall("shell", "tc-1"),
260
+ makeToolCall("read_file", "tc-2"),
261
+ ],
262
+ }),
263
+ makeMessage(MessageType.MESSAGE_AI, "now streaming more", {
264
+ isStreaming: true,
265
+ }),
266
+ ],
267
+ });
268
+
269
+ const shared = structuralShare(prev, next);
270
+
271
+ expect(shared.status!.messages[0]).toBe(aiMsg);
272
+ expect(shared.status!.messages[0].toolCalls[0]).toBe(tc1);
273
+ expect(shared.status!.messages[0].toolCalls[1]).toBe(tc2);
274
+ });
275
+
276
+ it("buildThreadItems with shared execution preserves tool group stability", () => {
277
+ const tc1 = makeToolCall("shell", "tc-1");
278
+ const completedMsg = makeMessage(MessageType.MESSAGE_AI, "used tools", {
279
+ toolCalls: [tc1],
280
+ });
281
+
282
+ const prev = makeExecution({
283
+ id: "exec-1",
284
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
285
+ messages: [
286
+ completedMsg,
287
+ makeMessage(MessageType.MESSAGE_AI, "stream", { isStreaming: true }),
288
+ ],
289
+ });
290
+
291
+ const next = makeExecution({
292
+ id: "exec-1",
293
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
294
+ messages: [
295
+ makeMessage(MessageType.MESSAGE_AI, "used tools", {
296
+ toolCalls: [makeToolCall("shell", "tc-1")],
297
+ }),
298
+ makeMessage(MessageType.MESSAGE_AI, "stream grows", {
299
+ isStreaming: true,
300
+ }),
301
+ ],
302
+ });
303
+
304
+ const shared = structuralShare(prev, next);
305
+
306
+ const itemsPrev = buildThreadItems([], prev, null, false, undefined);
307
+ const itemsNext = buildThreadItems([], shared, null, false, undefined);
308
+
309
+ const prevToolGroup = itemsPrev.find((i) => i.kind === "tool-group");
310
+ const nextToolGroup = itemsNext.find((i) => i.kind === "tool-group");
311
+
312
+ expect(prevToolGroup).toBeDefined();
313
+ expect(nextToolGroup).toBeDefined();
314
+
315
+ if (prevToolGroup?.kind === "tool-group" && nextToolGroup?.kind === "tool-group") {
316
+ expect(nextToolGroup.toolCalls).toBe(shared.status!.messages[0].toolCalls);
317
+ expect(nextToolGroup.toolCalls[0]).toBe(tc1);
318
+ }
319
+ });
320
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { ThreadSkeleton } from "../ThreadSkeleton";
4
+
5
+ describe("ThreadSkeleton", () => {
6
+ it("renders with aria-busy and loading label", () => {
7
+ const { container } = render(<ThreadSkeleton />);
8
+
9
+ const root = container.firstElementChild as HTMLElement;
10
+ expect(root.getAttribute("aria-busy")).toBe("true");
11
+ expect(root.getAttribute("aria-label")).toBe("Loading conversation");
12
+ });
13
+
14
+ it("renders human message bubble silhouettes", () => {
15
+ const { container } = render(<ThreadSkeleton />);
16
+
17
+ const humanBubbles = container.querySelectorAll("[class*='ms-']");
18
+ expect(humanBubbles.length).toBe(2);
19
+ });
20
+
21
+ it("renders AI response line silhouettes", () => {
22
+ const { container } = render(<ThreadSkeleton />);
23
+
24
+ const pulseContainer = container.querySelector(".animate-pulse");
25
+ expect(pulseContainer).toBeTruthy();
26
+
27
+ const lines = pulseContainer!.querySelectorAll("[style]");
28
+ expect(lines.length).toBe(7);
29
+ });
30
+
31
+ it("applies custom className", () => {
32
+ const { container } = render(<ThreadSkeleton className="my-custom-class" />);
33
+
34
+ const root = container.firstElementChild as HTMLElement;
35
+ expect(root.className).toContain("my-custom-class");
36
+ });
37
+
38
+ it("uses muted color tokens for skeleton bars", () => {
39
+ const { container } = render(<ThreadSkeleton />);
40
+
41
+ const bars = container.querySelectorAll("[class*='bg-muted']");
42
+ expect(bars.length).toBeGreaterThan(0);
43
+ });
44
+ });
@@ -12,6 +12,10 @@ import type { Stigmer } from "@stigmer/sdk";
12
12
  import { StigmerContext } from "../../context";
13
13
  import { useExecutionStream } from "../useExecutionStream";
14
14
 
15
+ // ---------------------------------------------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------------------------------------------
18
+
15
19
  /**
16
20
  * Creates a controllable async generator. Call `push(value)` to yield
17
21
  * the next value, `finish()` to end the stream, or `fail(err)` to
@@ -61,7 +65,9 @@ function makeSnapshot(phase: ExecutionPhase): AgentExecution {
61
65
  return exec;
62
66
  }
63
67
 
64
- function createMockStigmer(subscribeFn: Stigmer["agentExecution"]["subscribe"]) {
68
+ function createMockStigmer(
69
+ subscribeFn: Stigmer["agentExecution"]["subscribe"],
70
+ ) {
65
71
  return {
66
72
  agentExecution: { subscribe: subscribeFn },
67
73
  } as unknown as Stigmer;
@@ -70,11 +76,17 @@ function createMockStigmer(subscribeFn: Stigmer["agentExecution"]["subscribe"])
70
76
  function createWrapper(client: Stigmer) {
71
77
  return function Wrapper({ children }: { children: ReactNode }) {
72
78
  return (
73
- <StigmerContext.Provider value={client}>{children}</StigmerContext.Provider>
79
+ <StigmerContext.Provider value={client}>
80
+ {children}
81
+ </StigmerContext.Provider>
74
82
  );
75
83
  };
76
84
  }
77
85
 
86
+ // ---------------------------------------------------------------------------
87
+ // Tests
88
+ // ---------------------------------------------------------------------------
89
+
78
90
  describe("useExecutionStream", () => {
79
91
  let stream: ReturnType<typeof createControllableStream<AgentExecution>>;
80
92
  let subscribeFn: ReturnType<typeof vi.fn>;
@@ -98,12 +110,14 @@ describe("useExecutionStream", () => {
98
110
  expect(subscribeFn).not.toHaveBeenCalled();
99
111
  });
100
112
 
101
- it("starts in isConnecting state when given an ID", () => {
113
+ it("starts in isConnecting state when given an ID", async () => {
102
114
  const { result } = renderHook(() => useExecutionStream("exec-1"), {
103
115
  wrapper: createWrapper(mockStigmer),
104
116
  });
105
117
 
106
- expect(result.current.isConnecting).toBe(true);
118
+ await waitFor(() => {
119
+ expect(result.current.isConnecting).toBe(true);
120
+ });
107
121
  expect(result.current.isStreaming).toBe(false);
108
122
  expect(result.current.execution).toBeNull();
109
123
  });
@@ -129,18 +143,25 @@ describe("useExecutionStream", () => {
129
143
  wrapper: createWrapper(mockStigmer),
130
144
  });
131
145
 
132
- const snap1 = makeSnapshot(ExecutionPhase.EXECUTION_PENDING);
133
- act(() => stream.push(snap1));
146
+ act(() => {
147
+ stream.push(makeSnapshot(ExecutionPhase.EXECUTION_PENDING));
148
+ });
134
149
 
135
150
  await waitFor(() => {
136
- expect(result.current.execution).toBe(snap1);
151
+ expect(result.current.execution).not.toBeNull();
152
+ expect(result.current.execution?.status?.phase).toBe(
153
+ ExecutionPhase.EXECUTION_PENDING,
154
+ );
137
155
  });
138
156
 
139
- const snap2 = makeSnapshot(ExecutionPhase.EXECUTION_IN_PROGRESS);
140
- act(() => stream.push(snap2));
157
+ act(() => {
158
+ stream.push(makeSnapshot(ExecutionPhase.EXECUTION_IN_PROGRESS));
159
+ });
141
160
 
142
161
  await waitFor(() => {
143
- expect(result.current.execution).toBe(snap2);
162
+ expect(result.current.execution?.status?.phase).toBe(
163
+ ExecutionPhase.EXECUTION_IN_PROGRESS,
164
+ );
144
165
  });
145
166
  });
146
167
 
@@ -222,8 +243,10 @@ describe("useExecutionStream", () => {
222
243
 
223
244
  rerender({ id: "exec-2" });
224
245
 
225
- expect(result.current.isConnecting).toBe(true);
226
- expect(result.current.execution).toBeNull();
246
+ await waitFor(() => {
247
+ expect(result.current.isConnecting).toBe(true);
248
+ expect(result.current.execution).toBeNull();
249
+ });
227
250
  expect(subscribeFn).toHaveBeenCalledTimes(2);
228
251
  });
229
252
 
@@ -239,4 +262,78 @@ describe("useExecutionStream", () => {
239
262
 
240
263
  expect(signal.aborted).toBe(true);
241
264
  });
265
+
266
+ it("resets state when executionId becomes null", async () => {
267
+ const { result, rerender } = renderHook(
268
+ ({ id }: { id: string | null }) => useExecutionStream(id),
269
+ {
270
+ wrapper: createWrapper(mockStigmer),
271
+ initialProps: { id: "exec-1" as string | null },
272
+ },
273
+ );
274
+
275
+ act(() => {
276
+ stream.push(makeSnapshot(ExecutionPhase.EXECUTION_IN_PROGRESS));
277
+ });
278
+
279
+ await waitFor(() => {
280
+ expect(result.current.execution).not.toBeNull();
281
+ });
282
+
283
+ rerender({ id: null });
284
+
285
+ await waitFor(() => {
286
+ expect(result.current.execution).toBeNull();
287
+ expect(result.current.isConnecting).toBe(false);
288
+ expect(result.current.isStreaming).toBe(false);
289
+ });
290
+ });
291
+
292
+ it("derives phase from execution snapshot", async () => {
293
+ const { result } = renderHook(() => useExecutionStream("exec-1"), {
294
+ wrapper: createWrapper(mockStigmer),
295
+ });
296
+
297
+ expect(result.current.phase).toBe(
298
+ ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED,
299
+ );
300
+
301
+ act(() => {
302
+ stream.push(makeSnapshot(ExecutionPhase.EXECUTION_IN_PROGRESS));
303
+ });
304
+
305
+ await waitFor(() => {
306
+ expect(result.current.phase).toBe(
307
+ ExecutionPhase.EXECUTION_IN_PROGRESS,
308
+ );
309
+ });
310
+ });
311
+
312
+ it("handles all terminal phases correctly", async () => {
313
+ for (const terminalPhase of [
314
+ ExecutionPhase.EXECUTION_COMPLETED,
315
+ ExecutionPhase.EXECUTION_FAILED,
316
+ ExecutionPhase.EXECUTION_CANCELLED,
317
+ ExecutionPhase.EXECUTION_TERMINATED,
318
+ ]) {
319
+ const localStream = createControllableStream<AgentExecution>();
320
+ subscribeFn.mockReturnValue(localStream.generator);
321
+
322
+ const { result, unmount } = renderHook(
323
+ () => useExecutionStream("exec-terminal"),
324
+ { wrapper: createWrapper(mockStigmer) },
325
+ );
326
+
327
+ act(() => {
328
+ localStream.push(makeSnapshot(terminalPhase));
329
+ });
330
+
331
+ await waitFor(() => {
332
+ expect(result.current.isStreaming).toBe(false);
333
+ expect(result.current.phase).toBe(terminalPhase);
334
+ });
335
+
336
+ unmount();
337
+ }
338
+ });
242
339
  });
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { useSessionVariables } from "../useSessionVariables";
4
+
5
+ describe("useSessionVariables — return reference stability", () => {
6
+ it("returns the same object reference across re-renders when state is unchanged", () => {
7
+ const { result, rerender } = renderHook(() => useSessionVariables());
8
+
9
+ const first = result.current;
10
+ rerender();
11
+ const second = result.current;
12
+
13
+ expect(second).toBe(first);
14
+ });
15
+
16
+ it("returns a new reference after adding an entry", () => {
17
+ const { result } = renderHook(() => useSessionVariables());
18
+
19
+ const before = result.current;
20
+
21
+ act(() => {
22
+ result.current.addEntry();
23
+ });
24
+
25
+ expect(result.current).not.toBe(before);
26
+ expect(result.current.entries).toHaveLength(1);
27
+ expect(result.current.isEmpty).toBe(false);
28
+ });
29
+
30
+ it("stabilizes after mutation — re-renders without state change preserve reference", () => {
31
+ const { result, rerender } = renderHook(() => useSessionVariables());
32
+
33
+ act(() => {
34
+ result.current.addEntry();
35
+ });
36
+
37
+ const afterAdd = result.current;
38
+ rerender();
39
+
40
+ expect(result.current).toBe(afterAdd);
41
+ });
42
+
43
+ it("callback references are stable across re-renders", () => {
44
+ const { result, rerender } = renderHook(() => useSessionVariables());
45
+
46
+ const { addEntry, removeEntry, updateEntry, clear, toRuntimeEnv, toSaveForFutureEnv } =
47
+ result.current;
48
+
49
+ rerender();
50
+
51
+ expect(result.current.addEntry).toBe(addEntry);
52
+ expect(result.current.removeEntry).toBe(removeEntry);
53
+ expect(result.current.updateEntry).toBe(updateEntry);
54
+ expect(result.current.clear).toBe(clear);
55
+ expect(result.current.toRuntimeEnv).toBe(toRuntimeEnv);
56
+ expect(result.current.toSaveForFutureEnv).toBe(toSaveForFutureEnv);
57
+ });
58
+
59
+ it("returns a new reference after clearing", () => {
60
+ const { result } = renderHook(() => useSessionVariables());
61
+
62
+ act(() => {
63
+ result.current.addEntry();
64
+ });
65
+
66
+ const withEntry = result.current;
67
+
68
+ act(() => {
69
+ result.current.clear();
70
+ });
71
+
72
+ expect(result.current).not.toBe(withEntry);
73
+ expect(result.current.isEmpty).toBe(true);
74
+ });
75
+
76
+ it("derived booleans update correctly on state change", () => {
77
+ const { result } = renderHook(() => useSessionVariables());
78
+
79
+ expect(result.current.isEmpty).toBe(true);
80
+ expect(result.current.hasValidEntries).toBe(false);
81
+
82
+ act(() => {
83
+ result.current.addEntry();
84
+ });
85
+
86
+ const entryId = result.current.entries[0].id;
87
+
88
+ act(() => {
89
+ result.current.updateEntry(entryId, { key: "API_KEY", value: "secret123" });
90
+ });
91
+
92
+ expect(result.current.isEmpty).toBe(false);
93
+ expect(result.current.hasValidEntries).toBe(true);
94
+ });
95
+ });