@assistant-ui/core 0.3.16 → 0.3.18

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 (474) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal.d.ts +4 -2
  8. package/dist/internal.js +4 -2
  9. package/dist/model-context/frame/host.d.ts.map +1 -1
  10. package/dist/model-context/frame/host.js +10 -4
  11. package/dist/model-context/frame/host.js.map +1 -1
  12. package/dist/model-context/frame/provider.d.ts +5 -0
  13. package/dist/model-context/frame/provider.d.ts.map +1 -1
  14. package/dist/model-context/frame/provider.js +101 -28
  15. package/dist/model-context/frame/provider.js.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.js +16 -9
  18. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  19. package/dist/model-context/registry.d.ts.map +1 -1
  20. package/dist/model-context/registry.js +3 -5
  21. package/dist/model-context/registry.js.map +1 -1
  22. package/dist/model-context/tool-call-text.d.ts +26 -0
  23. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  24. package/dist/model-context/tool-call-text.js +18 -0
  25. package/dist/model-context/tool-call-text.js.map +1 -0
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/RuntimeAdapter.js +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  32. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  33. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  34. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.js +63 -57
  36. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  37. package/dist/react/client/Interactables.d.ts.map +1 -1
  38. package/dist/react/client/Interactables.js +25 -28
  39. package/dist/react/client/Interactables.js.map +1 -1
  40. package/dist/react/client/RemoteThreadList.d.ts +16 -0
  41. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  42. package/dist/react/client/RemoteThreadList.js +354 -242
  43. package/dist/react/client/RemoteThreadList.js.map +1 -1
  44. package/dist/react/client/Tools.d.ts.map +1 -1
  45. package/dist/react/client/Tools.js +24 -18
  46. package/dist/react/client/Tools.js.map +1 -1
  47. package/dist/react/index.d.ts +2 -1
  48. package/dist/react/index.js +2 -1
  49. package/dist/react/interactables-legacy/Interactables.js +246 -125
  50. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  51. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  52. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  53. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  54. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  55. package/dist/react/model-context/makeAssistantTool.js +1 -1
  56. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  57. package/dist/react/model-context/toolbox.d.ts +2 -14
  58. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  59. package/dist/react/model-context/toolbox.js +1 -13
  60. package/dist/react/model-context/toolbox.js.map +1 -1
  61. package/dist/react/model-context/useAssistantContext.js +37 -7
  62. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  63. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  64. package/dist/react/model-context/useAssistantTool.js +1 -1
  65. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  66. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  67. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  68. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  69. package/dist/react/model-context/useInlineRender.js +10 -36
  70. package/dist/react/model-context/useInlineRender.js.map +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  75. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  76. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  78. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  79. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  80. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  81. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  82. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  83. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  84. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  85. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  86. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  87. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  88. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  89. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  90. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  91. package/dist/react/primitives/message/MessageParts.js +3 -3
  92. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  93. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  94. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  95. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +6 -4
  97. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  98. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +59 -29
  99. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  100. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +11 -6
  101. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +160 -143
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadResource.d.ts +2 -6
  105. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  106. package/dist/react/runtimes/RemoteThreadResource.js +15 -13
  107. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  108. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  109. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  110. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  111. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  112. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +424 -52
  113. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  114. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  115. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  116. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  118. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  119. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +160 -0
  120. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  122. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  124. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.js +1 -1
  126. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  127. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  128. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +77 -29
  131. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  132. package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
  133. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  134. package/dist/react/types/scopes/tools.d.ts +2 -0
  135. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  136. package/dist/runtime/api/message-part-runtime.d.ts +2 -2
  137. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.js +1 -1
  139. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  140. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  141. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  142. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  143. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.d.ts +12 -1
  145. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  146. package/dist/runtime/api/thread-list-runtime.js +23 -14
  147. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  148. package/dist/runtime/api/thread-runtime.d.ts +5 -1
  149. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  150. package/dist/runtime/api/thread-runtime.js +1 -1
  151. package/dist/runtime/api/thread-runtime.js.map +1 -1
  152. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  153. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  154. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -2
  156. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  157. package/dist/runtime/base/base-thread-runtime-core.js +8 -7
  158. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  159. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +18 -3
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/utils/auto-status.d.ts +1 -1
  166. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  167. package/dist/runtime/utils/auto-status.js +6 -2
  168. package/dist/runtime/utils/auto-status.js.map +1 -1
  169. package/dist/runtime/utils/external-message-conversion.d.ts +3 -2
  170. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.js +5 -4
  172. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  173. package/dist/runtime/utils/message-repository.d.ts +1 -0
  174. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  175. package/dist/runtime/utils/message-repository.js +15 -6
  176. package/dist/runtime/utils/message-repository.js.map +1 -1
  177. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  178. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  179. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  180. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.d.ts +2 -10
  182. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  183. package/dist/runtime/utils/thread-message-like.js +1 -1
  184. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  185. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  186. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  187. package/dist/runtime/utils/tool-call-tree.js +85 -0
  188. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  189. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  190. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  191. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  192. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  193. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -1
  195. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +34 -28
  197. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  198. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  199. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  200. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -2
  202. package/dist/runtimes/internal.js +2 -2
  203. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  204. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.js +3 -1
  206. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  208. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  209. package/dist/runtimes/remote-thread-list/remote-thread-state.js +148 -43
  210. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  211. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  212. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  213. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  214. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  215. package/dist/runtimes/remote-thread-list/title.d.ts +11 -0
  216. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  217. package/dist/runtimes/remote-thread-list/title.js +14 -0
  218. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  219. package/dist/runtimes/remote-thread-list/types.d.ts +16 -5
  220. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  221. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  222. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  223. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +97 -60
  224. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  225. package/dist/store/clients/external-thread.d.ts +1 -1
  226. package/dist/store/clients/external-thread.d.ts.map +1 -1
  227. package/dist/store/clients/external-thread.js +30 -7
  228. package/dist/store/clients/external-thread.js.map +1 -1
  229. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  230. package/dist/store/clients/model-context-client.js +2 -1
  231. package/dist/store/clients/model-context-client.js.map +1 -1
  232. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  233. package/dist/store/clients/single-thread-list.js +2 -1
  234. package/dist/store/clients/single-thread-list.js.map +1 -1
  235. package/dist/store/clients/suggestions.d.ts.map +1 -1
  236. package/dist/store/clients/suggestions.js +19 -6
  237. package/dist/store/clients/suggestions.js.map +1 -1
  238. package/dist/store/clients/thread-message-client.js +1 -1
  239. package/dist/store/clients/thread-selection-events.js +1 -1
  240. package/dist/store/index.d.ts +2 -1
  241. package/dist/store/index.js +2 -1
  242. package/dist/store/internal.d.ts +2 -2
  243. package/dist/store/internal.js +2 -2
  244. package/dist/store/primitive-predicates.d.ts +4 -1
  245. package/dist/store/primitive-predicates.d.ts.map +1 -1
  246. package/dist/store/primitive-predicates.js +9 -1
  247. package/dist/store/primitive-predicates.js.map +1 -1
  248. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  249. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  250. package/dist/store/runtime-clients/composer-runtime-client.js +98 -62
  251. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  252. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  253. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  254. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  255. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  256. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  257. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  258. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  259. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  260. package/dist/store/runtime-clients/thread-list-runtime-client.js +128 -107
  261. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  262. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/thread-runtime-client.js +194 -122
  264. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  266. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  267. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  268. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  269. package/dist/store/scopes/composer.d.ts +10 -2
  270. package/dist/store/scopes/composer.d.ts.map +1 -1
  271. package/dist/store/scopes/message.d.ts +30 -1
  272. package/dist/store/scopes/message.d.ts.map +1 -1
  273. package/dist/store/scopes/part.d.ts +6 -3
  274. package/dist/store/scopes/part.d.ts.map +1 -1
  275. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  276. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  277. package/dist/store/scopes/thread.d.ts +8 -0
  278. package/dist/store/scopes/thread.d.ts.map +1 -1
  279. package/dist/store/scopes/threads.d.ts +1 -0
  280. package/dist/store/scopes/threads.d.ts.map +1 -1
  281. package/dist/subscribable/subscribable.d.ts +8 -1
  282. package/dist/subscribable/subscribable.d.ts.map +1 -1
  283. package/dist/subscribable/subscribable.js +24 -16
  284. package/dist/subscribable/subscribable.js.map +1 -1
  285. package/dist/types/attachment.d.ts +2 -1
  286. package/dist/types/attachment.d.ts.map +1 -1
  287. package/dist/types/attachment.js +2 -1
  288. package/dist/types/attachment.js.map +1 -1
  289. package/dist/types/message.d.ts +40 -1
  290. package/dist/types/message.d.ts.map +1 -1
  291. package/dist/types/message.js +7 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/abortable-iterable.d.ts +19 -0
  294. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  295. package/dist/utils/abortable-iterable.js +72 -0
  296. package/dist/utils/abortable-iterable.js.map +1 -0
  297. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  298. package/dist/utils/composite-context-provider.js +20 -2
  299. package/dist/utils/composite-context-provider.js.map +1 -1
  300. package/dist/utils/record.d.ts +11 -0
  301. package/dist/utils/record.d.ts.map +1 -0
  302. package/dist/utils/record.js +12 -0
  303. package/dist/utils/record.js.map +1 -0
  304. package/package.json +12 -17
  305. package/src/adapters/attachment.test.ts +50 -1
  306. package/src/adapters/attachment.ts +7 -4
  307. package/src/index.ts +7 -1
  308. package/src/internal.ts +12 -0
  309. package/src/model-context/frame/host.test.ts +24 -0
  310. package/src/model-context/frame/host.ts +10 -4
  311. package/src/model-context/frame/provider.test.ts +288 -1
  312. package/src/model-context/frame/provider.ts +148 -30
  313. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  314. package/src/model-context/interactable-composer-metadata.ts +9 -3
  315. package/src/model-context/registry.test.ts +12 -0
  316. package/src/model-context/registry.ts +3 -2
  317. package/src/model-context/tool-call-text.test.ts +67 -0
  318. package/src/model-context/tool-call-text.ts +67 -0
  319. package/src/model-context/types.test.ts +25 -0
  320. package/src/model-context/types.ts +9 -4
  321. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  322. package/src/react/RuntimeAdapter.test.ts +34 -0
  323. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  324. package/src/react/client/InMemoryThreadList.ts +1 -0
  325. package/src/react/client/Interactables.test.ts +28 -0
  326. package/src/react/client/Interactables.ts +15 -13
  327. package/src/react/client/RemoteThreadList.background.test.ts +526 -0
  328. package/src/react/client/RemoteThreadList.concurrent.test.tsx +315 -0
  329. package/src/react/client/RemoteThreadList.test.ts +617 -3
  330. package/src/react/client/RemoteThreadList.ts +433 -167
  331. package/src/react/client/Tools.ts +29 -12
  332. package/src/react/index.ts +4 -0
  333. package/src/react/interactables-legacy/Interactables.ts +16 -16
  334. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  335. package/src/react/model-context/makeAssistantTool.ts +2 -2
  336. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  337. package/src/react/model-context/toolbox.test.ts +9 -9
  338. package/src/react/model-context/toolbox.ts +9 -36
  339. package/src/react/model-context/useAssistantContext.ts +4 -2
  340. package/src/react/model-context/useAssistantTool.ts +2 -2
  341. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  342. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  343. package/src/react/model-context/useInlineRender.ts +14 -10
  344. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  345. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  346. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  347. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  348. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  349. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  350. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  351. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  352. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  353. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  354. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  355. package/src/react/primitives/message/MessageParts.tsx +3 -3
  356. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  357. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  358. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  359. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +127 -1
  360. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  361. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +99 -35
  362. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  363. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +274 -0
  364. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +223 -152
  365. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  366. package/src/react/runtimes/RemoteThreadResource.ts +20 -16
  367. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  368. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  369. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  370. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +546 -5
  371. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +546 -103
  372. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  373. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +103 -0
  374. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +237 -0
  375. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  376. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  377. package/src/react/runtimes/external-message-converter.ts +1 -0
  378. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  379. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  380. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  381. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  382. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  383. package/src/react/runtimes/useRemoteThreadListRuntime.ts +57 -6
  384. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  385. package/src/react/types/scopes/tools.ts +2 -0
  386. package/src/runtime/api/message-part-runtime.ts +3 -3
  387. package/src/runtime/api/message-runtime.test.ts +75 -0
  388. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  389. package/src/runtime/api/thread-list-runtime.ts +46 -12
  390. package/src/runtime/api/thread-runtime.test.ts +78 -0
  391. package/src/runtime/api/thread-runtime.ts +8 -1
  392. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  393. package/src/runtime/base/base-thread-runtime-core.test.ts +149 -4
  394. package/src/runtime/base/base-thread-runtime-core.ts +16 -8
  395. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  396. package/src/runtime/interfaces/thread-list-runtime-core.ts +25 -2
  397. package/src/runtime/interfaces/thread-runtime-core.ts +17 -1
  398. package/src/runtime/utils/auto-status.test.ts +57 -0
  399. package/src/runtime/utils/auto-status.ts +13 -1
  400. package/src/runtime/utils/external-message-conversion.test.ts +165 -0
  401. package/src/runtime/utils/external-message-conversion.ts +18 -2
  402. package/src/runtime/utils/message-repository.ts +29 -17
  403. package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
  404. package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
  405. package/src/runtime/utils/thread-message-like.ts +3 -11
  406. package/src/runtime/utils/tool-call-tree.test.ts +191 -0
  407. package/src/runtime/utils/tool-call-tree.ts +97 -0
  408. package/src/runtimes/external-store/external-store-adapter.ts +38 -2
  409. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +56 -33
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/internal.ts +1 -0
  413. package/src/runtimes/local/local-thread-runtime-core.test.ts +29 -3
  414. package/src/runtimes/local/local-thread-runtime-core.ts +5 -1
  415. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +238 -0
  416. package/src/runtimes/remote-thread-list/remote-thread-state.ts +223 -44
  417. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  418. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  419. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  420. package/src/runtimes/remote-thread-list/title.ts +16 -0
  421. package/src/runtimes/remote-thread-list/types.ts +16 -5
  422. package/src/runtimes/tool-invocations/EDGE_CASES.md +110 -5
  423. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +750 -136
  424. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +144 -91
  425. package/src/store/clients/external-thread.ts +62 -11
  426. package/src/store/clients/model-context-client.ts +5 -1
  427. package/src/store/clients/single-thread-list.ts +1 -0
  428. package/src/store/clients/suggestions.test.ts +195 -1
  429. package/src/store/clients/suggestions.ts +45 -34
  430. package/src/store/index.ts +4 -0
  431. package/src/store/internal.ts +2 -0
  432. package/src/store/primitive-predicates.ts +35 -0
  433. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  434. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  435. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  436. package/src/store/runtime-clients/thread-list-runtime-client.ts +17 -2
  437. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  438. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  439. package/src/store/scopes/composer.ts +16 -4
  440. package/src/store/scopes/message.ts +14 -0
  441. package/src/store/scopes/part.ts +6 -3
  442. package/src/store/scopes/thread-list-item.ts +5 -2
  443. package/src/store/scopes/thread.ts +4 -0
  444. package/src/store/scopes/threads.ts +1 -0
  445. package/src/subscribable/subscribable.test.ts +50 -0
  446. package/src/subscribable/subscribable.ts +33 -21
  447. package/src/tests/MessageRepository.test.ts +116 -0
  448. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  449. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  450. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +158 -0
  451. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  452. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  453. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  454. package/src/tests/attachment-adapters.test.ts +14 -0
  455. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  456. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  457. package/src/tests/external-store-repository-instance.test.ts +166 -0
  458. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  459. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +49 -34
  460. package/src/tests/external-thread-attachments.test.tsx +224 -1
  461. package/src/tests/external-thread-parity.test.tsx +23 -0
  462. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  463. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  464. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  465. package/src/tests/thread-switch-events.test.tsx +475 -2
  466. package/src/tests/tools-scope-migration.test.tsx +28 -0
  467. package/src/types/attachment.ts +4 -0
  468. package/src/types/message.ts +50 -2
  469. package/src/utils/abortable-iterable.test.ts +213 -0
  470. package/src/utils/abortable-iterable.ts +93 -0
  471. package/src/utils/composite-context-provider.test.ts +25 -0
  472. package/src/utils/composite-context-provider.ts +23 -2
  473. package/src/utils/record.test.ts +48 -0
  474. package/src/utils/record.ts +11 -0
