@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
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useMemo, useRef } from "react";
3
+ import { lazy, memo, Suspense, useCallback, useMemo } from "react";
4
4
  import { create } from "@bufbuild/protobuf";
5
5
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
6
6
  import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
@@ -24,6 +24,16 @@ import { ApprovalCard } from "./ApprovalCard";
24
24
  import { FilePathContext, type FilePathContextValue } from "./FilePathContext";
25
25
  import type { ResolvedPathAction } from "./file-path-resolver";
26
26
  import { SandboxContext, type SandboxContextValue } from "./SandboxContext";
27
+ import { useRenderTracer, useKeyStability, useDomNodeCount, DevProfiler } from "../internal/dev";
28
+ import { useAutoScroll } from "../internal/useAutoScroll";
29
+ import { JumpToLatestButton } from "../internal/JumpToLatestButton";
30
+ import { ThreadItemWrapper } from "../internal/ThreadItemWrapper";
31
+
32
+ const LazyVirtualizedThread = lazy(() =>
33
+ import("../internal/VirtualizedThread").then((m) => ({
34
+ default: m.VirtualizedThread,
35
+ })),
36
+ );
27
37
 
28
38
  /** Props for {@link MessageThread}. */
29
39
  export interface MessageThreadProps {
@@ -93,22 +103,31 @@ export interface MessageThreadProps {
93
103
  * the user's own filesystem (no normalization needed).
94
104
  */
95
105
  readonly sandboxWorkspaceRoot?: string;
106
+ /**
107
+ * Enable virtualized rendering for long conversations. Requires
108
+ * `react-virtuoso` to be installed as a peer dependency. When
109
+ * enabled, only visible items are rendered in the DOM, improving
110
+ * performance for threads with 100+ items.
111
+ *
112
+ * @default false
113
+ */
114
+ readonly virtualized?: boolean;
96
115
  }
97
116
 
98
- const AUTO_SCROLL_THRESHOLD_PX = 80;
99
-
100
117
  /**
101
118
  * Flattened representation of one renderable item in the thread.
102
119
  *
103
120
  * Discriminated union keeps the render loop a simple switch with no
104
121
  * type narrowing gymnastics.
122
+ *
123
+ * @internal Exported for internal use by `VirtualizedThread` — not
124
+ * part of the public API.
105
125
  */
106
- type ThreadItem =
107
- | { readonly kind: "message"; readonly message: AgentMessage; readonly key: string }
126
+ export type ThreadItem =
127
+ | { readonly kind: "message"; readonly message: AgentMessage; readonly key: string; readonly isPending?: boolean }
108
128
  | { readonly kind: "tool-group"; readonly toolCalls: readonly ToolCall[]; readonly subAgentExecutions: readonly SubAgentExecution[]; readonly key: string }
109
129
  | { readonly kind: "sub-agent"; readonly subAgentExecution: SubAgentExecution; readonly key: string }
110
130
  | { readonly kind: "phase-badge"; readonly phase: ExecutionPhase; readonly key: string }
111
- | { readonly kind: "pending-message"; readonly content: string; readonly key: string }
112
131
  | { readonly kind: "approval-request"; readonly pendingApproval: PendingApproval; readonly key: string }
113
132
  | { readonly kind: "setup-progress"; readonly workspaceEntries: readonly WorkspaceEntry[]; readonly serverPhase?: string; readonly key: string };
114
133
 
@@ -121,7 +140,15 @@ function hasAiMessages(execution: AgentExecution): boolean {
121
140
  );
122
141
  }
123
142
 
