@stigmer/react 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/billing/AutoRechargeCard.d.ts +38 -0
  2. package/billing/AutoRechargeCard.d.ts.map +1 -0
  3. package/billing/AutoRechargeCard.js +90 -0
  4. package/billing/AutoRechargeCard.js.map +1 -0
  5. package/billing/BillingSection.d.ts +32 -0
  6. package/billing/BillingSection.d.ts.map +1 -0
  7. package/billing/BillingSection.js +81 -0
  8. package/billing/BillingSection.js.map +1 -0
  9. package/billing/CreditBalanceCard.d.ts +25 -0
  10. package/billing/CreditBalanceCard.d.ts.map +1 -0
  11. package/billing/CreditBalanceCard.js +28 -0
  12. package/billing/CreditBalanceCard.js.map +1 -0
  13. package/billing/CreditLedgerTable.d.ts +22 -0
  14. package/billing/CreditLedgerTable.d.ts.map +1 -0
  15. package/billing/CreditLedgerTable.js +75 -0
  16. package/billing/CreditLedgerTable.js.map +1 -0
  17. package/billing/CreditPackGrid.d.ts +31 -0
  18. package/billing/CreditPackGrid.d.ts.map +1 -0
  19. package/billing/CreditPackGrid.js +35 -0
  20. package/billing/CreditPackGrid.js.map +1 -0
  21. package/billing/LowBalanceBanner.d.ts +26 -0
  22. package/billing/LowBalanceBanner.d.ts.map +1 -0
  23. package/billing/LowBalanceBanner.js +33 -0
  24. package/billing/LowBalanceBanner.js.map +1 -0
  25. package/billing/PaymentMethodCard.d.ts +35 -0
  26. package/billing/PaymentMethodCard.d.ts.map +1 -0
  27. package/billing/PaymentMethodCard.js +48 -0
  28. package/billing/PaymentMethodCard.js.map +1 -0
  29. package/billing/credit-packs.d.ts +25 -0
  30. package/billing/credit-packs.d.ts.map +1 -0
  31. package/billing/credit-packs.js +39 -0
  32. package/billing/credit-packs.js.map +1 -0
  33. package/billing/format.d.ts +39 -0
  34. package/billing/format.d.ts.map +1 -0
  35. package/billing/format.js +90 -0
  36. package/billing/format.js.map +1 -0
  37. package/billing/index.d.ts +32 -0
  38. package/billing/index.d.ts.map +1 -0
  39. package/billing/index.js +21 -0
  40. package/billing/index.js.map +1 -0
  41. package/billing/useBillingAccount.d.ts +40 -0
  42. package/billing/useBillingAccount.d.ts.map +1 -0
  43. package/billing/useBillingAccount.js +35 -0
  44. package/billing/useBillingAccount.js.map +1 -0
  45. package/billing/useBillingUsageReport.d.ts +42 -0
  46. package/billing/useBillingUsageReport.d.ts.map +1 -0
  47. package/billing/useBillingUsageReport.js +43 -0
  48. package/billing/useBillingUsageReport.js.map +1 -0
  49. package/billing/useCreateBillingPortalSession.d.ts +35 -0
  50. package/billing/useCreateBillingPortalSession.d.ts.map +1 -0
  51. package/billing/useCreateBillingPortalSession.js +50 -0
  52. package/billing/useCreateBillingPortalSession.js.map +1 -0
  53. package/billing/useCreateCheckoutSession.d.ts +54 -0
  54. package/billing/useCreateCheckoutSession.d.ts.map +1 -0
  55. package/billing/useCreateCheckoutSession.js +58 -0
  56. package/billing/useCreateCheckoutSession.js.map +1 -0
  57. package/billing/useCreditLedger.d.ts +48 -0
  58. package/billing/useCreditLedger.d.ts.map +1 -0
  59. package/billing/useCreditLedger.js +39 -0
  60. package/billing/useCreditLedger.js.map +1 -0
  61. package/billing/useCustomerModelPricing.d.ts +41 -0
  62. package/billing/useCustomerModelPricing.d.ts.map +1 -0
  63. package/billing/useCustomerModelPricing.js +37 -0
  64. package/billing/useCustomerModelPricing.js.map +1 -0
  65. package/billing/useSetAutoRechargeConfig.d.ts +50 -0
  66. package/billing/useSetAutoRechargeConfig.d.ts.map +1 -0
  67. package/billing/useSetAutoRechargeConfig.js +53 -0
  68. package/billing/useSetAutoRechargeConfig.js.map +1 -0
  69. package/composer/ComposerToolbar.js +1 -1
  70. package/composer/ComposerToolbar.js.map +1 -1
  71. package/composer/SessionComposer.d.ts +1 -1
  72. package/composer/SessionComposer.d.ts.map +1 -1
  73. package/composer/SessionComposer.js +19 -4
  74. package/composer/SessionComposer.js.map +1 -1
  75. package/composer/__tests__/SessionComposer-memo.test.d.ts +2 -0
  76. package/composer/__tests__/SessionComposer-memo.test.d.ts.map +1 -0
  77. package/composer/__tests__/SessionComposer-memo.test.js +23 -0
  78. package/composer/__tests__/SessionComposer-memo.test.js.map +1 -0
  79. package/execution/ApprovalCard.d.ts +5 -1
  80. package/execution/ApprovalCard.d.ts.map +1 -1
  81. package/execution/ApprovalCard.js +7 -3
  82. package/execution/ApprovalCard.js.map +1 -1
  83. package/execution/ExecutionPhaseBadge.d.ts +1 -1
  84. package/execution/ExecutionPhaseBadge.d.ts.map +1 -1
  85. package/execution/ExecutionPhaseBadge.js +3 -2
  86. package/execution/ExecutionPhaseBadge.js.map +1 -1
  87. package/execution/MessageEntry.d.ts +7 -3
  88. package/execution/MessageEntry.d.ts.map +1 -1
  89. package/execution/MessageEntry.js +19 -8
  90. package/execution/MessageEntry.js.map +1 -1
  91. package/execution/MessageThread.d.ts +84 -3
  92. package/execution/MessageThread.d.ts.map +1 -1
  93. package/execution/MessageThread.js +113 -65
  94. package/execution/MessageThread.js.map +1 -1
  95. package/execution/SetupProgress.d.ts +1 -1
  96. package/execution/SetupProgress.d.ts.map +1 -1
  97. package/execution/SetupProgress.js +3 -3
  98. package/execution/SetupProgress.js.map +1 -1
  99. package/execution/SubAgentSection.d.ts +5 -1
  100. package/execution/SubAgentSection.d.ts.map +1 -1
  101. package/execution/SubAgentSection.js +13 -7
  102. package/execution/SubAgentSection.js.map +1 -1
  103. package/execution/ThreadSkeleton.d.ts +22 -0
  104. package/execution/ThreadSkeleton.d.ts.map +1 -0
  105. package/execution/ThreadSkeleton.js +26 -0
  106. package/execution/ThreadSkeleton.js.map +1 -0
  107. package/execution/ToolCallGroup.d.ts +16 -1
  108. package/execution/ToolCallGroup.d.ts.map +1 -1
  109. package/execution/ToolCallGroup.js +31 -3
  110. package/execution/ToolCallGroup.js.map +1 -1
  111. package/execution/UsageWidget.d.ts +1 -1
  112. package/execution/__tests__/message-entry.test.d.ts +2 -0
  113. package/execution/__tests__/message-entry.test.d.ts.map +1 -0
  114. package/execution/__tests__/message-entry.test.js +178 -0
  115. package/execution/__tests__/message-entry.test.js.map +1 -0
  116. package/execution/__tests__/thread-keys.test.d.ts +2 -0
  117. package/execution/__tests__/thread-keys.test.d.ts.map +1 -0
  118. package/execution/__tests__/thread-keys.test.js +289 -0
  119. package/execution/__tests__/thread-keys.test.js.map +1 -0
  120. package/execution/__tests__/thread-memoization.test.d.ts +2 -0
  121. package/execution/__tests__/thread-memoization.test.d.ts.map +1 -0
  122. package/execution/__tests__/thread-memoization.test.js +262 -0
  123. package/execution/__tests__/thread-memoization.test.js.map +1 -0
  124. package/execution/__tests__/thread-skeleton.test.d.ts +2 -0
  125. package/execution/__tests__/thread-skeleton.test.d.ts.map +1 -0
  126. package/execution/__tests__/thread-skeleton.test.js +35 -0
  127. package/execution/__tests__/thread-skeleton.test.js.map +1 -0
  128. package/execution/__tests__/useExecutionStream.test.js +73 -10
  129. package/execution/__tests__/useExecutionStream.test.js.map +1 -1
  130. package/execution/__tests__/useSessionVariables-stability.test.d.ts +2 -0
  131. package/execution/__tests__/useSessionVariables-stability.test.d.ts.map +1 -0
  132. package/execution/__tests__/useSessionVariables-stability.test.js +69 -0
  133. package/execution/__tests__/useSessionVariables-stability.test.js.map +1 -0
  134. package/execution/__tests__/virtualized-thread.test.d.ts +2 -0
  135. package/execution/__tests__/virtualized-thread.test.d.ts.map +1 -0
  136. package/execution/__tests__/virtualized-thread.test.js +274 -0
  137. package/execution/__tests__/virtualized-thread.test.js.map +1 -0
  138. package/execution/index.d.ts +2 -0
  139. package/execution/index.d.ts.map +1 -1
  140. package/execution/index.js +1 -0
  141. package/execution/index.js.map +1 -1
  142. package/execution/useExecutionStream.d.ts +35 -10
  143. package/execution/useExecutionStream.d.ts.map +1 -1
  144. package/execution/useExecutionStream.js +79 -40
  145. package/execution/useExecutionStream.js.map +1 -1
  146. package/execution/useSessionVariables.d.ts.map +1 -1
  147. package/execution/useSessionVariables.js +4 -3
  148. package/execution/useSessionVariables.js.map +1 -1
  149. package/github/useGitHubConnection.d.ts.map +1 -1
  150. package/github/useGitHubConnection.js +5 -4
  151. package/github/useGitHubConnection.js.map +1 -1
  152. package/identity-account/index.d.ts +2 -0
  153. package/identity-account/index.d.ts.map +1 -0
  154. package/identity-account/index.js +2 -0
  155. package/identity-account/index.js.map +1 -0
  156. package/identity-account/useIdentityAccountGate.d.ts +81 -0
  157. package/identity-account/useIdentityAccountGate.d.ts.map +1 -0
  158. package/identity-account/useIdentityAccountGate.js +100 -0
  159. package/identity-account/useIdentityAccountGate.js.map +1 -0
  160. package/index.d.ts +10 -4
  161. package/index.d.ts.map +1 -1
  162. package/index.js +8 -2
  163. package/index.js.map +1 -1
  164. package/internal/FetchCacheProvider.d.ts +44 -0
  165. package/internal/FetchCacheProvider.d.ts.map +1 -0
  166. package/internal/FetchCacheProvider.js +61 -0
  167. package/internal/FetchCacheProvider.js.map +1 -0
  168. package/internal/JumpToLatestButton.d.ts +14 -0
  169. package/internal/JumpToLatestButton.d.ts.map +1 -0
  170. package/internal/JumpToLatestButton.js +19 -0
  171. package/internal/JumpToLatestButton.js.map +1 -0
  172. package/internal/ThreadItemWrapper.d.ts +20 -0
  173. package/internal/ThreadItemWrapper.d.ts.map +1 -0
  174. package/internal/ThreadItemWrapper.js +44 -0
  175. package/internal/ThreadItemWrapper.js.map +1 -0
  176. package/internal/VirtualizedThread.d.ts +25 -0
  177. package/internal/VirtualizedThread.d.ts.map +1 -0
  178. package/internal/VirtualizedThread.js +58 -0
  179. package/internal/VirtualizedThread.js.map +1 -0
  180. package/internal/__tests__/fetch-cache.test.d.ts +2 -0
  181. package/internal/__tests__/fetch-cache.test.d.ts.map +1 -0
  182. package/internal/__tests__/fetch-cache.test.js +182 -0
  183. package/internal/__tests__/fetch-cache.test.js.map +1 -0
  184. package/internal/__tests__/stream-controller.test.d.ts +2 -0
  185. package/internal/__tests__/stream-controller.test.d.ts.map +1 -0
  186. package/internal/__tests__/stream-controller.test.js +294 -0
  187. package/internal/__tests__/stream-controller.test.js.map +1 -0
  188. package/internal/__tests__/thread-animation.test.d.ts +2 -0
  189. package/internal/__tests__/thread-animation.test.d.ts.map +1 -0
  190. package/internal/__tests__/thread-animation.test.js +79 -0
  191. package/internal/__tests__/thread-animation.test.js.map +1 -0
  192. package/internal/__tests__/useAutoScroll.test.d.ts +2 -0
  193. package/internal/__tests__/useAutoScroll.test.d.ts.map +1 -0
  194. package/internal/__tests__/useAutoScroll.test.js +188 -0
  195. package/internal/__tests__/useAutoScroll.test.js.map +1 -0
  196. package/internal/__tests__/useFetch-cache.test.d.ts +2 -0
  197. package/internal/__tests__/useFetch-cache.test.d.ts.map +1 -0
  198. package/internal/__tests__/useFetch-cache.test.js +137 -0
  199. package/internal/__tests__/useFetch-cache.test.js.map +1 -0
  200. package/internal/dev/__tests__/use-key-stability.test.d.ts +2 -0
  201. package/internal/dev/__tests__/use-key-stability.test.d.ts.map +1 -0
  202. package/internal/dev/__tests__/use-key-stability.test.js +72 -0
  203. package/internal/dev/__tests__/use-key-stability.test.js.map +1 -0
  204. package/internal/dev/__tests__/use-render-tracer.test.d.ts +2 -0
  205. package/internal/dev/__tests__/use-render-tracer.test.d.ts.map +1 -0
  206. package/internal/dev/__tests__/use-render-tracer.test.js +55 -0
  207. package/internal/dev/__tests__/use-render-tracer.test.js.map +1 -0
  208. package/internal/dev/dom-counter.d.ts +14 -0
  209. package/internal/dev/dom-counter.d.ts.map +1 -0
  210. package/internal/dev/dom-counter.js +39 -0
  211. package/internal/dev/dom-counter.js.map +1 -0
  212. package/internal/dev/index.d.ts +6 -0
  213. package/internal/dev/index.d.ts.map +1 -0
  214. package/internal/dev/index.js +6 -0
  215. package/internal/dev/index.js.map +1 -0
  216. package/internal/dev/profiler-wrapper.d.ts +16 -0
  217. package/internal/dev/profiler-wrapper.d.ts.map +1 -0
  218. package/internal/dev/profiler-wrapper.js +31 -0
  219. package/internal/dev/profiler-wrapper.js.map +1 -0
  220. package/internal/dev/use-key-stability.d.ts +22 -0
  221. package/internal/dev/use-key-stability.d.ts.map +1 -0
  222. package/internal/dev/use-key-stability.js +67 -0
  223. package/internal/dev/use-key-stability.js.map +1 -0
  224. package/internal/dev/use-render-tracer.d.ts +13 -0
  225. package/internal/dev/use-render-tracer.d.ts.map +1 -0
  226. package/internal/dev/use-render-tracer.js +57 -0
  227. package/internal/dev/use-render-tracer.js.map +1 -0
  228. package/internal/dev/use-stream-rate.d.ts +23 -0
  229. package/internal/dev/use-stream-rate.d.ts.map +1 -0
  230. package/internal/dev/use-stream-rate.js +94 -0
  231. package/internal/dev/use-stream-rate.js.map +1 -0
  232. package/internal/fetch-cache.d.ts +72 -0
  233. package/internal/fetch-cache.d.ts.map +1 -0
  234. package/internal/fetch-cache.js +118 -0
  235. package/internal/fetch-cache.js.map +1 -0
  236. package/internal/store/__tests__/conversation-store.test.d.ts +2 -0
  237. package/internal/store/__tests__/conversation-store.test.d.ts.map +1 -0
  238. package/internal/store/__tests__/conversation-store.test.js +200 -0
  239. package/internal/store/__tests__/conversation-store.test.js.map +1 -0
  240. package/internal/store/__tests__/structural-share.test.d.ts +2 -0
  241. package/internal/store/__tests__/structural-share.test.d.ts.map +1 -0
  242. package/internal/store/__tests__/structural-share.test.js +368 -0
  243. package/internal/store/__tests__/structural-share.test.js.map +1 -0
  244. package/internal/store/conversation-store.d.ts +62 -0
  245. package/internal/store/conversation-store.d.ts.map +1 -0
  246. package/internal/store/conversation-store.js +95 -0
  247. package/internal/store/conversation-store.js.map +1 -0
  248. package/internal/store/index.d.ts +31 -0
  249. package/internal/store/index.d.ts.map +1 -0
  250. package/internal/store/index.js +54 -0
  251. package/internal/store/index.js.map +1 -0
  252. package/internal/store/structural-share.d.ts +13 -0
  253. package/internal/store/structural-share.d.ts.map +1 -0
  254. package/internal/store/structural-share.js +240 -0
  255. package/internal/store/structural-share.js.map +1 -0
  256. package/internal/stream-controller.d.ts +85 -0
  257. package/internal/stream-controller.d.ts.map +1 -0
  258. package/internal/stream-controller.js +146 -0
  259. package/internal/stream-controller.js.map +1 -0
  260. package/internal/useAutoScroll.d.ts +32 -0
  261. package/internal/useAutoScroll.d.ts.map +1 -0
  262. package/internal/useAutoScroll.js +97 -0
  263. package/internal/useAutoScroll.js.map +1 -0
  264. package/internal/useFetch.d.ts +14 -0
  265. package/internal/useFetch.d.ts.map +1 -1
  266. package/internal/useFetch.js +32 -2
  267. package/internal/useFetch.js.map +1 -1
  268. package/package.json +7 -5
  269. package/session/__tests__/useNewSessionFlow.test.js +16 -0
  270. package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
  271. package/session/__tests__/usePersistedModel.test.d.ts +2 -0
  272. package/session/__tests__/usePersistedModel.test.d.ts.map +1 -0
  273. package/session/__tests__/usePersistedModel.test.js +82 -0
  274. package/session/__tests__/usePersistedModel.test.js.map +1 -0
  275. package/session/__tests__/useSession.test.d.ts +2 -0
  276. package/session/__tests__/useSession.test.d.ts.map +1 -0
  277. package/session/__tests__/useSession.test.js +130 -0
  278. package/session/__tests__/useSession.test.js.map +1 -0
  279. package/session/useNewSessionFlow.d.ts.map +1 -1
  280. package/session/useNewSessionFlow.js +12 -6
  281. package/session/useNewSessionFlow.js.map +1 -1
  282. package/session/usePersistedModel.d.ts +3 -0
  283. package/session/usePersistedModel.d.ts.map +1 -1
  284. package/session/usePersistedModel.js +27 -2
  285. package/session/usePersistedModel.js.map +1 -1
  286. package/session/useSession.d.ts.map +1 -1
  287. package/session/useSession.js +1 -1
  288. package/session/useSession.js.map +1 -1
  289. package/session/useSessionConversation.d.ts.map +1 -1
  290. package/session/useSessionConversation.js +9 -1
  291. package/session/useSessionConversation.js.map +1 -1
  292. package/session/useSessionExecutions.d.ts.map +1 -1
  293. package/session/useSessionExecutions.js +1 -1
  294. package/session/useSessionExecutions.js.map +1 -1
  295. package/session/useSessionPageFlow.js +1 -1
  296. package/session/useSessionPageFlow.js.map +1 -1
  297. package/session/useSessionUsage.d.ts +24 -40
  298. package/session/useSessionUsage.d.ts.map +1 -1
  299. package/session/useSessionUsage.js +64 -97
  300. package/session/useSessionUsage.js.map +1 -1
  301. package/settings/BillingSection.d.ts +3 -0
  302. package/settings/BillingSection.d.ts.map +1 -0
  303. package/settings/BillingSection.js +3 -0
  304. package/settings/BillingSection.js.map +1 -0
  305. package/settings/index.d.ts +2 -0
  306. package/settings/index.d.ts.map +1 -1
  307. package/settings/index.js +1 -0
  308. package/settings/index.js.map +1 -1
  309. package/settings/settings-nav.js +1 -1
  310. package/settings/settings-nav.js.map +1 -1
  311. package/src/billing/AutoRechargeCard.tsx +274 -0
  312. package/src/billing/BillingSection.tsx +255 -0
  313. package/src/billing/CreditBalanceCard.tsx +81 -0
  314. package/src/billing/CreditLedgerTable.tsx +281 -0
  315. package/src/billing/CreditPackGrid.tsx +132 -0
  316. package/src/billing/LowBalanceBanner.tsx +67 -0
  317. package/src/billing/PaymentMethodCard.tsx +133 -0
  318. package/src/billing/credit-packs.ts +54 -0
  319. package/src/billing/format.ts +97 -0
  320. package/src/billing/index.ts +51 -0
  321. package/src/billing/useBillingAccount.ts +64 -0
  322. package/src/billing/useBillingUsageReport.ts +73 -0
  323. package/src/billing/useCreateBillingPortalSession.ts +76 -0
  324. package/src/billing/useCreateCheckoutSession.ts +101 -0
  325. package/src/billing/useCreditLedger.ts +79 -0
  326. package/src/billing/useCustomerModelPricing.ts +67 -0
  327. package/src/billing/useSetAutoRechargeConfig.ts +90 -0
  328. package/src/composer/ComposerToolbar.tsx +1 -1
  329. package/src/composer/SessionComposer.tsx +22 -4
  330. package/src/composer/__tests__/SessionComposer-memo.test.ts +26 -0
  331. package/src/execution/ApprovalCard.tsx +7 -3
  332. package/src/execution/ExecutionPhaseBadge.tsx +3 -2
  333. package/src/execution/MessageEntry.tsx +27 -16
  334. package/src/execution/MessageThread.tsx +308 -131
  335. package/src/execution/SetupProgress.tsx +3 -3
  336. package/src/execution/SubAgentSection.tsx +14 -6
  337. package/src/execution/ThreadSkeleton.tsx +73 -0
  338. package/src/execution/ToolCallGroup.tsx +36 -3
  339. package/src/execution/UsageWidget.tsx +1 -1
  340. package/src/execution/__tests__/message-entry.test.tsx +236 -0
  341. package/src/execution/__tests__/thread-keys.test.ts +409 -0
  342. package/src/execution/__tests__/thread-memoization.test.ts +320 -0
  343. package/src/execution/__tests__/thread-skeleton.test.tsx +44 -0
  344. package/src/execution/__tests__/useExecutionStream.test.tsx +109 -12
  345. package/src/execution/__tests__/useSessionVariables-stability.test.ts +95 -0
  346. package/src/execution/__tests__/virtualized-thread.test.tsx +401 -0
  347. package/src/execution/index.ts +3 -0
  348. package/src/execution/useExecutionStream.ts +123 -48
  349. package/src/execution/useSessionVariables.ts +17 -12
  350. package/src/github/useGitHubConnection.ts +18 -13
  351. package/src/identity-account/index.ts +5 -0
  352. package/src/identity-account/useIdentityAccountGate.ts +163 -0
  353. package/src/index.ts +73 -0
  354. package/src/internal/FetchCacheProvider.tsx +74 -0
  355. package/src/internal/JumpToLatestButton.tsx +61 -0
  356. package/src/internal/ThreadItemWrapper.tsx +65 -0
  357. package/src/internal/VirtualizedThread.tsx +162 -0
  358. package/src/internal/__tests__/fetch-cache.test.ts +230 -0
  359. package/src/internal/__tests__/stream-controller.test.ts +395 -0
  360. package/src/internal/__tests__/thread-animation.test.tsx +121 -0
  361. package/src/internal/__tests__/useAutoScroll.test.tsx +261 -0
  362. package/src/internal/__tests__/useFetch-cache.test.ts +214 -0
  363. package/src/internal/dev/__tests__/use-key-stability.test.ts +124 -0
  364. package/src/internal/dev/__tests__/use-render-tracer.test.ts +78 -0
  365. package/src/internal/dev/dom-counter.ts +47 -0
  366. package/src/internal/dev/index.ts +5 -0
  367. package/src/internal/dev/profiler-wrapper.tsx +52 -0
  368. package/src/internal/dev/use-key-stability.ts +86 -0
  369. package/src/internal/dev/use-render-tracer.ts +70 -0
  370. package/src/internal/dev/use-stream-rate.ts +138 -0
  371. package/src/internal/fetch-cache.ts +155 -0
  372. package/src/internal/store/__tests__/conversation-store.test.ts +257 -0
  373. package/src/internal/store/__tests__/structural-share.test.ts +454 -0
  374. package/src/internal/store/conversation-store.ts +128 -0
  375. package/src/internal/store/index.ts +68 -0
  376. package/src/internal/store/structural-share.ts +318 -0
  377. package/src/internal/stream-controller.ts +201 -0
  378. package/src/internal/useAutoScroll.ts +121 -0
  379. package/src/internal/useFetch.ts +51 -2
  380. package/src/session/__tests__/useNewSessionFlow.test.tsx +22 -0
  381. package/src/session/__tests__/usePersistedModel.test.tsx +117 -0
  382. package/src/session/__tests__/useSession.test.tsx +187 -0
  383. package/src/session/useNewSessionFlow.ts +12 -6
  384. package/src/session/usePersistedModel.ts +28 -2
  385. package/src/session/useSession.ts +1 -0
  386. package/src/session/useSessionConversation.ts +11 -2
  387. package/src/session/useSessionExecutions.ts +1 -0
  388. package/src/session/useSessionPageFlow.ts +1 -1
  389. package/src/session/useSessionUsage.ts +102 -123
  390. package/src/settings/BillingSection.tsx +4 -0
  391. package/src/settings/index.ts +2 -0
  392. package/src/settings/settings-nav.ts +1 -1
  393. package/src/styles.css +31 -0
  394. package/src/usage/AgentBreakdownList.tsx +147 -0
  395. package/src/usage/CreditRunwayIndicator.tsx +71 -0
  396. package/src/usage/ExportButton.tsx +115 -0
  397. package/src/usage/HarnessSplitCard.tsx +103 -0
  398. package/src/usage/OrgUsagePanel.tsx +109 -45
  399. package/src/usage/index.ts +15 -0
  400. package/src/usage/useExportCSV.ts +115 -0
  401. package/src/usage/useOrgUsageReport.ts +2 -1
  402. package/src/workspace/__tests__/useWorkspaceEntries-stability.test.ts +76 -0
  403. package/src/workspace/useWorkspaceEntries.ts +16 -11
  404. package/styles.css +1 -1
  405. package/usage/AgentBreakdownList.d.ts +21 -0
  406. package/usage/AgentBreakdownList.d.ts.map +1 -0
  407. package/usage/AgentBreakdownList.js +44 -0
  408. package/usage/AgentBreakdownList.js.map +1 -0
  409. package/usage/CreditRunwayIndicator.d.ts +21 -0
  410. package/usage/CreditRunwayIndicator.d.ts.map +1 -0
  411. package/usage/CreditRunwayIndicator.js +38 -0
  412. package/usage/CreditRunwayIndicator.js.map +1 -0
  413. package/usage/ExportButton.d.ts +20 -0
  414. package/usage/ExportButton.d.ts.map +1 -0
  415. package/usage/ExportButton.js +36 -0
  416. package/usage/ExportButton.js.map +1 -0
  417. package/usage/HarnessSplitCard.d.ts +17 -0
  418. package/usage/HarnessSplitCard.d.ts.map +1 -0
  419. package/usage/HarnessSplitCard.js +38 -0
  420. package/usage/HarnessSplitCard.js.map +1 -0
  421. package/usage/OrgUsagePanel.d.ts.map +1 -1
  422. package/usage/OrgUsagePanel.js +30 -22
  423. package/usage/OrgUsagePanel.js.map +1 -1
  424. package/usage/index.d.ts +10 -0
  425. package/usage/index.d.ts.map +1 -1
  426. package/usage/index.js +5 -0
  427. package/usage/index.js.map +1 -1
  428. package/usage/useExportCSV.d.ts +23 -0
  429. package/usage/useExportCSV.d.ts.map +1 -0
  430. package/usage/useExportCSV.js +81 -0
  431. package/usage/useExportCSV.js.map +1 -0
  432. package/usage/useOrgUsageReport.d.ts +2 -1
  433. package/usage/useOrgUsageReport.d.ts.map +1 -1
  434. package/usage/useOrgUsageReport.js +2 -1
  435. package/usage/useOrgUsageReport.js.map +1 -1
  436. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts +2 -0
  437. package/workspace/__tests__/useWorkspaceEntries-stability.test.d.ts.map +1 -0
  438. package/workspace/__tests__/useWorkspaceEntries-stability.test.js +57 -0
  439. package/workspace/__tests__/useWorkspaceEntries-stability.test.js.map +1 -0
  440. package/workspace/useWorkspaceEntries.d.ts.map +1 -1
  441. package/workspace/useWorkspaceEntries.js +5 -4
  442. package/workspace/useWorkspaceEntries.js.map +1 -1
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { type DependencyList, useCallback, useEffect, useRef, useState } from "react";
4
+ import { useFetchCache } from "./FetchCacheProvider";
4
5
  import { toError } from "./toError";