@@ -6,6 +6,7 @@ import {
6
6
  useMemo,
7
7
  } from "react";
8
8
  import { RenderChildrenWithAccessor, useAuiState } from "@assistant-ui/store";
9
+ import { useShallowSelector } from "@assistant-ui/store/internal";
9
10
  import type { ThreadListItemState } from "../../../store/scopes/thread-list-item";
10
11
  import { ThreadListItemByIndexProvider } from "../../providers/ThreadListItemByIndexProvider";
11
12
 
@@ -64,15 +65,17 @@ const ThreadListPrimitiveItemsInner: FC<{
64
65
  archived: boolean;
65
66
  children: (value: { threadListItem: ThreadListItemState }) => ReactNode;
66
67
  }> = ({ archived, children }) => {
67
- const contentLength = useAuiState((s) =>
68
- archived ? s.threads.archivedThreadIds.length : s.threads.threadIds.length,
68
+ const threadIds = useAuiState(
69
+ useShallowSelector((s) =>
70
+ archived ? s.threads.archivedThreadIds : s.threads.threadIds,
71
+ ),
69
72
  );
70
73
 
71
74
  return useMemo(
72
75
  () =>
73
- Array.from({ length: contentLength }, (_, index) => (
76
+ threadIds.map((threadId, index) => (
74
77
  <ThreadListItemByIndexProvider
75
- key={index}
78
+ key={threadId}
76
79
  index={index}
77
80
  archived={archived}
78
81
  >
@@ -91,7 +94,7 @@ const ThreadListPrimitiveItemsInner: FC<{
91
94
  </RenderChildrenWithAccessor>
92
95
  </ThreadListItemByIndexProvider>
93
96
  )),
94
- [contentLength, archived, children],
97
+ [threadIds, archived, children],
95
98
  );
96
99
  };
97
100
 
@@ -0,0 +1,172 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
3
+ import type { ThreadRuntimeCore } from "../../runtime/interfaces/thread-runtime-core";
4
+ import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
5
+
6
+ const makeManager = () =>
7
+ new RemoteThreadListHookInstanceManager(
8
+ () => ({}) as never,
9
+ {} as ThreadListRuntimeCore,
10
+ );
11
+
12
+ // No AdapterSink attaches a runtime here, so the start and restart promises
13
+ // stay pending or reject on stop; neither is what these tests are about.
14
+ const start = (manager: RemoteThreadListHookInstanceManager, id: string) => {
15
+ manager.startThreadRuntime(id).catch(() => {});
16
+ };
17
+ const restart = (manager: RemoteThreadListHookInstanceManager, id: string) => {
18
+ manager.__internal_restartThreadRuntime(id).catch(() => {});
19
+ };
20
+
21
+ const publishedSignal = (
22
+ manager: RemoteThreadListHookInstanceManager,
23
+ id: string,
24
+ ) => {
25
+ const { hostStore } = manager as unknown as {
26
+ hostStore: {
27
+ getState: () => {
28
+ threads: readonly { id: string; destroySignal: AbortSignal }[];
29
+ };
30
+ };
31
+ };
32
+ return hostStore.getState().threads.find((thread) => thread.id === id)
33
+ ?.destroySignal;
34
+ };
35
+
36
+ describe("RemoteThreadListHookInstanceManager destroy signal", () => {
37
+ it("publishes a live signal for each started thread", () => {
38
+ const manager = makeManager();
39
+ start(manager, "thread-1");
40
+
41
+ expect(publishedSignal(manager, "thread-1")?.aborted).toBe(false);
42
+ });
43
+
44
+ it("aborts the thread's signal when its runtime stops", () => {
45
+ const manager = makeManager();
46
+ start(manager, "thread-1");
47
+ const signal = publishedSignal(manager, "thread-1")!;
48
+
49
+ manager.stopThreadRuntime("thread-1");
50
+
51
+ expect(signal.aborted).toBe(true);
52
+ });
53
+
54
+ it("leaves sibling threads untouched when one stops", () => {
55
+ const manager = makeManager();
56
+ start(manager, "thread-1");
57
+ start(manager, "thread-2");
58
+ const sibling = publishedSignal(manager, "thread-2")!;
59
+
60
+ manager.stopThreadRuntime("thread-1");
61
+
62
+ expect(sibling.aborted).toBe(false);
63
+ });
64
+
65
+ it("hands the next generation a fresh signal on restart", () => {
66
+ const manager = makeManager();
67
+ start(manager, "thread-1");
68
+ const first = publishedSignal(manager, "thread-1")!;
69
+
70
+ restart(manager, "thread-1");
71
+ const second = publishedSignal(manager, "thread-1")!;
72
+
73
+ expect(first.aborted).toBe(true);
74
+ expect(second).not.toBe(first);
75
+ expect(second.aborted).toBe(false);
76
+ });
77
+
78
+ it("aborts every thread when the manager is disposed", () => {
79
+ const manager = makeManager();
80
+ start(manager, "thread-1");
81
+ start(manager, "thread-2");
82
+ const signals = [
83
+ publishedSignal(manager, "thread-1")!,
84
+ publishedSignal(manager, "thread-2")!,
85
+ ];
86
+
87
+ manager.__internal_dispose();
88
+
89
+ expect(signals.map((signal) => signal.aborted)).toEqual([true, true]);
90
+ });
91
+ });
92
+
93
+ const makeRunningRuntime = () => {
94
+ const subscribers = new Set<() => void>();
95
+ const eventListeners = new Map<string, Set<() => void>>();
96
+ const runtime = {
97
+ isRunning: true,
98
+ messages: [],
99
+ subscribe: (callback: () => void) => {
100
+ subscribers.add(callback);
101
+ return () => subscribers.delete(callback);
102
+ },
103
+ unstable_on: (event: string, callback: () => void) => {
104
+ let listeners = eventListeners.get(event);
105
+ if (!listeners) {
106
+ listeners = new Set();
107
+ eventListeners.set(event, listeners);
108
+ }
109
+ listeners.add(callback);
110
+ return () => listeners.delete(callback);
111
+ },
112
+ } as unknown as ThreadRuntimeCore & { isRunning: boolean };
113
+
114
+ return {
115
+ runtime,
116
+ // what a stopOnClientDestroy consumer does when the destroy signal fires
117
+ stop: () => {
118
+ runtime.isRunning = false;
119
+ for (const callback of eventListeners.get("runEnd") ?? []) callback();
120
+ for (const callback of subscribers) callback();
121
+ },
122
+ };
123
+ };
124
+
125
+ const publish = (
126
+ manager: RemoteThreadListHookInstanceManager,
127
+ threadId: string,
128
+ runtime: ThreadRuntimeCore,
129
+ ) => {
130
+ const internals = manager as unknown as {
131
+ instances: Map<string, { generation: number }>;
132
+ _publishThreadRuntime: (
133
+ threadId: string,
134
+ runtime: ThreadRuntimeCore,
135
+ generation: number,
136
+ ) => void;
137
+ };
138
+ internals._publishThreadRuntime(
139
+ threadId,
140
+ runtime,
141
+ internals.instances.get(threadId)!.generation,
142
+ );
143
+ };
144
+
145
+ describe("RemoteThreadListHookInstanceManager restart teardown", () => {
146
+ it("keeps the outgoing generation's terminal events off the thread's subscribers", () => {
147
+ const manager = makeManager();
148
+ start(manager, "thread-1");
149
+ const { runtime, stop } = makeRunningRuntime();
150
+ publish(manager, "thread-1", runtime);
151
+
152
+ const signal = publishedSignal(manager, "thread-1")!;
153
+ signal.addEventListener("abort", stop, { once: true });
154
+
155
+ const events: string[] = [];
156
+ manager.__internal_subscribeThreadEvents((event) =>
157
+ events.push(event.type),
158
+ );
159
+ const runningChanges: boolean[] = [];
160
+ manager.__internal_subscribeRunningChanged(() =>
161
+ runningChanges.push(manager.__internal_isThreadRunning("thread-1")),
162
+ );
163
+ expect(manager.__internal_isThreadRunning("thread-1")).toBe(true);
164
+
165
+ restart(manager, "thread-1");
166
+
167
+ expect(signal.aborted).toBe(true);
168
+ expect(events).toEqual([]);
169
+ expect(runningChanges).toEqual([]);
170
+ expect(manager.__internal_isThreadRunning("thread-1")).toBe(true);
171
+ });
172
+ });
@@ -5,9 +5,12 @@ import type { ThreadMessage } from "../../types/message";
5
5
  import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
6
6
 
7
7
  const makeRuntime = (
8
- initial: Partial<Pick<ThreadRuntimeCore, "isRunning" | "messages">> = {},
8
+ initial: Partial<Pick<ThreadRuntimeCore, "isRunning" | "messages">> & {
9
+ initialized?: boolean;
10
+ } = {},
9
11
  ) => {
10
12
  const subscribers = new Set<() => void>();
13
+ const eventListeners = new Map<string, Set<() => void>>();
11
14
  const runtime = {
12
15
  isRunning: initial.isRunning,
13
16
  messages: initial.messages ?? [],
@@ -15,6 +18,23 @@ const makeRuntime = (
15
18
  subscribers.add(callback);
16
19
  return () => subscribers.delete(callback);
17
20
  },
21
+ unstable_on: (event: string, callback: () => void) => {
22
+ let listeners = eventListeners.get(event);
23
+ if (!listeners) {
24
+ listeners = new Set();
25
+ eventListeners.set(event, listeners);
26
+ }
27
+ listeners.add(callback);
28
+ // mirrors the real core: initialize latches, so a subscriber that
29
+ // attaches after initialization gets a deferred replay
30
+ if (event === "initialize" && initial.initialized) {
31
+ const latched = listeners;
32
+ queueMicrotask(() => {
33
+ if (latched.has(callback)) callback();
34
+ });
35
+ }
36
+ return () => listeners.delete(callback);
37
+ },
18
38
  } as unknown as ThreadRuntimeCore & {
19
39
  isRunning: boolean | undefined;
20
40
  messages: readonly ThreadMessage[];
@@ -23,6 +43,14 @@ const makeRuntime = (
23
43
  return {
24
44
  runtime,
25
45
  subscriberCount: () => subscribers.size,
46
+ eventListenerCount: () =>
47
+ [...eventListeners.values()].reduce(
48
+ (total, listeners) => total + listeners.size,
49
+ 0,
50
+ ),
51
+ emit: (event: string) => {
52
+ for (const callback of eventListeners.get(event) ?? []) callback();
53
+ },
26
54
  setRunning: (isRunning: boolean | undefined) => {
27
55
  runtime.isRunning = isRunning;
28
56
  for (const callback of subscribers) callback();
@@ -127,6 +155,39 @@ describe("RemoteThreadListHookInstanceManager run tracking", () => {
127
155
  expect(onChange).toHaveBeenCalledTimes(2);
128
156
  });
129
157
 
158
+ it("notifies every running subscriber when one throws", () => {
159
+ const manager = makeManager();
160
+ start(manager, "thread-1");
161
+ const thread = makeRuntime({ isRunning: false });
162
+ publish(manager, "thread-1", thread.runtime);
163
+ const error = new Error("listener failed");
164
+ manager.__internal_subscribeRunningChanged(() => {
165
+ throw error;
166
+ });
167
+ const laterSubscriber = vi.fn();
168
+ manager.__internal_subscribeRunningChanged(laterSubscriber);
169
+
170
+ expect(() => thread.setRunning(true)).toThrow(error);
171
+ expect(laterSubscriber).toHaveBeenCalledOnce();
172
+ });
173
+
174
+ it("notifies every replacement subscriber when one throws", () => {
175
+ const manager = makeManager();
176
+ start(manager, "thread-1");
177
+ publish(manager, "thread-1", makeRuntime().runtime);
178
+ const error = new Error("listener failed");
179
+ manager.__internal_subscribeRuntimeReplaced(() => {
180
+ throw error;
181
+ });
182
+ const laterSubscriber = vi.fn();
183
+ manager.__internal_subscribeRuntimeReplaced(laterSubscriber);
184
+
185
+ expect(() => publish(manager, "thread-1", makeRuntime().runtime)).toThrow(
186
+ error,
187
+ );
188
+ expect(laterSubscriber).toHaveBeenCalledOnce();
189
+ });
190
+
130
191
  it("moves tracking to the runtime a restart publishes", () => {
131
192
  const manager = makeManager();
132
193
  start(manager, "thread-1");
@@ -152,6 +213,71 @@ describe("RemoteThreadListHookInstanceManager run tracking", () => {
152
213
  manager.stopThreadRuntime("thread-1");
153
214
 
154
215
  expect(thread.subscriberCount()).toBe(0);
216
+ expect(thread.eventListenerCount()).toBe(0);
155
217
  expect(manager.__internal_isThreadRunning("thread-1")).toBe(false);
156
218
  });
219
+
220
+ it("forwards every lifecycle event from every tracked thread with its thread id", () => {
221
+ const manager = makeManager();
222
+ const events: unknown[] = [];
223
+ manager.__internal_subscribeThreadEvents((event) => events.push(event));
224
+
225
+ start(manager, "thread-1");
226
+ start(manager, "thread-2");
227
+ const first = makeRuntime();
228
+ const second = makeRuntime();
229
+ publish(manager, "thread-1", first.runtime);
230
+ publish(manager, "thread-2", second.runtime);
231
+
232
+ second.emit("runStart");
233
+ first.emit("runEnd");
234
+ first.emit("initialize");
235
+ second.emit("modelContextUpdate");
236
+
237
+ expect(events).toEqual([
238
+ { threadId: "thread-2", type: "runStart" },
239
+ { threadId: "thread-1", type: "runEnd" },
240
+ { threadId: "thread-1", type: "initialize" },
241
+ { threadId: "thread-2", type: "modelContextUpdate" },
242
+ ]);
243
+ });
244
+
245
+ it("replays a latched initialize from the surviving runtime after a restart", async () => {
246
+ const manager = makeManager();
247
+ const events: unknown[] = [];
248
+ manager.__internal_subscribeThreadEvents((event) => events.push(event));
249
+
250
+ start(manager, "thread-1");
251
+ const before = makeRuntime({ initialized: true });
252
+ publish(manager, "thread-1", before.runtime);
253
+ const after = makeRuntime({ initialized: true });
254
+ publish(manager, "thread-1", after.runtime);
255
+ await new Promise<void>((resolve) => queueMicrotask(resolve));
256
+
257
+ expect(before.eventListenerCount()).toBe(0);
258
+ expect(events).toEqual([{ threadId: "thread-1", type: "initialize" }]);
259
+ });
260
+
261
+ it("stops forwarding lifecycle events from a runtime a restart replaced", () => {
262
+ const manager = makeManager();
263
+ const events: unknown[] = [];
264
+ manager.__internal_subscribeThreadEvents((event) => events.push(event));
265
+
266
+ start(manager, "thread-1");
267
+ const before = makeRuntime();
268
+ publish(manager, "thread-1", before.runtime);
269
+ const after = makeRuntime();
270
+ publish(manager, "thread-1", after.runtime);
271
+
272
+ before.emit("runEnd");
273
+ before.emit("initialize");
274
+ after.emit("runEnd");
275
+ after.emit("modelContextUpdate");
276
+
277
+ expect(before.eventListenerCount()).toBe(0);
278
+ expect(events).toEqual([
279
+ { threadId: "thread-1", type: "runEnd" },
280
+ { threadId: "thread-1", type: "modelContextUpdate" },
281
+ ]);
282
+ });
157
283
  });
@@ -37,13 +37,19 @@ describe("RemoteThreadListHookInstanceManager", () => {
37
37
  const internals = manager as unknown as {
38
38
  instances: Map<
39
39
  string,
40
- { runtime: typeof runtime; generation: number; isRunning: boolean }
40
+ {
41
+ runtime: typeof runtime;
42
+ generation: number;
43
+ isRunning: boolean;
44
+ destroy: AbortController;
45
+ }
41
46
  >;
42
47
  };
43
48
  internals.instances.set("thread-1", {
44
49
  runtime,
45
50
  generation: 0,
46
51
  isRunning: false,
52
+ destroy: new AbortController(),
47
53
  });
48
54
 
49
55
  const appendPromise = runtime.append({
@@ -10,17 +10,28 @@ import {
10
10
  type ComponentType,
11
11
  Fragment,
12
12
  } from "react";
13
- import { create } from "zustand";
14
13
  import { useResources, withKey } from "@assistant-ui/tap";
15
14
  import type { AssistantClient } from "@assistant-ui/store";
16
15
  import { ThreadListItemRuntimeProvider } from "../providers/ThreadListItemRuntimeProvider";
17
- import type { ThreadRuntimeCore } from "../../runtime/interfaces/thread-runtime-core";
18
- import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
16
+ import type {
17
+ ThreadRuntimeCore,
18
+ ThreadRuntimeEventType,
19
+ } from "../../runtime/interfaces/thread-runtime-core";
20
+ import type {
21
+ ThreadListRuntimeCore,
22
+ ThreadListRuntimeEvent,
23
+ } from "../../runtime/interfaces/thread-list-runtime-core";
19
24
  import type { Unsubscribe } from "../../types/unsubscribe";
20
- import { BaseSubscribable } from "../../subscribable/subscribable";
25
+ import {
26
+ BaseSubscribable,
27
+ notifySubscribers,
28
+ WritableSubscribable,
29
+ } from "../../subscribable/subscribable";
30
+ import { useSubscribable } from "../../store/runtime-clients/useSubscribable";
21
31
  import { getThreadRuntimeCoreIsRunning } from "../../runtime/api/thread-runtime";
22
32
  import { ThreadListRuntimeImpl } from "../../runtime/api/thread-list-runtime";
23
33
  import { invalidateThreadRuntime } from "../../runtime/utils/thread-runtime-lifecycle";
34
+ import { notifyEventListeners } from "../../utils/notify-event-listeners";
24
35
  import {
25
36
  useRuntimeAdapters,
26
37
  type RuntimeAdapters,
@@ -30,12 +41,22 @@ import {
30
41
  type RemoteThreadListHook,
31
42
  } from "./RemoteThreadResource";
32
43
 
44
+ const THREAD_EVENTS = [
45
+ "runStart",
46
+ "runEnd",
47
+ "initialize",
48
+ "modelContextUpdate",
49
+ ] as const satisfies readonly ThreadRuntimeEventType[];
50
+
33
51
  type RemoteThreadListHookInstance = {
34
52
  runtime?: ThreadRuntimeCore | undefined;
35
53
  publishedGeneration?: number | undefined;
36
54
  generation: number;
37
55
  isRunning: boolean;
38
56
  unsubscribeRunning?: Unsubscribe | undefined;
57
+ // Permanent teardown of the thread's runtime, aborted when the thread is
58
+ // stopped or its runtime restarted. A soft unmount leaves it armed.
59
+ destroy: AbortController;
39
60
  };
40
61
 
41
62
  type AdapterSnapshot = {
@@ -44,7 +65,11 @@ type AdapterSnapshot = {
44
65
  };
45
66
 
46
67
  type HostSnapshot = {
47
- threads: readonly { id: string; generation: number }[];
68
+ threads: readonly {
69
+ id: string;
70
+ generation: number;
71
+ destroySignal: AbortSignal;
72
+ }[];
48
73
  hookEpoch: number;
49
74
  };
50
75
 
@@ -59,14 +84,14 @@ const ProviderRenderDetector: FC<{
59
84
 
60
85
  export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
61
86
  private runtimeHook: RemoteThreadListHook;
62
- private readonly adapterStore = create<AdapterSnapshot>(() => ({
87
+ private readonly adapterStore = new WritableSubscribable<AdapterSnapshot>({
63
88
  defaultAdapters: null,
64
89
  threadAdapters: new Map(),
65
- }));
66
- private readonly hostStore = create<HostSnapshot>(() => ({
90
+ });
91
+ private readonly hostStore = new WritableSubscribable<HostSnapshot>({
67
92
  threads: [],
68
93
  hookEpoch: 0,
69
- }));
94
+ });
70
95
  private readonly pendingThreadAdapters = new Map<
71
96
  string,
72
97
  RuntimeAdapters | null
@@ -112,6 +137,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
112
137
  this.instances.set(threadId, {
113
138
  generation,
114
139
  isRunning: false,
140
+ destroy: new AbortController(),
115
141
  });
116
142
  this._syncHostThreads();
117
143
  }
@@ -124,6 +150,14 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
124
150
  if (!instance) return this.startThreadRuntime(threadId);
125
151
 
126
152
  if (instance.runtime) invalidateThreadRuntime(instance.runtime);
153
+ // Detach before aborting, as stopThreadRuntime does: the abort runs the
154
+ // destroy listeners synchronously, and a listener that stops the outgoing
155
+ // runtime would otherwise emit that generation's terminal events through
156
+ // the subscription the next generation is about to reuse.
157
+ instance.unsubscribeRunning?.();
158
+ instance.unsubscribeRunning = undefined;
159
+ instance.destroy.abort();
160
+ instance.destroy = new AbortController();
127
161
  instance.generation = this.nextGeneration++;
128
162
  this._syncHostThreads();
129
163
  this._notifySubscribers();
@@ -148,6 +182,17 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
148
182
  return () => this.runningSubscribers.delete(callback);
149
183
  }
150
184
 
185
+ private threadEventSubscribers = new Set<
186
+ (event: ThreadListRuntimeEvent) => void
187
+ >();
188
+
189
+ public __internal_subscribeThreadEvents(
190
+ callback: (event: ThreadListRuntimeEvent) => void,
191
+ ): Unsubscribe {
192
+ this.threadEventSubscribers.add(callback);
193
+ return () => this.threadEventSubscribers.delete(callback);
194
+ }
195
+
151
196
  private _publish = (
152
197
  threadId: string,
153
198
  runtime: ThreadRuntimeCore,
@@ -170,11 +215,11 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
170
215
  instance.runtime = runtime;
171
216
  instance.publishedGeneration = generation;
172
217
  if (previousRuntime !== runtime) {
173
- this._trackRunning(instance);
218
+ this._trackRunning(threadId, instance);
174
219
  }
175
220
  this._notifySubscribers();
176
221
  if (previousRuntime !== undefined && previousRuntime !== runtime) {
177
- for (const callback of this.replacedSubscribers) callback();
222
+ notifySubscribers(this.replacedSubscribers);
178
223
  }
179
224
  }
180
225
 
@@ -187,7 +232,10 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
187
232
  return () => this.replacedSubscribers.delete(callback);
188
233
  }
189
234
 
190
- private _trackRunning(instance: RemoteThreadListHookInstance) {
235
+ private _trackRunning(
236
+ threadId: string,
237
+ instance: RemoteThreadListHookInstance,
238
+ ) {
191
239
  instance.unsubscribeRunning?.();
192
240
 
193
241
  const runtime = instance.runtime;
@@ -198,9 +246,23 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
198
246
  }
199
247
 
200
248
  this._setRunning(instance, getThreadRuntimeCoreIsRunning(runtime));
201
- instance.unsubscribeRunning = runtime.subscribe(() => {
202
- this._setRunning(instance, getThreadRuntimeCoreIsRunning(runtime));
203
- });
249
+ const unsubscribers = [
250
+ runtime.subscribe(() => {
251
+ this._setRunning(instance, getThreadRuntimeCoreIsRunning(runtime));
252
+ }),
253
+ ...THREAD_EVENTS.map((type) =>
254
+ runtime.unstable_on(type, () => {
255
+ notifyEventListeners(
256
+ this.threadEventSubscribers,
257
+ { threadId, type },
258
+ `Thread event "${type}"`,
259
+ );
260
+ }),
261
+ ),
262
+ ];
263
+ instance.unsubscribeRunning = () => {
264
+ for (const unsubscribe of unsubscribers) unsubscribe();
265
+ };
204
266
  }
205
267
 
206
268
  private _setRunning(
@@ -209,13 +271,14 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
209
271
  ) {
210
272
  if (instance.isRunning === isRunning) return;
211
273
  instance.isRunning = isRunning;
212
- for (const callback of this.runningSubscribers) callback();
274
+ notifySubscribers(this.runningSubscribers);
213
275
  }
214
276
 
215
277
  public stopThreadRuntime(threadId: string) {
216
278
  const instance = this.instances.get(threadId);
217
279
  if (instance?.runtime) invalidateThreadRuntime(instance.runtime);
218
280
  instance?.unsubscribeRunning?.();
281
+ instance?.destroy.abort();
219
282
  this.instances.delete(threadId);
220
283
  this.pendingThreadAdapters.delete(threadId);
221
284
  this._syncHostThreads();
@@ -225,16 +288,14 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
225
288
  public setRuntimeHook(newRuntimeHook: RemoteThreadListHook) {
226
289
  if (this.runtimeHook === newRuntimeHook) return;
227
290
  this.runtimeHook = newRuntimeHook;
228
- this.hostStore.setState(
229
- (state) => ({ ...state, hookEpoch: state.hookEpoch + 1 }),
230
- true,
231
- );
291
+ const host = this.hostStore.getState();
292
+ this.hostStore.setState({ ...host, hookEpoch: host.hookEpoch + 1 });
232
293
  }
233
294
 
234
295
  public __internal_setDefaultAdapters(adapters: RuntimeAdapters | null) {
235
296
  const current = this.adapterStore.getState();
236
297
  if (current.defaultAdapters === adapters) return;
237
- this.adapterStore.setState({ ...current, defaultAdapters: adapters }, true);
298
+ this.adapterStore.setState({ ...current, defaultAdapters: adapters });
238
299
  }
239
300
 
240
301
  public __internal_setThreadAdapters(
@@ -245,7 +306,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
245
306
  const next = new Map(current.threadAdapters);
246
307
  if (adapters === null) next.delete(threadId);
247
308
  else next.set(threadId, adapters);
248
- this.adapterStore.setState({ ...current, threadAdapters: next }, true);
309
+ this.adapterStore.setState({ ...current, threadAdapters: next });
249
310
  }
250
311
 
251
312
  public __internal_dispose() {
@@ -255,23 +316,25 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
255
316
  }
256
317
 
257
318
  private _syncHostThreads() {
258
- this.hostStore.setState(
259
- (state) => ({
260
- ...state,
261
- threads: Array.from(this.instances.entries()).map(
262
- ([id, { generation }]) => ({ id, generation }),
263
- ),
264
- }),
265
- true,
266
- );
319
+ const host = this.hostStore.getState();
320
+ this.hostStore.setState({
321
+ ...host,
322
+ threads: Array.from(this.instances.entries()).map(
323
+ ([id, { generation, destroy }]) => ({
324
+ id,
325
+ generation,
326
+ destroySignal: destroy.signal,
327
+ }),
328
+ ),
329
+ });
267
330
  }
268
331
 
269
332
  public __internal_useHost(parentClient: AssistantClient) {
270
- const { threads, hookEpoch } = this.hostStore();
271
- const adapters = this.adapterStore();
333
+ const { threads, hookEpoch } = useSubscribable(this.hostStore);
334
+ const adapters = useSubscribable(this.adapterStore);
272
335
  const runtimeHook = this.runtimeHook;
273
336
  return useResources(
274
- threads.map(({ id, generation }) => {
337
+ threads.map(({ id, generation, destroySignal }) => {
275
338
  const threadAdapters = this.pendingThreadAdapters.has(id)
276
339
  ? this.pendingThreadAdapters.get(id)!
277
340
  : (adapters.threadAdapters.get(id) ?? adapters.defaultAdapters);
@@ -285,6 +348,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
285
348
  parentClient,
286
349
  adapters: threadAdapters,
287
350
  publish: this._publish,
351
+ destroySignal,
288
352
  }),
289
353
  );
290
354
  }),
@@ -294,7 +358,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
294
358
  public __internal_RenderThreadRuntimes: FC<{
295
359
  provider: ComponentType<PropsWithChildren>;
296
360
  }> = ({ provider }) => {
297
- this.hostStore();
361
+ useSubscribable(this.hostStore);
298
362
 
299
363
  return Array.from(this.instances.entries()).map(
300
364
  ([threadId, { generation }]) => (