124
- function buildThreadItems(
143
+ /**
144
+ * Builds a flat list of renderable thread items from execution data.
145
+ *
146
+ * Keys use stable execution IDs (not array indices) so React can
147
+ * reconcile items across renders without unnecessary remounts.
148
+ *
149
+ * @internal Exported for testing — not part of the public API.
150
+ */
151
+ export function buildThreadItems(
125
152
  executions: readonly AgentExecution[],
126
153
  activeStreamExecution: AgentExecution | null | undefined,
127
154
  pendingUserMessage: string | null | undefined,
@@ -132,9 +159,14 @@ function buildThreadItems(
132
159
  const allExecutions = activeStreamExecution
133
160
  ? [...executions, activeStreamExecution]
134
161
  : executions;
162
+ const activeStreamIndex = activeStreamExecution
163
+ ? allExecutions.length - 1
164
+ : -1;
135
165
 
136
166
  for (let ei = 0; ei < allExecutions.length; ei++) {
137
167
  const exec = allExecutions[ei];
168
+ const execId = exec.metadata?.id ?? `_e${ei}`;
169
+ const isActiveStreamExec = ei === activeStreamIndex;
138
170
  const messages = exec.status?.messages ?? [];
139
171
  const subAgents = exec.status?.subAgentExecutions ?? [];
140
172
 
@@ -143,10 +175,19 @@ function buildThreadItems(
143
175
  const syntheticHumanMsg = create(AgentMessageSchema);
144
176
  syntheticHumanMsg.type = MessageType.MESSAGE_HUMAN;
145
177
  syntheticHumanMsg.content = specMessage;
178
+
179
+ // When the active stream execution's spec message matches the
180
+ // pending user message, use a shared bridging key so React
181
+ // updates the pending bubble in place instead of remounting.
182
+ const bridgePending =
183
+ isActiveStreamExec &&
184
+ pendingUserMessage != null &&
185
+ specMessage === pendingUserMessage;
186
+
146
187
  items.push({
147
188
  kind: "message",
148
189
  message: syntheticHumanMsg,
149
- key: `e${ei}-spec-msg`,
190
+ key: bridgePending ? "pending-user-turn" : `${execId}-spec`,
150
191
  });
151
192
  }
152
193
 
@@ -164,7 +205,7 @@ function buildThreadItems(
164
205
  items.push({
165
206
  kind: "message",
166
207
  message: msg,
167
- key: `e${ei}-m${mi}`,
208
+ key: `${execId}-m${mi}`,
168
209
  });
169
210
  }
170
211
 
@@ -172,34 +213,41 @@ function buildThreadItems(
172
213
  msg.type === MessageType.MESSAGE_AI &&
173
214
  msg.toolCalls.length > 0
174
215
  ) {
175
- const regularTools: ToolCall[] = [];
176
- const taskTools: ToolCall[] = [];
177
- for (const tc of msg.toolCalls) {
178
- if (tc.name === "task") {
179
- taskTools.push(tc);
180
- } else {
181
- regularTools.push(tc);
182
- }
183
- }
184
-
185
- if (regularTools.length > 0) {
186
- items.push({
187
- kind: "tool-group",
188
- toolCalls: regularTools,
189
- subAgentExecutions: subAgents,
190
- key: `e${ei}-m${mi}-tc`,
191
- });
192
- }
216
+ const hasTaskTools = msg.toolCalls.some((tc) => tc.name === "task");
193
217
 
194
- for (let ti = 0; ti < taskTools.length; ti++) {
195
- const matched = subAgents.find((sa) => sa.id === taskTools[ti].id);
196
- if (matched) {
218
+ if (hasTaskTools) {
219
+ const regularTools: ToolCall[] = [];
220
+ const matchedSubAgents: SubAgentExecution[] = [];
221
+ for (const tc of msg.toolCalls) {
222
+ if (tc.name === "task") {
223
+ const matched = subAgents.find((sa) => sa.id === tc.id);
224
+ if (matched) matchedSubAgents.push(matched);
225
+ } else {
226
+ regularTools.push(tc);
227
+ }
228
+ }
229
+ if (regularTools.length > 0) {
230
+ items.push({
231
+ kind: "tool-group",
232
+ toolCalls: regularTools,
233
+ subAgentExecutions: subAgents,
234
+ key: `${execId}-m${mi}-tc`,
235
+ });
236
+ }
237
+ for (const sa of matchedSubAgents) {
197
238
  items.push({
198
239
  kind: "sub-agent",
199
- subAgentExecution: matched,
200
- key: `e${ei}-m${mi}-sa${ti}`,
240
+ subAgentExecution: sa,
241
+ key: `sa-${sa.id}`,
201
242
  });
202
243
  }
244
+ } else {
245
+ items.push({
246
+ kind: "tool-group",
247
+ toolCalls: msg.toolCalls,
248
+ subAgentExecutions: subAgents,
249
+ key: `${execId}-m${mi}-tc`,
250
+ });
203
251
  }
204
252
  }
205
253
  }
@@ -252,10 +300,14 @@ function buildThreadItems(
252
300
  const alreadySynthesized =
253
301
  lastExec?.spec?.message === pendingUserMessage;
254
302
  if (!alreadySynthesized) {
303
+ const syntheticPending = create(AgentMessageSchema);
304
+ syntheticPending.type = MessageType.MESSAGE_HUMAN;
305
+ syntheticPending.content = pendingUserMessage;
255
306
  items.push({
256
- kind: "pending-message",
257
- content: pendingUserMessage,
258
- key: "pending-user-message",
307
+ kind: "message",
308
+ message: syntheticPending,
309
+ key: "pending-user-turn",
310
+ isPending: true,
259
311
  });
260
312
  }
261
313
  }
@@ -297,9 +349,9 @@ export function MessageThread({
297
349
  workspaceEntries,
298
350
  onFilePathClick,
299
351
  sandboxWorkspaceRoot,
352
+ virtualized = false,
300
353
  }: MessageThreadProps) {
301
- const scrollRef = useRef<HTMLDivElement>(null);
302
- const isNearBottomRef = useRef(true);
354
+ useRenderTracer("MessageThread", { executions, activeStreamExecution });
303
355
 
304
356
  const includeApprovals = onApprovalSubmit != null;
305
357
  const items = useMemo(
@@ -307,19 +359,7 @@ export function MessageThread({
307
359
  [executions, activeStreamExecution, pendingUserMessage, includeApprovals, workspaceEntries],
308
360
  );
309
361
 
310
- const handleScroll = useCallback(() => {
311
- const el = scrollRef.current;
312
- if (!el) return;
313
- isNearBottomRef.current =
314
- el.scrollHeight - el.scrollTop - el.clientHeight < AUTO_SCROLL_THRESHOLD_PX;
315
- }, []);
316
-
317
- useEffect(() => {
318
- if (!isNearBottomRef.current) return;
319
- const el = scrollRef.current;
320
- if (!el) return;
321
- el.scrollTop = el.scrollHeight;
322
- }, [items]);
362
+ useKeyStability(items);
323
363
 
324
364
  const filePathCtx = useMemo<FilePathContextValue>(
325
365
  () => ({
@@ -334,89 +374,226 @@ export function MessageThread({
334
374
  [sandboxWorkspaceRoot],
335
375
  );
336
376
 
377
+ if (virtualized) {
378
+ return (
379
+ <div className={cn("relative min-h-0", className)}>
380
+ <Suspense fallback={null}>
381
+ <LazyVirtualizedThread
382
+ items={items}
383
+ formatToolCallSummary={formatToolCallSummary}
384
+ onApprovalSubmit={onApprovalSubmit}
385
+ submittingApprovalIds={submittingApprovalIds}
386
+ filePathCtx={filePathCtx}
387
+ sandboxCtx={sandboxCtx}
388
+ />
389
+ </Suspense>
390
+ </div>
391
+ );
392
+ }
393
+
337
394
  return (
338
- <div
339
- ref={scrollRef}
340
- role="log"
341
- aria-live="polite"
342
- aria-relevant="additions"
343
- onScroll={handleScroll}
344
- className={cn(
345
- "flex flex-col gap-4 overflow-y-auto pt-6 pb-4",
346
- "[scrollbar-width:thin] [scrollbar-color:var(--color-border)_transparent]",
347
- "[&::-webkit-scrollbar]:w-1.5",
348
- "[&::-webkit-scrollbar-track]:bg-transparent",
349
- "[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/40",
350
- className,
351
- )}
352
- >
353
- <SandboxContext.Provider value={sandboxCtx}>
354
- <FilePathContext.Provider value={filePathCtx}>
355
- {items.map((item) => {
356
- switch (item.kind) {
357
- case "message":
358
- return <MessageEntry key={item.key} message={item.message} />;
359
- case "tool-group":
360
- return (
361
- <ToolCallGroup
362
- key={item.key}
363
- toolCalls={item.toolCalls}
364
- subAgentExecutions={item.subAgentExecutions}
365
- formatSummary={formatToolCallSummary}
366
- className="mx-4"
367
- />
368
- );
369
- case "sub-agent":
370
- return (
371
- <SubAgentSection
372
- key={item.key}
373
- subAgentExecution={item.subAgentExecution}
374
- className="mx-4"
375
- />
376
- );
377
- case "phase-badge":
378
- return (
379
- <div key={item.key} className="flex justify-center py-3">
380
- <ExecutionPhaseBadge phase={item.phase} />
381
- </div>
382
- );
383
- case "approval-request":
384
- return (
385
- <ApprovalCard
386
- key={item.key}
387
- pendingApproval={item.pendingApproval}
388
- onSubmit={(action, comment) =>
389
- onApprovalSubmit!(item.pendingApproval.toolCallId, action, comment)
390
- }
391
- isSubmitting={submittingApprovalIds?.has(item.pendingApproval.toolCallId) ?? false}
392
- className="mx-4"
393
- />
394
- );
395
- case "setup-progress":
396
- return (
397
- <SetupProgress
398
- key={item.key}
399
- workspaceEntries={item.workspaceEntries}
400
- serverPhase={item.serverPhase}
395
+ <NonVirtualizedThread
396
+ items={items}
397
+ className={className}
398
+ formatToolCallSummary={formatToolCallSummary}
399
+ onApprovalSubmit={onApprovalSubmit}
400
+ submittingApprovalIds={submittingApprovalIds}
401
+ filePathCtx={filePathCtx}
402
+ sandboxCtx={sandboxCtx}
403
+ />
404
+ );
405
+ }
406
+
407
+ // ---------------------------------------------------------------------------
408
+ // NonVirtualizedThread — original scroll-container rendering path
409
+ // ---------------------------------------------------------------------------
410
+
411
+ interface NonVirtualizedThreadProps {
412
+ readonly items: readonly ThreadItem[];
413
+ readonly className?: string;
414
+ readonly formatToolCallSummary?: (toolCalls: readonly ToolCall[]) => string;
415
+ readonly onApprovalSubmit?: (
416
+ toolCallId: string,
417
+ action: ApprovalAction,
418
+ comment?: string,
419
+ ) => void;
420
+ readonly submittingApprovalIds?: ReadonlySet<string>;
421
+ readonly filePathCtx: FilePathContextValue;
422
+ readonly sandboxCtx: SandboxContextValue;
423
+ }
424
+
425
+ function NonVirtualizedThread({
426
+ items,
427
+ className,
428
+ formatToolCallSummary,
429
+ onApprovalSubmit,
430
+ submittingApprovalIds,
431
+ filePathCtx,
432
+ sandboxCtx,
433
+ }: NonVirtualizedThreadProps) {
434
+ const { scrollRef, sentinelRef, contentRef, isFollowing, jumpToLatest } =
435
+ useAutoScroll();
436
+
437
+ useDomNodeCount(scrollRef, "MessageThread");
438
+
439
+ return (
440
+ <div className={cn("relative min-h-0", className)}>
441
+ <div
442
+ ref={scrollRef}
443
+ role="log"
444
+ aria-live="polite"
445
+ aria-relevant="additions"
446
+ className={cn(
447
+ "h-full overflow-y-auto pt-6 pb-4 [overflow-anchor:none]",
448
+ "[scrollbar-width:thin] [scrollbar-color:var(--color-border)_transparent]",
449
+ "[&::-webkit-scrollbar]:w-1.5",
450
+ "[&::-webkit-scrollbar-track]:bg-transparent",
451
+ "[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/40",
452
+ )}
453
+ >
454
+ <SandboxContext.Provider value={sandboxCtx}>
455
+ <FilePathContext.Provider value={filePathCtx}>
456
+ <DevProfiler id="MessageThread">
457
+ <div ref={contentRef} className="flex flex-col gap-4">
458
+ {items.map((item) => (
459
+ <ThreadItemWrapper key={item.key} animate>
460
+ <ThreadItemRenderer
461
+ item={item}
462
+ formatToolCallSummary={formatToolCallSummary}
463
+ onApprovalSubmit={onApprovalSubmit}
464
+ submittingApprovalIds={submittingApprovalIds}
401
465
  />
402
- );
403
- case "pending-message":
404
- return (
405
- <div
406
- key={item.key}
407
- role="article"
408
- aria-label="Sending message"
409
- className="ms-[20%] rounded-lg bg-muted-subtle px-4 py-3 opacity-70"
410
- >
411
- <p className="text-sm text-foreground whitespace-pre-wrap">
412
- {item.content}
413
- </p>
414
- </div>
415
- );
416
- }
417
- })}
418
- </FilePathContext.Provider>
419
- </SandboxContext.Provider>
466
+ </ThreadItemWrapper>
467
+ ))}
468
+ </div>
469
+ </DevProfiler>
470
+ </FilePathContext.Provider>
471
+ </SandboxContext.Provider>
472
+ <div ref={sentinelRef} aria-hidden="true" />
473
+ </div>
474
+ <JumpToLatestButton onClick={jumpToLatest} visible={!isFollowing} />
420
475
  </div>
421
476
  );
422
477
  }
478
+
479
+ // ---------------------------------------------------------------------------
480
+ // ThreadItemRenderer — renders a single ThreadItem by kind
481
+ // ---------------------------------------------------------------------------
482
+
483
+ /**
484
+ * Props for {@link ThreadItemRenderer}.
485
+ *
486
+ * @internal Exported for internal use by `VirtualizedThread` — not
487
+ * part of the public API.
488
+ */
489
+ export interface ThreadItemRendererProps {
490
+ readonly item: ThreadItem;
491
+ readonly formatToolCallSummary?: (toolCalls: readonly ToolCall[]) => string;
492
+ readonly onApprovalSubmit?: (
493
+ toolCallId: string,
494
+ action: ApprovalAction,
495
+ comment?: string,
496
+ ) => void;
497
+ readonly submittingApprovalIds?: ReadonlySet<string>;
498
+ }
499
+
500
+ /**
501
+ * Renders a single thread item by discriminated `kind`. Used by both
502
+ * the non-virtualized `items.map()` path and the virtualized
503
+ * `Virtuoso.itemContent` callback.
504
+ *
505
+ * Does not receive a `key` prop — the caller is responsible for
506
+ * keying (either via `items.map` or `computeItemKey`).
507
+ *
508
+ * @internal Exported for internal use by `VirtualizedThread` — not
509
+ * part of the public API.
510
+ */
511
+ export function ThreadItemRenderer({
512
+ item,
513
+ formatToolCallSummary,
514
+ onApprovalSubmit,
515
+ submittingApprovalIds,
516
+ }: ThreadItemRendererProps) {
517
+ switch (item.kind) {
518
+ case "message":
519
+ return (
520
+ <MessageEntry
521
+ message={item.message}
522
+ className={item.isPending ? "opacity-70" : undefined}
523
+ />
524
+ );
525
+ case "tool-group":
526
+ return (
527
+ <ToolCallGroup
528
+ toolCalls={item.toolCalls}
529
+ subAgentExecutions={item.subAgentExecutions}
530
+ formatSummary={formatToolCallSummary}
531
+ className="mx-4"
532
+ />
533
+ );
534
+ case "sub-agent":
535
+ return (
536
+ <SubAgentSection
537
+ subAgentExecution={item.subAgentExecution}
538
+ className="mx-4"
539
+ />
540
+ );
541
+ case "phase-badge":
542
+ return (
543
+ <div className="flex justify-center py-3">
544
+ <ExecutionPhaseBadge phase={item.phase} />
545
+ </div>
546
+ );
547
+ case "approval-request":
548
+ return (
549
+ <ApprovalCardRow
550
+ pendingApproval={item.pendingApproval}
551
+ onApprovalSubmit={onApprovalSubmit!}
552
+ isSubmitting={submittingApprovalIds?.has(item.pendingApproval.toolCallId) ?? false}
553
+ />
554
+ );
555
+ case "setup-progress":
556
+ return (
557
+ <SetupProgress
558
+ workspaceEntries={item.workspaceEntries}
559
+ serverPhase={item.serverPhase}
560
+ />
561
+ );
562
+ }
563
+ }
564
+
565
+ // ---------------------------------------------------------------------------
566
+ // ApprovalCardRow — stabilizes the onSubmit callback for React.memo
567
+ // ---------------------------------------------------------------------------
568
+
569
+ interface ApprovalCardRowProps {
570
+ readonly pendingApproval: PendingApproval;
571
+ readonly onApprovalSubmit: (
572
+ toolCallId: string,
573
+ action: ApprovalAction,
574
+ comment?: string,
575
+ ) => void;
576
+ readonly isSubmitting: boolean;
577
+ }
578
+
579
+ const ApprovalCardRow = memo(function ApprovalCardRow({
580
+ pendingApproval,
581
+ onApprovalSubmit,
582
+ isSubmitting,
583
+ }: ApprovalCardRowProps) {
584
+ const handleSubmit = useCallback(
585
+ (action: ApprovalAction, comment?: string) => {
586
+ onApprovalSubmit(pendingApproval.toolCallId, action, comment);
587
+ },
588
+ [onApprovalSubmit, pendingApproval.toolCallId],
589
+ );
590
+
591
+ return (
592
+ <ApprovalCard
593
+ pendingApproval={pendingApproval}
594
+ onSubmit={handleSubmit}
595
+ isSubmitting={isSubmitting}
596
+ className="mx-4"
597
+ />
598
+ );
599
+ });
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useMemo, useState } from "react";
3
+ import { memo, useEffect, useMemo, useState } from "react";
4
4
  import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
5
5
  import { cn } from "@stigmer/theme";
6
6
 
@@ -87,7 +87,7 @@ function buildSteps(
87
87
  * <SetupProgress workspaceEntries={session.spec?.workspaceEntries} />
88
88
  * ```
89
89
  */
90
- export function SetupProgress({
90
+ export const SetupProgress = memo(function SetupProgress({
91
91
  workspaceEntries,
92
92
  serverPhase,
93
93
  className,
@@ -129,7 +129,7 @@ export function SetupProgress({
129
129
  </span>
130
130
  </div>
131
131
  );
132
- }
132
+ });
133
133
 
134
134
  function PulseIndicator() {
135
135
  return (
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useMemo, useState } from "react";
3
+ import { memo, useMemo, useState } from "react";
4
4
  import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
5
5
  import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
6
6
  import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
@@ -8,6 +8,7 @@ import {
8
8
  MessageType,
9
9
  SubAgentStatus,
10
10
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
11
+ import { useRenderTracer } from "../internal/dev";
11
12
  import { cn } from "@stigmer/theme";
12
13
  import { formatDuration } from "./ToolCallDetail";
13
14
  import { MessageEntry } from "./MessageEntry";
@@ -55,6 +56,10 @@ export interface SubAgentSectionProps {
55
56
  * the sub-agent's internal messages and tool calls — the same
56
57
  * building blocks used by the top-level {@link MessageThread}.
57
58
  *
59
+ * Wrapped in `React.memo` — structural sharing (T04) preserves the
60
+ * `SubAgentExecution` reference when the sub-agent's state is
61
+ * unchanged, so completed sub-agents skip re-renders entirely.
62
+ *
58
63
  * @example
59
64
  * ```tsx
60
65
  * // Standalone collapsible card (default)
@@ -64,16 +69,18 @@ export interface SubAgentSectionProps {
64
69
  * <SubAgentSection subAgentExecution={sub} collapsible={false} />
65
70
  * ```
66
71
  */
67
- export function SubAgentSection({
72
+ export const SubAgentSection = memo(function SubAgentSection({
68
73
  subAgentExecution: sub,
69
74
  collapsible = true,
70
75
  className,
71
76
  }: SubAgentSectionProps) {
77
+ useRenderTracer("SubAgentSection", { status: sub.status, name: sub.name });
78
+
72
79
  const duration = formatDuration(sub.startedAt, sub.completedAt);
73
80
  const statusInfo = SUB_AGENT_STATUS_MAP[sub.status];
74
81
  const StatusIcon = statusInfo.icon;
75
82
  const isFailed = sub.status === SubAgentStatus.SUB_AGENT_FAILED;
76
- const threadItems = buildSubAgentThreadItems(sub.messages);
83
+ const threadItems = buildSubAgentThreadItems(sub.id, sub.messages);
77
84
 
78
85
  const displayLabel = sub.subject || sub.name;
79
86
 
@@ -102,7 +109,7 @@ export function SubAgentSection({
102
109
  className={className}
103
110
  />
104
111
  );
105
- }
112
+ });
106
113
 
107
114
  // ---------------------------------------------------------------------------
108
115
  // Collapsible card — progressive disclosure (default mode)
@@ -367,6 +374,7 @@ type SubAgentThreadItem =
367
374
  | { readonly kind: "tool-group"; readonly toolCalls: readonly ToolCall[]; readonly key: string };
368
375
 
369
376
  function buildSubAgentThreadItems(
377
+ subAgentId: string,
370
378
  messages: readonly AgentMessage[],
371
379
  ): SubAgentThreadItem[] {
372
380
  const items: SubAgentThreadItem[] = [];
@@ -376,13 +384,13 @@ function buildSubAgentThreadItems(
376
384
 
377
385
  if (msg.type === MessageType.MESSAGE_TOOL) continue;
378
386
 
379
- items.push({ kind: "message", message: msg, key: `sa-m${i}` });
387
+ items.push({ kind: "message", message: msg, key: `${subAgentId}-m${i}` });
380
388
 
381
389
  if (msg.type === MessageType.MESSAGE_AI && msg.toolCalls.length > 0) {
382
390
  items.push({
383
391
  kind: "tool-group",
384
392
  toolCalls: msg.toolCalls,
385
- key: `sa-m${i}-tc`,
393
+ key: `${subAgentId}-m${i}-tc`,
386
394
  });
387
395
  }
388
396
  }