@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
@@ -117,6 +117,7 @@ const mountList = (
117
117
  threadId?: string,
118
118
  refetch?: () => Promise<void>,
119
119
  onSwitchToThread?: (id: string) => void,
120
+ onDelete?: (id: string) => void,
120
121
  ) => {
121
122
  const onThreadIdChange = vi.fn();
122
123
  const handle = createAssistantClient(
@@ -127,6 +128,7 @@ const mountList = (
127
128
  threadId,
128
129
  onThreadIdChange,
129
130
  onSwitchToThread,
131
+ onDelete,
130
132
  }),
131
133
  }),
132
134
  );
@@ -134,6 +136,46 @@ const mountList = (
134
136
  return { handle, onThreadIdChange };
135
137
  };
136
138
 
139
+ const mountArchivedInitializingThread = async () => {
140
+ const initialize = deferred<{
141
+ remoteId: string;
142
+ externalId: undefined;
143
+ }>();
144
+ const adapter = makeAdapter({
145
+ list: vi.fn(async () => ({
146
+ threads: [
147
+ {
148
+ status: "regular" as const,
149
+ remoteId: "fallback",
150
+ title: "Fallback",
151
+ },
152
+ ],
153
+ })),
154
+ initialize: vi.fn(() => initialize.promise),
155
+ });
156
+ const { handle } = mountList(adapter);
157
+ const aui = handle.getClient();
158
+ await aui.threads.getLoadThreadsPromise();
159
+ const targetId = aui.threads.getState().mainThreadId;
160
+ const initialization = aui.threads.item("main").initialize();
161
+ await vi.waitFor(() => {
162
+ expect(handle.getClient().threads.item("main").getState().status).toBe(
163
+ "regular",
164
+ );
165
+ });
166
+ flushTapSync(() => aui.threads.switchToThread("fallback"));
167
+ await vi.waitFor(() => {
168
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("fallback");
169
+ });
170
+ flushTapSync(() => aui.threads.item({ id: targetId }).archive());
171
+ await vi.waitFor(() => {
172
+ expect(handle.getClient().threads.getState().archivedThreadIds).toContain(
173
+ targetId,
174
+ );
175
+ });
176
+ return { adapter, handle, targetId, initialization, initialize };
177
+ };
178
+
137
179
  describe("RemoteThreadList", () => {
138
180
  it("loads adapter threads on a standalone client", async () => {
139
181
  const adapter = makeAdapter({
@@ -362,6 +404,141 @@ describe("RemoteThreadList", () => {
362
404
  handle.destroy();
363
405
  });
364
406
 
407
+ it("clears the deleted thread's title state so a reborn slot can auto-title", async () => {
408
+ const adapter = makeAdapter({
409
+ list: vi.fn(async () => ({
410
+ threads: [
411
+ { status: "regular" as const, remoteId: "t1", title: "One" },
412
+ { status: "regular" as const, remoteId: "t2", title: "Two" },
413
+ ],
414
+ })),
415
+ });
416
+ const { handle } = mountList(adapter);
417
+ const aui = handle.getClient();
418
+ await aui.threads.getLoadThreadsPromise();
419
+ await vi.waitFor(() => {
420
+ expect(aui.threads.getState().threadIds).toEqual(["t1", "t2"]);
421
+ });
422
+
423
+ await aui.threads.item({ id: "t1" }).rename("Manual title");
424
+ await aui.threads.item({ id: "t2" }).rename("Kept manual title");
425
+ await aui.threads.item({ id: "t1" }).delete();
426
+ await vi.waitFor(() => {
427
+ expect(aui.threads.getState().threadIds).toEqual(["t2"]);
428
+ });
429
+
430
+ // The server still lists t1, so a reload rebirths the slot under the same
431
+ // remote id; a leaked manual-rename entry would suppress its auto-title.
432
+ await aui.threads.reload();
433
+ await vi.waitFor(() => {
434
+ expect(aui.threads.getState().threadIds).toEqual(["t1", "t2"]);
435
+ });
436
+ flushTapSync(() => aui.threads.switchToThread("t1"));
437
+ await vi.waitFor(() => {
438
+ expect(aui.threads.getState().mainThreadId).toBe("t1");
439
+ });
440
+
441
+ await aui.threads.item({ id: "t1" }).generateTitle({ automatic: true });
442
+ expect(adapter.generateTitle).toHaveBeenCalledOnce();
443
+
444
+ // t2 is the control: a reload that cleared every title state would
445
+ // generate here as well.
446
+ flushTapSync(() => aui.threads.switchToThread("t2"));
447
+ await vi.waitFor(() => {
448
+ expect(aui.threads.getState().mainThreadId).toBe("t2");
449
+ });
450
+ await aui.threads.item({ id: "t2" }).generateTitle({ automatic: true });
451
+ expect(adapter.generateTitle).toHaveBeenCalledOnce();
452
+ handle.destroy();
453
+ });
454
+
455
+ it("preserves an existing title when generation returns no title", async () => {
456
+ const adapter = makeAdapter({
457
+ list: vi.fn(async () => ({
458
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
459
+ })),
460
+ });
461
+ const { handle } = mountList(adapter);
462
+ const aui = handle.getClient();
463
+ await aui.threads.getLoadThreadsPromise();
464
+ await vi.waitFor(() => {
465
+ expect(aui.threads.getState().threadIds).toEqual(["t1"]);
466
+ });
467
+ flushTapSync(() => aui.threads.switchToThread("t1"));
468
+ await vi.waitFor(() => {
469
+ expect(aui.threads.getState().mainThreadId).toBe("t1");
470
+ });
471
+
472
+ await aui.threads.item({ id: "t1" }).generateTitle();
473
+ // The list item re-renders a tick after the generation resolves, so an
474
+ // erasing title write lands only once that render has run.
475
+ await new Promise((resolve) => setTimeout(resolve, 0));
476
+
477
+ expect(adapter.generateTitle).toHaveBeenCalledOnce();
478
+ expect(aui.threads.item({ id: "t1" }).getState().title).toBe("One");
479
+ handle.destroy();
480
+ });
481
+
482
+ it("keeps a manual rename made during automatic title generation", async () => {
483
+ const generatedTitle = deferred<ReadableStream>();
484
+ const adapter = makeAdapter({
485
+ list: vi.fn(async () => ({
486
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
487
+ })),
488
+ generateTitle: vi.fn(async () => generatedTitle.promise as never),
489
+ });
490
+ const { handle } = mountList(adapter);
491
+ const aui = handle.getClient();
492
+ await aui.threads.getLoadThreadsPromise();
493
+ await vi.waitFor(() => {
494
+ expect(aui.threads.getState().threadIds).toEqual(["t1"]);
495
+ });
496
+ flushTapSync(() => aui.threads.switchToThread("t1"));
497
+ await vi.waitFor(() => {
498
+ expect(aui.threads.getState().mainThreadId).toBe("t1");
499
+ });
500
+
501
+ const generation = aui.threads
502
+ .item({ id: "t1" })
503
+ .generateTitle({ automatic: true });
504
+ await vi.waitFor(() => {
505
+ expect(adapter.generateTitle).toHaveBeenCalledOnce();
506
+ });
507
+ await aui.threads.item({ id: "t1" }).rename("Manual title");
508
+ await vi.waitFor(() => {
509
+ expect(aui.threads.item({ id: "t1" }).getState().title).toBe(
510
+ "Manual title",
511
+ );
512
+ });
513
+
514
+ generatedTitle.resolve(
515
+ new ReadableStream({
516
+ start(controller) {
517
+ controller.enqueue({
518
+ type: "part-start",
519
+ path: [0],
520
+ part: { type: "text" },
521
+ });
522
+ controller.enqueue({
523
+ type: "text-delta",
524
+ path: [0],
525
+ textDelta: "Generated title",
526
+ });
527
+ controller.enqueue({ type: "part-finish", path: [0] });
528
+ controller.close();
529
+ },
530
+ }),
531
+ );
532
+ await generation;
533
+
534
+ expect(aui.threads.item({ id: "t1" }).getState().title).toBe(
535
+ "Manual title",
536
+ );
537
+ expect(adapter.rename).toHaveBeenNthCalledWith(1, "t1", "Manual title");
538
+ expect(adapter.rename).toHaveBeenNthCalledWith(2, "t1", "Manual title");
539
+ handle.destroy();
540
+ });
541
+
365
542
  it("preserves generated titles across an overlapping reload", async () => {
366
543
  const reload = deferred<{
367
544
  threads: {
@@ -427,6 +604,63 @@ describe("RemoteThreadList", () => {
427
604
  handle.destroy();
428
605
  });
429
606
 
607
+ it("clears loading when a reload supersedes a hung list", async () => {
608
+ const adapter = makeAdapter({
609
+ list: vi
610
+ .fn()
611
+ .mockImplementationOnce(() => new Promise<never>(() => {}))
612
+ .mockResolvedValueOnce({
613
+ threads: [
614
+ { status: "regular" as const, remoteId: "fresh", title: "Fresh" },
615
+ ],
616
+ }),
617
+ });
618
+ const { handle } = mountList(adapter);
619
+ const threads = handle.getClient().threads;
620
+
621
+ void threads.getLoadThreadsPromise();
622
+ await threads.reload();
623
+
624
+ await vi.waitFor(() => {
625
+ const state = handle.getClient().threads.getState();
626
+ expect(state.threadIds).toEqual(["fresh"]);
627
+ expect(state.isLoading).toBe(false);
628
+ });
629
+ handle.destroy();
630
+ });
631
+
632
+ it("clears loadingMore when a reload supersedes a hung loadMore", async () => {
633
+ const adapter = makeAdapter({
634
+ list: vi
635
+ .fn()
636
+ .mockResolvedValueOnce({
637
+ threads: [
638
+ { status: "regular" as const, remoteId: "t1", title: "One" },
639
+ ],
640
+ nextCursor: "c1",
641
+ })
642
+ .mockImplementationOnce(() => new Promise<never>(() => {}))
643
+ .mockResolvedValueOnce({
644
+ threads: [
645
+ { status: "regular" as const, remoteId: "fresh", title: "Fresh" },
646
+ ],
647
+ }),
648
+ });
649
+ const { handle } = mountList(adapter);
650
+ const threads = handle.getClient().threads;
651
+
652
+ await threads.getLoadThreadsPromise();
653
+ void threads.loadMore();
654
+ await threads.reload();
655
+
656
+ await vi.waitFor(() => {
657
+ const state = handle.getClient().threads.getState();
658
+ expect(state.threadIds).toEqual(["fresh"]);
659
+ expect(state.isLoadingMore).toBe(false);
660
+ });
661
+ handle.destroy();
662
+ });
663
+
430
664
  it("opens a controlled threadId without echoing it back", async () => {
431
665
  const adapter = makeAdapter({
432
666
  list: vi.fn(async () => ({
@@ -513,7 +747,7 @@ describe("RemoteThreadList", () => {
513
747
  await vi.waitFor(() => {
514
748
  expect(handle.getClient().threads.getState().threadIds).toEqual([
515
749
  "other",
516
- `remote-${localId}`,
750
+ localId,
517
751
  ]);
518
752
  });
519
753
  expect(handle.getClient().threads.item("main").getState().remoteId).toBe(
@@ -525,6 +759,203 @@ describe("RemoteThreadList", () => {
525
759
  handle.destroy();
526
760
  });
527
761
 
762
+ it("keeps one slot per remote id across reload and clears it on delete", async () => {
763
+ const onDelete = vi.fn();
764
+ const adapter = makeAdapter({
765
+ list: vi.fn(async () => ({ threads: [] })),
766
+ });
767
+ const { handle } = mountList(
768
+ adapter,
769
+ undefined,
770
+ undefined,
771
+ undefined,
772
+ onDelete,
773
+ );
774
+ const aui = handle.getClient();
775
+ await aui.threads.getLoadThreadsPromise();
776
+ const localId = aui.threads.getState().mainThreadId;
777
+ await aui.threads.item("main").initialize();
778
+ const remoteId = `remote-${localId}`;
779
+ adapter.list = vi.fn(async () => ({
780
+ threads: [{ status: "regular" as const, remoteId, title: "Mine" }],
781
+ }));
782
+ await aui.threads.reload();
783
+ await vi.waitFor(() => {
784
+ expect(aui.threads.getState().threadIds).toEqual([localId]);
785
+ });
786
+
787
+ await aui.threads.item({ id: remoteId }).delete();
788
+ await vi.waitFor(() => {
789
+ expect(aui.threads.getState().threadIds).toEqual([]);
790
+ });
791
+ expect(() => aui.threads.item({ id: localId }).getState()).toThrow();
792
+ expect(() => aui.threads.item({ id: remoteId }).getState()).toThrow();
793
+ expect(onDelete).toHaveBeenCalledOnce();
794
+ expect(onDelete).toHaveBeenCalledWith(localId);
795
+ handle.destroy();
796
+ });
797
+
798
+ it("does not invoke deletion cleanup when the remote deletion fails", async () => {
799
+ const error = new Error("delete failed");
800
+ const onDelete = vi.fn();
801
+ const adapter = makeAdapter({
802
+ list: vi.fn(async () => ({
803
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
804
+ })),
805
+ delete: vi.fn(async () => {
806
+ throw error;
807
+ }),
808
+ });
809
+ const { handle } = mountList(
810
+ adapter,
811
+ undefined,
812
+ undefined,
813
+ undefined,
814
+ onDelete,
815
+ );
816
+ const aui = handle.getClient();
817
+ await aui.threads.getLoadThreadsPromise();
818
+ await vi.waitFor(() => {
819
+ expect(aui.threads.getState().threadIds).toContain("t1");
820
+ });
821
+
822
+ await expect(aui.threads.item({ id: "t1" }).delete()).rejects.toBe(error);
823
+
824
+ expect(aui.threads.getState().threadIds).toContain("t1");
825
+ expect(onDelete).not.toHaveBeenCalled();
826
+ handle.destroy();
827
+ });
828
+
829
+ const deleteDuringAdapterSwap = async (
830
+ replacementThreads: readonly {
831
+ status: "regular";
832
+ remoteId: string;
833
+ title: string;
834
+ }[],
835
+ ) => {
836
+ const removal = deferred<void>();
837
+ const onDelete = vi.fn();
838
+ const adapterA = makeAdapter({
839
+ list: vi.fn(async () => ({
840
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
841
+ })),
842
+ delete: vi.fn(() => removal.promise),
843
+ });
844
+ const adapterB = makeAdapter({
845
+ list: vi.fn(async () => ({ threads: replacementThreads })),
846
+ });
847
+ let adapter: RemoteThreadListAdapter = adapterA;
848
+ const listeners = new Set<() => void>();
849
+ const source: AssistantConfigSource = {
850
+ getConfig: () =>
851
+ AuiConfig({
852
+ threads: RemoteThreadList({
853
+ adapter,
854
+ thread: (id) => StubThread({ threadId: id }) as never,
855
+ onDelete,
856
+ }),
857
+ }),
858
+ subscribe: (listener) => {
859
+ listeners.add(listener);
860
+ return () => {
861
+ listeners.delete(listener);
862
+ };
863
+ },
864
+ };
865
+ const handle = createAssistantClient(source);
866
+ handle.subscribe(() => {});
867
+ await handle.getClient().threads.getLoadThreadsPromise();
868
+ await vi.waitFor(() => {
869
+ expect(handle.getClient().threads.getState().threadIds).toContain("t1");
870
+ });
871
+
872
+ const deletion = handle.getClient().threads.item({ id: "t1" }).delete();
873
+ await vi.waitFor(() => {
874
+ expect(adapterA.delete).toHaveBeenCalledWith("t1");
875
+ });
876
+
877
+ adapter = adapterB;
878
+ for (const listener of listeners) listener();
879
+ await vi.waitFor(async () => {
880
+ flushTapSync(() => {});
881
+ await handle.getClient().threads.reload();
882
+ expect(adapterB.list).toHaveBeenCalled();
883
+ expect(handle.getClient().threads.getState().threadIds).toEqual(
884
+ replacementThreads.map((thread) => thread.remoteId),
885
+ );
886
+ });
887
+
888
+ removal.resolve();
889
+ await deletion;
890
+ return { handle, onDelete };
891
+ };
892
+
893
+ it("invokes deletion cleanup when the adapter changes during a successful deletion", async () => {
894
+ const { handle, onDelete } = await deleteDuringAdapterSwap([]);
895
+
896
+ expect(onDelete).toHaveBeenCalledWith("t1");
897
+ handle.destroy();
898
+ });
899
+
900
+ it("skips deletion cleanup when the replacement adapter re-lists the deleted id", async () => {
901
+ const { handle, onDelete } = await deleteDuringAdapterSwap([
902
+ { status: "regular" as const, remoteId: "t1", title: "One" },
903
+ ]);
904
+
905
+ expect(handle.getClient().threads.getState().threadIds).toContain("t1");
906
+ expect(onDelete).not.toHaveBeenCalled();
907
+ handle.destroy();
908
+ });
909
+
910
+ const mountRacedInitialize = async (status: "regular" | "archived") => {
911
+ const initialize = deferred<{ remoteId: string; externalId: undefined }>();
912
+ const adapter = makeAdapter({
913
+ list: vi.fn(async () => ({ threads: [] })),
914
+ initialize: vi.fn(() => initialize.promise),
915
+ });
916
+ const { handle } = mountList(adapter);
917
+ const aui = handle.getClient();
918
+ await aui.threads.getLoadThreadsPromise();
919
+ const localId = aui.threads.getState().mainThreadId;
920
+ const initialization = aui.threads.item("main").initialize();
921
+
922
+ adapter.list = vi.fn(async () => ({
923
+ threads: [{ status, remoteId: "remote-1", title: "Mine" }],
924
+ }));
925
+ await aui.threads.reload();
926
+
927
+ initialize.resolve({ remoteId: "remote-1", externalId: undefined });
928
+ await initialization;
929
+ return { handle, aui, localId };
930
+ };
931
+
932
+ it("collapses a slot the list minted while initialize was in flight", async () => {
933
+ const { handle, aui, localId } = await mountRacedInitialize("regular");
934
+
935
+ await vi.waitFor(() => {
936
+ expect(aui.threads.getState().threadIds).toEqual([localId]);
937
+ });
938
+ expect(aui.threads.item({ id: "remote-1" }).getState().id).toBe(localId);
939
+
940
+ await aui.threads.item({ id: "remote-1" }).delete();
941
+ await vi.waitFor(() => {
942
+ expect(aui.threads.getState().threadIds).toEqual([]);
943
+ });
944
+ expect(() => aui.threads.item({ id: localId }).getState()).toThrow();
945
+ handle.destroy();
946
+ });
947
+
948
+ it("does not leave the collapsed slot in both lists when the race reported it archived", async () => {
949
+ const { handle, aui, localId } = await mountRacedInitialize("archived");
950
+
951
+ await vi.waitFor(() => {
952
+ expect(aui.threads.getState().threadIds).toEqual([localId]);
953
+ });
954
+ expect(aui.threads.getState().archivedThreadIds).toEqual([]);
955
+ expect(aui.threads.item({ id: "remote-1" }).getState().id).toBe(localId);
956
+ handle.destroy();
957
+ });
958
+
528
959
  it("keeps item(main) isRunning after reload remaps the local id", async () => {
529
960
  const adapter = makeAdapter({
530
961
  list: vi.fn(async () => ({ threads: [] })),
@@ -700,6 +1131,7 @@ describe("RemoteThreadList", () => {
700
1131
  });
701
1132
 
702
1133
  it("does not reset again when retrying a failed replacement load", async () => {
1134
+ const error = new Error("network");
703
1135
  const methodsA = makeAdapter({
704
1136
  list: vi.fn(async () => ({
705
1137
  threads: [
@@ -710,8 +1142,8 @@ describe("RemoteThreadList", () => {
710
1142
  const methodsB = makeAdapter({
711
1143
  list: vi
712
1144
  .fn()
713
- .mockRejectedValueOnce(new Error("network"))
714
- .mockResolvedValueOnce({
1145
+ .mockRejectedValueOnce(error)
1146
+ .mockResolvedValue({
715
1147
  threads: [
716
1148
  { status: "regular" as const, remoteId: "thread-b", title: "B" },
717
1149
  ],
@@ -760,6 +1192,7 @@ describe("RemoteThreadList", () => {
760
1192
  const mainAfterFailure = handle
761
1193
  .getClient()
762
1194
  .threads.getState().mainThreadId;
1195
+ expect(handle.getClient().threads.getState().loadError).toBe(error);
763
1196
  await handle.getClient().threads.reload();
764
1197
  await vi.waitFor(() => {
765
1198
  expect(handle.getClient().threads.getState().threadIds).toEqual([
@@ -769,12 +1202,94 @@ describe("RemoteThreadList", () => {
769
1202
  expect(handle.getClient().threads.getState().mainThreadId).toBe(
770
1203
  mainAfterFailure,
771
1204
  );
1205
+ expect(handle.getClient().threads.getState().loadError).toBeUndefined();
772
1206
  } finally {
773
1207
  consoleError.mockRestore();
774
1208
  }
775
1209
  handle.destroy();
776
1210
  });
777
1211
 
1212
+ it("reloads once when the browser comes online after a failed load", async () => {
1213
+ vi.stubGlobal("window", new EventTarget());
1214
+ vi.stubGlobal(
1215
+ "document",
1216
+ Object.assign(new EventTarget(), { visibilityState: "visible" }),
1217
+ );
1218
+ const error = new Error("offline");
1219
+ const firstLoad = deferred<{ threads: [] }>();
1220
+ const list = vi
1221
+ .fn()
1222
+ .mockReturnValueOnce(firstLoad.promise)
1223
+ .mockResolvedValueOnce({ threads: [] });
1224
+ const consoleError = vi
1225
+ .spyOn(console, "error")
1226
+ .mockImplementation(() => {});
1227
+ const { handle } = mountList(makeAdapter({ list }));
1228
+
1229
+ try {
1230
+ await vi.waitFor(() => expect(list).toHaveBeenCalledTimes(1));
1231
+ firstLoad.reject(error);
1232
+ await vi.waitFor(() =>
1233
+ expect(handle.getClient().threads.getState().loadError).toBe(error),
1234
+ );
1235
+
1236
+ window.dispatchEvent(new Event("online"));
1237
+
1238
+ await vi.waitFor(() => expect(list).toHaveBeenCalledTimes(2));
1239
+ await vi.waitFor(() =>
1240
+ expect(handle.getClient().threads.getState().loadError).toBeUndefined(),
1241
+ );
1242
+ expect(list).toHaveBeenCalledTimes(2);
1243
+ } finally {
1244
+ handle.destroy();
1245
+ consoleError.mockRestore();
1246
+ vi.unstubAllGlobals();
1247
+ }
1248
+ });
1249
+
1250
+ it("retries once when the document becomes visible after a failed load", async () => {
1251
+ vi.stubGlobal("window", new EventTarget());
1252
+ const document = Object.assign(new EventTarget(), {
1253
+ visibilityState: "hidden",
1254
+ });
1255
+ vi.stubGlobal("document", document);
1256
+ const error = new Error("offline");
1257
+ const firstLoad = deferred<{ threads: [] }>();
1258
+ const list = vi
1259
+ .fn()
1260
+ .mockReturnValueOnce(firstLoad.promise)
1261
+ .mockResolvedValueOnce({ threads: [] });
1262
+ const consoleError = vi
1263
+ .spyOn(console, "error")
1264
+ .mockImplementation(() => {});
1265
+ const { handle } = mountList(makeAdapter({ list }));
1266
+
1267
+ try {
1268
+ await vi.waitFor(() => expect(list).toHaveBeenCalledTimes(1));
1269
+ firstLoad.reject(error);
1270
+ await vi.waitFor(() =>
1271
+ expect(handle.getClient().threads.getState().loadError).toBe(error),
1272
+ );
1273
+
1274
+ document.dispatchEvent(new Event("visibilitychange"));
1275
+ await new Promise((resolve) => setTimeout(resolve, 20));
1276
+ expect(list).toHaveBeenCalledTimes(1);
1277
+
1278
+ document.visibilityState = "visible";
1279
+ document.dispatchEvent(new Event("visibilitychange"));
1280
+
1281
+ await vi.waitFor(() => expect(list).toHaveBeenCalledTimes(2));
1282
+ await vi.waitFor(() =>
1283
+ expect(handle.getClient().threads.getState().loadError).toBeUndefined(),
1284
+ );
1285
+ expect(list).toHaveBeenCalledTimes(2);
1286
+ } finally {
1287
+ handle.destroy();
1288
+ consoleError.mockRestore();
1289
+ vi.unstubAllGlobals();
1290
+ }
1291
+ });
1292
+
778
1293
  it("does not send a superseded rename through the replacement adapter", async () => {
779
1294
  const initializeRequest = deferred<{
780
1295
  remoteId: string;
@@ -1266,6 +1781,105 @@ describe("RemoteThreadList", () => {
1266
1781
  handle.destroy();
1267
1782
  });
1268
1783
 
1784
+ it("does not unarchive an archived thread deleted during initialization", async () => {
1785
+ const { adapter, handle, targetId, initialization, initialize } =
1786
+ await mountArchivedInitializingThread();
1787
+ const aui = handle.getClient();
1788
+ flushTapSync(() => aui.threads.switchToThread(targetId));
1789
+ flushTapSync(() => aui.threads.item({ id: targetId }).delete());
1790
+ await vi.waitFor(() => {
1791
+ const state = handle.getClient().threads.getState();
1792
+ expect(state.threadIds).not.toContain(targetId);
1793
+ expect(state.archivedThreadIds).not.toContain(targetId);
1794
+ });
1795
+
1796
+ initialize.resolve({
1797
+ remoteId: `remote-${targetId}`,
1798
+ externalId: undefined,
1799
+ });
1800
+ await initialization;
1801
+ await new Promise((resolve) => setTimeout(resolve, 0));
1802
+
1803
+ expect(adapter.unarchive).not.toHaveBeenCalled();
1804
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("fallback");
1805
+ expect(() =>
1806
+ handle.getClient().threads.item("main").getState(),
1807
+ ).not.toThrow();
1808
+ handle.destroy();
1809
+ });
1810
+
1811
+ it("does not unarchive again when the target became regular during initialization", async () => {
1812
+ const { adapter, handle, targetId, initialization, initialize } =
1813
+ await mountArchivedInitializingThread();
1814
+ const aui = handle.getClient();
1815
+
1816
+ flushTapSync(() => aui.threads.switchToThread(targetId));
1817
+ flushTapSync(() => aui.threads.item({ id: targetId }).unarchive());
1818
+ await vi.waitFor(() => {
1819
+ expect(
1820
+ handle.getClient().threads.item({ id: targetId }).getState().status,
1821
+ ).toBe("regular");
1822
+ });
1823
+
1824
+ initialize.resolve({
1825
+ remoteId: `remote-${targetId}`,
1826
+ externalId: undefined,
1827
+ });
1828
+ await initialization;
1829
+ await vi.waitFor(() => {
1830
+ expect(handle.getClient().threads.getState().mainThreadId).toBe(targetId);
1831
+ });
1832
+
1833
+ expect(adapter.unarchive).toHaveBeenCalledTimes(1);
1834
+ expect(adapter.unarchive).toHaveBeenCalledWith(`remote-${targetId}`);
1835
+ handle.destroy();
1836
+ });
1837
+
1838
+ it("keeps item(main) resolvable when deleting an archived thread during unarchive", async () => {
1839
+ const unarchive = deferred<void>();
1840
+ const onSwitchToThread = vi.fn();
1841
+ const adapter = makeAdapter({
1842
+ list: vi.fn(async () => ({
1843
+ threads: [
1844
+ { status: "archived" as const, remoteId: "t1", title: "One" },
1845
+ ],
1846
+ })),
1847
+ unarchive: vi.fn(() => unarchive.promise),
1848
+ });
1849
+ const { handle } = mountList(
1850
+ adapter,
1851
+ undefined,
1852
+ undefined,
1853
+ onSwitchToThread,
1854
+ );
1855
+ const aui = handle.getClient();
1856
+ await aui.threads.getLoadThreadsPromise();
1857
+ const initialMainThreadId = aui.threads.getState().mainThreadId;
1858
+
1859
+ flushTapSync(() => aui.threads.switchToThread("t1"));
1860
+ await vi.waitFor(() => {
1861
+ expect(adapter.unarchive).toHaveBeenCalledWith("t1");
1862
+ });
1863
+ flushTapSync(() => aui.threads.item({ id: "t1" }).delete());
1864
+ await vi.waitFor(() => {
1865
+ const state = handle.getClient().threads.getState();
1866
+ expect(state.threadIds).not.toContain("t1");
1867
+ expect(state.archivedThreadIds).not.toContain("t1");
1868
+ });
1869
+
1870
+ unarchive.resolve();
1871
+ await new Promise((resolve) => setTimeout(resolve, 0));
1872
+
1873
+ expect(handle.getClient().threads.getState().mainThreadId).toBe(
1874
+ initialMainThreadId,
1875
+ );
1876
+ expect(() =>
1877
+ handle.getClient().threads.item("main").getState(),
1878
+ ).not.toThrow();
1879
+ expect(onSwitchToThread).not.toHaveBeenCalled();
1880
+ handle.destroy();
1881
+ });
1882
+
1269
1883
  it("keeps item(main) resolvable when deleting a thread mid-initialize", async () => {
1270
1884
  const initialize = deferred<{
1271
1885
  remoteId: string;