5
6
 
6
7
  /** Options for {@link useFetch}. */
@@ -13,6 +14,21 @@ export interface UseFetchOptions {
13
14
  * request piling on slow connections.
14
15
  */
15
16
  readonly refetchInterval?: number | false;
17
+
18
+ /**
19
+ * Stable string key for cross-mount caching.
20
+ *
21
+ * When provided (and a {@link FetchCacheProvider} is mounted above
22
+ * this component), the hook reads cached data on mount to avoid a
23
+ * loading skeleton, and writes fresh data to the cache on every
24
+ * successful fetch.
25
+ *
26
+ * Pass `undefined` to opt out of caching for a given call.
27
+ *
28
+ * @example `session:${id}`
29
+ * @example `session-executions:${sessionId}`
30
+ */
31
+ readonly cacheKey?: string;
16
32
  }
17
33
 
18
34
  /** Return value of {@link useFetch}. */
@@ -59,14 +75,33 @@ export function useFetch<T>(
59
75
  initialData: T,
60
76
  options?: UseFetchOptions,
61
77
  ): UseFetchReturn<T> {
62
- const [data, setData] = useState<T>(initialData);
78
+ const cache = useFetchCache();
79
+ const cacheKey = options?.cacheKey;
80
+
81
+ // Resolve initial state from cache when available. The initializer
82
+ // function runs once on mount — exactly the right time to seed state
83
+ // from a previous mount's result and skip the loading skeleton.
84
+ const [data, setData] = useState<T>(() => {
85
+ if (cacheKey && cache) {
86
+ const cached = cache.get<T>(cacheKey);
87
+ if (cached !== undefined) return cached;
88
+ }
89
+ return initialData;
90
+ });
63
91
  const [error, setError] = useState<Error | null>(null);
64
92
  const [fetchKey, setFetchKey] = useState(0);
65
93
 
66
- const hasDataRef = useRef(false);
94
+ const hasDataRef = useRef(
95
+ cacheKey && cache ? cache.has(cacheKey) : false,
96
+ );
67
97
  const isFetchingRef = useRef(false);
68
98
  const [isFetching, setIsFetching] = useState(false);
69
99
 
100
+ // Stable ref for cache — avoids adding cache to effect deps while
101
+ // still letting the effect body access the current instance.
102
+ const cacheRef = useRef(cache);
103
+ cacheRef.current = cache;
104
+
70
105
  const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
71
106
 
72
107
  useEffect(() => {
@@ -79,6 +114,17 @@ export function useFetch<T>(
79
114
  return;
80
115
  }
81
116
 
117
+ // On dep change (without remount), check cache for the new key so
118
+ // we can show cached data immediately rather than stale data from
119
+ // a different identity (e.g. session A's data while session B loads).
120
+ if (cacheKey && cacheRef.current) {
121
+ const cached = cacheRef.current.get<T>(cacheKey);
122
+ if (cached !== undefined) {
123
+ setData(cached);
124
+ hasDataRef.current = true;
125
+ }
126
+ }
127
+
82
128
  const cancelled = { current: false };
83
129
  setIsFetching(true);
84
130
  isFetchingRef.current = true;
@@ -91,6 +137,9 @@ export function useFetch<T>(
91
137
  hasDataRef.current = true;
92
138
  setIsFetching(false);
93
139
  isFetchingRef.current = false;
140
+ if (cacheKey && cacheRef.current) {
141
+ cacheRef.current.set(cacheKey, result);
142
+ }
94
143
  },
95
144
  (err) => {
96
145
  if (cancelled.current) return;
@@ -190,6 +190,28 @@ describe("useNewSessionFlow", () => {
190
190
  // DEFAULT_MODEL_ID (anthropic) is not in cursor registry
191
191
  expect(result.current.modelId).not.toBe(DEFAULT_MODEL_ID);
192
192
  });
193
+
194
+ it("strips compound keys before persisting to localStorage", () => {
195
+ localStorage.setItem(STORAGE_KEY_HARNESS, "cursor");
196
+ const { result } = renderHook(() => useNewSessionFlow(defaultOptions()));
197
+
198
+ // Simulate compound key from unified mode ModelSelector
199
+ act(() => result.current.setModelId("cursor/default"));
200
+
201
+ // Should store plain modelId, not compound key
202
+ expect(localStorage.getItem(STORAGE_KEY_MODEL_CURSOR)).toBe("default");
203
+ });
204
+
205
+ it("restores compound keys from localStorage as plain modelId", () => {
206
+ localStorage.setItem(STORAGE_KEY_HARNESS, "cursor");
207
+ // Legacy: compound key was stored before fix
208
+ localStorage.setItem(STORAGE_KEY_MODEL_CURSOR, "cursor/default");
209
+
210
+ const { result } = renderHook(() => useNewSessionFlow(defaultOptions()));
211
+
212
+ // Should extract plain modelId and validate against registry
213
+ expect(result.current.modelId).toBe(DEFAULT_CURSOR_MODEL_ID);
214
+ });
193
215
  });
194
216
 
195
217
  describe("submit with harness", () => {
@@ -0,0 +1,117 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { usePersistedModel } from "../usePersistedModel";
4
+ import { DEFAULT_MODEL_ID, DEFAULT_CURSOR_MODEL_ID } from "../../models/registry";
5
+
6
+ const STORAGE_KEY_NATIVE = "stigmer:session:model";
7
+ const STORAGE_KEY_CURSOR = "stigmer:session:model:cursor";
8
+
9
+ describe("usePersistedModel", () => {
10
+ beforeEach(() => {
11
+ localStorage.clear();
12
+ });
13
+
14
+ afterEach(() => {
15
+ localStorage.clear();
16
+ });
17
+
18
+ describe("basic persistence", () => {
19
+ it("returns undefined when localStorage is empty", () => {
20
+ const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
21
+ expect(result.current[0]).toBeUndefined();
22
+ });
23
+
24
+ it("restores a valid model from localStorage", () => {
25
+ localStorage.setItem(STORAGE_KEY_NATIVE, DEFAULT_MODEL_ID);
26
+ const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
27
+ expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
28
+ });
29
+
30
+ it("returns undefined for an invalid model in localStorage", () => {
31
+ localStorage.setItem(STORAGE_KEY_NATIVE, "nonexistent-model-xyz");
32
+ const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
33
+ expect(result.current[0]).toBeUndefined();
34
+ });
35
+
36
+ it("persists model on change", () => {
37
+ const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
38
+
39
+ act(() => result.current[1](DEFAULT_MODEL_ID));
40
+
41
+ expect(localStorage.getItem(STORAGE_KEY_NATIVE)).toBe(DEFAULT_MODEL_ID);
42
+ expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
43
+ });
44
+
45
+ it("uses cursor-specific key for cursor harness", () => {
46
+ localStorage.setItem(STORAGE_KEY_CURSOR, DEFAULT_CURSOR_MODEL_ID);
47
+ const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }));
48
+ expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
49
+ });
50
+ });
51
+
52
+ describe("compound key handling", () => {
53
+ it("extracts plain modelId from compound key in localStorage", () => {
54
+ localStorage.setItem(STORAGE_KEY_CURSOR, "cursor/default");
55
+ const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }));
56
+ expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
57
+ });
58
+
59
+ it("extracts plain modelId from native compound key", () => {
60
+ localStorage.setItem(STORAGE_KEY_NATIVE, `native/${DEFAULT_MODEL_ID}`);
61
+ const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
62
+ expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
63
+ });
64
+
65
+ it("handles non-compound values unchanged", () => {
66
+ localStorage.setItem(STORAGE_KEY_CURSOR, DEFAULT_CURSOR_MODEL_ID);
67
+ const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }));
68
+ expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
69
+ });
70
+ });
71
+
72
+ describe("harness transition (key change re-sync)", () => {
73
+ it("re-reads from new localStorage key when harness changes", () => {
74
+ localStorage.setItem(STORAGE_KEY_NATIVE, DEFAULT_MODEL_ID);
75
+ localStorage.setItem(STORAGE_KEY_CURSOR, DEFAULT_CURSOR_MODEL_ID);
76
+
77
+ const { result, rerender } = renderHook(
78
+ ({ harness }: { harness: "native" | "cursor" }) => usePersistedModel({ harness }),
79
+ { initialProps: { harness: "native" } },
80
+ );
81
+
82
+ expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
83
+
84
+ rerender({ harness: "cursor" });
85
+
86
+ expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
87
+ });
88
+
89
+ it("returns undefined after harness change when new key has no stored value", () => {
90
+ localStorage.setItem(STORAGE_KEY_NATIVE, DEFAULT_MODEL_ID);
91
+
92
+ const { result, rerender } = renderHook(
93
+ ({ harness }: { harness: "native" | "cursor" }) => usePersistedModel({ harness }),
94
+ { initialProps: { harness: "native" } },
95
+ );
96
+
97
+ expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
98
+
99
+ rerender({ harness: "cursor" });
100
+
101
+ expect(result.current[0]).toBeUndefined();
102
+ });
103
+
104
+ it("handles compound key in new storage key after harness transition", () => {
105
+ localStorage.setItem(STORAGE_KEY_CURSOR, "cursor/default");
106
+
107
+ const { result, rerender } = renderHook(
108
+ ({ harness }: { harness: "native" | "cursor" }) => usePersistedModel({ harness }),
109
+ { initialProps: { harness: "native" } },
110
+ );
111
+
112
+ rerender({ harness: "cursor" });
113
+
114
+ expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,187 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import {
6
+ SessionSchema,
7
+ type Session,
8
+ } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
9
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
10
+ import type { Stigmer } from "@stigmer/sdk";
11
+ import { StigmerContext } from "../../context";
12
+ import { FetchCacheContext } from "../../internal/FetchCacheProvider";
13
+ import { FetchCache } from "../../internal/fetch-cache";
14
+ import { useSession } from "../useSession";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function makeSession(id: string): Session {
21
+ const session = create(SessionSchema);
22
+ const metadata = create(ApiResourceMetadataSchema);
23
+ metadata.id = id;
24
+ session.metadata = metadata;
25
+ return session;
26
+ }
27
+
28
+ function createMockStigmer(sessionGet: ReturnType<typeof vi.fn>): Stigmer {
29
+ return {
30
+ session: { get: sessionGet },
31
+ } as unknown as Stigmer;
32
+ }
33
+
34
+ async function flush(): Promise<void> {
35
+ await act(async () => {
36
+ await Promise.resolve();
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Wrapper that provides both the Stigmer client and a shared FetchCache
42
+ * instance via context. Sharing the cache instance across renderHook
43
+ * calls mirrors the production layout where FetchCacheProvider sits
44
+ * above the key-based remount boundary.
45
+ */
46
+ function createWrapper(client: Stigmer, cache: FetchCache) {
47
+ return function Wrapper({ children }: { children: ReactNode }) {
48
+ return (
49
+ <FetchCacheContext.Provider value={cache}>
50
+ <StigmerContext.Provider value={client}>
51
+ {children}
52
+ </StigmerContext.Provider>
53
+ </FetchCacheContext.Provider>
54
+ );
55
+ };
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Tests
60
+ // ---------------------------------------------------------------------------
61
+
62
+ describe("useSession — cache behavior", () => {
63
+ it("first visit shows loading then resolves data", async () => {
64
+ const session = makeSession("ses_1");
65
+ const sessionGet = vi.fn().mockResolvedValue(session);
66
+ const cache = new FetchCache();
67
+ const wrapper = createWrapper(createMockStigmer(sessionGet), cache);
68
+
69
+ const { result } = renderHook(() => useSession("ses_1"), { wrapper });
70
+
71
+ expect(result.current.isLoading).toBe(true);
72
+ expect(result.current.session).toBeNull();
73
+
74
+ await flush();
75
+
76
+ expect(result.current.isLoading).toBe(false);
77
+ expect(result.current.session).toBe(session);
78
+ expect(sessionGet).toHaveBeenCalledOnce();
79
+ });
80
+
81
+ it("remount serves cached data instantly (no isLoading)", async () => {
82
+ const session1 = makeSession("ses_1");
83
+ const sessionGet = vi.fn().mockResolvedValue(session1);
84
+ const client = createMockStigmer(sessionGet);
85
+ const cache = new FetchCache();
86
+ const wrapper = createWrapper(client, cache);
87
+
88
+ // First mount — populates the cache.
89
+ const { result: r1, unmount } = renderHook(
90
+ () => useSession("ses_1"),
91
+ { wrapper },
92
+ );
93
+ await flush();
94
+ expect(r1.current.session).toBe(session1);
95
+ unmount();
96
+
97
+ // Second mount (simulates remount after key={activeSessionId} change).
98
+ const freshSession = makeSession("ses_1");
99
+ sessionGet.mockResolvedValue(freshSession);
100
+
101
+ const { result: r2 } = renderHook(
102
+ () => useSession("ses_1"),
103
+ { wrapper },
104
+ );
105
+
106
+ // Cached data is served synchronously — no loading skeleton.
107
+ expect(r2.current.isLoading).toBe(false);
108
+ expect(r2.current.session).toBe(session1);
109
+ expect(r2.current.isRefetching).toBe(true);
110
+
111
+ // Background fetch completes with fresh data.
112
+ await flush();
113
+ expect(r2.current.session).toBe(freshSession);
114
+ expect(r2.current.isRefetching).toBe(false);
115
+ });
116
+
117
+ it("different session IDs have independent cache entries", async () => {
118
+ const sessionA = makeSession("ses_A");
119
+ const sessionB = makeSession("ses_B");
120
+ const sessionGet = vi.fn()
121
+ .mockResolvedValueOnce(sessionA)
122
+ .mockResolvedValueOnce(sessionB)
123
+ .mockResolvedValue(sessionA);
124
+ const cache = new FetchCache();
125
+ const wrapper = createWrapper(createMockStigmer(sessionGet), cache);
126
+
127
+ // Mount session A.
128
+ const { unmount: unmountA } = renderHook(
129
+ () => useSession("ses_A"),
130
+ { wrapper },
131
+ );
132
+ await flush();
133
+ unmountA();
134
+
135
+ // Mount session B.
136
+ const { unmount: unmountB } = renderHook(
137
+ () => useSession("ses_B"),
138
+ { wrapper },
139
+ );
140
+ await flush();
141
+ unmountB();
142
+
143
+ // Remount session A — should get A's cached data, not B's.
144
+ const { result } = renderHook(
145
+ () => useSession("ses_A"),
146
+ { wrapper },
147
+ );
148
+
149
+ expect(result.current.isLoading).toBe(false);
150
+ expect(result.current.session?.metadata?.id).toBe("ses_A");
151
+ });
152
+
153
+ it("works without FetchCacheProvider (standard loading flow)", async () => {
154
+ const session = makeSession("ses_1");
155
+ const sessionGet = vi.fn().mockResolvedValue(session);
156
+ const client = createMockStigmer(sessionGet);
157
+
158
+ function NoCacheWrapper({ children }: { children: ReactNode }) {
159
+ return (
160
+ <StigmerContext.Provider value={client}>
161
+ {children}
162
+ </StigmerContext.Provider>
163
+ );
164
+ }
165
+
166
+ const { result } = renderHook(() => useSession("ses_1"), {
167
+ wrapper: NoCacheWrapper,
168
+ });
169
+
170
+ expect(result.current.isLoading).toBe(true);
171
+ await flush();
172
+ expect(result.current.session).toBe(session);
173
+ });
174
+
175
+ it("null id skips fetching and caching", async () => {
176
+ const sessionGet = vi.fn();
177
+ const cache = new FetchCache();
178
+ const wrapper = createWrapper(createMockStigmer(sessionGet), cache);
179
+
180
+ const { result } = renderHook(() => useSession(null), { wrapper });
181
+
182
+ expect(result.current.isLoading).toBe(false);
183
+ expect(result.current.session).toBeNull();
184
+ expect(sessionGet).not.toHaveBeenCalled();
185
+ expect(cache.size).toBe(0);
186
+ });
187
+ });
@@ -5,6 +5,7 @@ import { getUserMessage, type McpServerUsageInput, type ResourceRef } from "@sti
5
5
  import type { AgentResolution } from "../agent";
6
6
  import { useDefaultAgent } from "../agent";
7
7
  import { useModelRegistry } from "../models";
8
+ import { parseModelKey } from "../models/registry";
8
9
  import { DEFAULT_HARNESS, type HarnessOption } from "../models/harness";
9
10
  import { useWorkspaceEntries, type UseWorkspaceEntriesReturn } from "../workspace";
10
11
  import { useSessionVariables, type UseSessionVariablesReturn } from "../execution/useSessionVariables";
@@ -184,9 +185,9 @@ export function useNewSessionFlow(
184
185
  const setHarness = useCallback(
185
186
  (h: HarnessOption) => {
186
187
  setHarnessRaw(h);
187
- // Restore per-harness model preference, or clear if none stored
188
188
  const storedModel = localStorage.getItem(modelStorageKey(h));
189
- setModelId(storedModel ?? undefined);
189
+ const plain = storedModel ? (parseModelKey(storedModel)?.modelId ?? storedModel) : undefined;
190
+ setModelId(plain);
190
191
  },
191
192
  [],
192
193
  );
@@ -194,15 +195,20 @@ export function useNewSessionFlow(
194
195
  // Restore persisted model on mount (using current harness key)
195
196
  useEffect(() => {
196
197
  const stored = localStorage.getItem(modelStorageKey(harness));
197
- if (stored && getModel(stored)) {
198
- setModelId(stored);
198
+ if (stored) {
199
+ const plain = parseModelKey(stored)?.modelId ?? stored;
200
+ if (getModel(plain)) {
201
+ setModelId(plain);
202
+ }
199
203
  }
200
204
  }, [getModel, harness]);
201
205
 
202
- // Persist model on change (using current harness key)
206
+ // Persist model on change (using current harness key).
207
+ // Strip compound keys (e.g. "cursor/default") to plain modelId before storing.
203
208
  useEffect(() => {
204
209
  if (modelId) {
205
- localStorage.setItem(modelStorageKey(harness), modelId);
210
+ const plain = parseModelKey(modelId)?.modelId ?? modelId;
211
+ localStorage.setItem(modelStorageKey(harness), plain);
206
212
  }
207
213
  }, [modelId, harness]);
208
214
 
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useState } from "react";
3
+ import { useEffect, useRef, useState } from "react";
4
4
  import { useModelRegistry } from "../models";
5
+ import { parseModelKey } from "../models/registry";
5
6
  import type { HarnessOption } from "../models/harness";
6
7
 
7
8
  /** Options for {@link usePersistedModel}. */
@@ -28,6 +29,16 @@ function storageKey(harness?: HarnessOption): string {
28
29
  : "stigmer:session:model";
29
30
  }
30
31
 
32
+ /**
33
+ * Extract the plain modelId from a value that might be a compound key
34
+ * (e.g. `"cursor/default"` → `"default"`). Returns the value unchanged
35
+ * if it's already a plain ID.
36
+ */
37
+ function extractPlainModelId(value: string): string {
38
+ const parsed = parseModelKey(value);
39
+ return parsed ? parsed.modelId : value;
40
+ }
41
+
31
42
  /**
32
43
  * Model selection with localStorage persistence.
33
44
  *
@@ -39,6 +50,9 @@ function storageKey(harness?: HarnessOption): string {
39
50
  * When `options.harness` is provided, the stored model is read from a
40
51
  * harness-specific key and validated against the harness-filtered registry.
41
52
  *
53
+ * Handles legacy compound keys (`"cursor/default"`) gracefully by
54
+ * extracting the plain modelId portion before validation.
55
+ *
42
56
  * Used by both the session launcher (new session) and session page
43
57
  * (follow-up messages) to maintain a consistent model preference.
44
58
  */
@@ -48,12 +62,24 @@ export function usePersistedModel(
48
62
  const harness = options?.harness;
49
63
  const { getModel } = useModelRegistry({ harness });
50
64
  const key = storageKey(harness);
65
+ const prevKeyRef = useRef(key);
51
66
 
52
67
  const [modelId, setModelId] = useState<string | undefined>(() => {
53
68
  if (typeof window === "undefined") return undefined;
54
- return localStorage.getItem(key) ?? undefined;
69
+ const raw = localStorage.getItem(key);
70
+ return raw ? extractPlainModelId(raw) : undefined;
55
71
  });
56
72
 
73
+ // Re-read from localStorage when the storage key changes (harness transition).
74
+ useEffect(() => {
75
+ if (prevKeyRef.current === key) return;
76
+ prevKeyRef.current = key;
77
+
78
+ if (typeof window === "undefined") return;
79
+ const raw = localStorage.getItem(key);
80
+ setModelId(raw ? extractPlainModelId(raw) : undefined);
81
+ }, [key]);
82
+
57
83
  useEffect(() => {
58
84
  if (modelId) {
59
85
  localStorage.setItem(key, modelId);
@@ -62,6 +62,7 @@ export function useSession(id: string | null): UseSessionReturn {
62
62
  id ? () => stigmer.session.get(id) : null,
63
63
  [id, stigmer],
64
64
  null as Session | null,
65
+ { cacheKey: id ? `session:${id}` : undefined },
65
66
  );
66
67
 
67
68
  return { session, isLoading, isRefetching, error, refetch };
@@ -17,6 +17,7 @@ import type {
17
17
  } from "@stigmer/sdk";
18
18
  import { isTerminalPhase } from "../execution/execution-phases";
19
19
  import { useStigmer } from "../hooks";
20
+ import { useConversationStoreRef } from "../internal/store";
20
21
  import { useCreateAgentExecution } from "../execution/useCreateAgentExecution";
21
22
  import { useExecutionStream } from "../execution/useExecutionStream";
22
23
  import { useSubmitApproval } from "../execution/useSubmitApproval";
@@ -248,7 +249,14 @@ export function useSessionConversation(
248
249
 
249
250
  const activeExecutionId = pendingExecutionId ?? listActiveId;
250
251
 
251
- const stream = useExecutionStream(activeExecutionId);
252
+ // The conversation store is shared between useExecutionStream (which
253
+ // ingests snapshots with structural sharing + rAF coalescing) and the
254
+ // rendering tree. This eliminates the duplicate structuralShare that
255
+ // was previously done in this hook.
256
+ const conversationStore = useConversationStoreRef();
257
+ const stream = useExecutionStream(activeExecutionId, {
258
+ store: conversationStore,
259
+ });
252
260
 
253
261
  // Clear pendingExecutionId once the execution appears in the fetched list
254
262
  useEffect(() => {
@@ -291,7 +299,8 @@ export function useSessionConversation(
291
299
  );
292
300
  }, [executions, activeExecutionId]);
293
301
 
294
- const activeStreamExecution = stream.execution ?? fetchedActiveExecution;
302
+ const activeStreamExecution =
303
+ stream.execution ?? fetchedActiveExecution;
295
304
 
296
305
  const activePhase = useMemo<ExecutionPhase | null>(() => {
297
306
  if (!activeExecutionId) return null;
@@ -73,6 +73,7 @@ export function useSessionExecutions(
73
73
  : null,
74
74
  [sessionId, stigmer],
75
75
  [] as AgentExecution[],
76
+ { cacheKey: sessionId ? `session-executions:${sessionId}` : undefined },
76
77
  );
77
78
 
78
79
  return { executions, isLoading, isRefetching, error, refetch };
@@ -253,7 +253,7 @@ export function useSessionPageFlow(
253
253
 
254
254
  sessionVariables.clear();
255
255
  },
256
- [conv, modelId, workspace, mcpServerUsages, skillRefs, sessionVariables, resolution, agentRef, sessionInstanceId, stigmer],
256
+ [conv.sendFollowUp, modelId, workspace, mcpServerUsages, skillRefs, sessionVariables.clear, resolution, agentRef, sessionInstanceId, stigmer],
257
257
  );
258
258
 
259
259
  // -------------------------------------------------------------------------