@assistant-ui/core 0.3.13 → 0.3.14

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 (396) hide show
  1. package/dist/adapters/speech.d.ts.map +1 -1
  2. package/dist/adapters/speech.js +9 -15
  3. package/dist/adapters/speech.js.map +1 -1
  4. package/dist/adapters/thread-history.d.ts +2 -0
  5. package/dist/adapters/thread-history.d.ts.map +1 -1
  6. package/dist/adapters/voice.d.ts.map +1 -1
  7. package/dist/adapters/voice.js +27 -15
  8. package/dist/adapters/voice.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +1 -1
  12. package/dist/internal.js +6 -6
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +2 -1
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/registry.d.ts.map +1 -1
  17. package/dist/model-context/registry.js +16 -4
  18. package/dist/model-context/registry.js.map +1 -1
  19. package/dist/react/RuntimeAdapter.d.ts +8 -1
  20. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  21. package/dist/react/RuntimeAdapter.js +23 -18
  22. package/dist/react/RuntimeAdapter.js.map +1 -1
  23. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  26. package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
  27. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
  28. package/dist/react/client/AdaptedRemoteThread.js +27 -0
  29. package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
  30. package/dist/react/client/InMemoryThreadList.d.ts +19 -0
  31. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
  32. package/dist/react/client/InMemoryThreadList.js +418 -0
  33. package/dist/react/client/InMemoryThreadList.js.map +1 -0
  34. package/dist/react/client/Interactables.d.ts.map +1 -1
  35. package/dist/react/client/Interactables.js +159 -102
  36. package/dist/react/client/Interactables.js.map +1 -1
  37. package/dist/react/client/RemoteThreadList.d.ts +34 -0
  38. package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
  39. package/dist/react/client/RemoteThreadList.js +826 -0
  40. package/dist/react/client/RemoteThreadList.js.map +1 -0
  41. package/dist/react/client/Tools.d.ts.map +1 -1
  42. package/dist/react/client/Tools.js +38 -27
  43. package/dist/react/client/Tools.js.map +1 -1
  44. package/dist/react/index.d.ts +7 -2
  45. package/dist/react/index.js +5 -2
  46. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  47. package/dist/react/interactables-legacy/Interactables.js +145 -110
  48. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  49. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useActionBarCopy.js +13 -18
  51. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  52. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  53. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
  54. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  55. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
  57. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  58. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  59. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
  60. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  61. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  62. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
  63. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  64. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
  66. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  67. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  68. package/dist/react/primitive-hooks/useComposerSend.js +2 -4
  69. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  70. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  71. package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
  72. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  73. package/dist/react/primitives/message/MessageParts.d.ts +13 -0
  74. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  75. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  76. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  77. package/dist/react/primitives/thread/ThreadMessages.js +1 -1
  78. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  79. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +22 -10
  80. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  81. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +109 -71
  82. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  83. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -2
  84. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +40 -13
  86. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  87. package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
  88. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
  89. package/dist/react/runtimes/RemoteThreadResource.js +134 -0
  90. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
  91. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
  92. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  93. package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
  94. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  95. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
  96. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
  97. package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
  98. package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
  99. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
  100. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  101. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
  102. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
  103. package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
  104. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
  105. package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
  106. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
  107. package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
  108. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
  109. package/dist/react/runtimes/assistant-transport/types.js +0 -0
  110. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
  111. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  112. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
  113. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  114. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
  115. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
  116. package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
  117. package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
  118. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
  119. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
  120. package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
  121. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
  122. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
  124. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  125. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
  126. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
  128. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
  130. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/auiV0.js +7 -0
  132. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  133. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  134. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -12
  135. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  136. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
  137. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
  138. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
  139. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
  140. package/dist/react/runtimes/external-message-converter.js +14 -14
  141. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  142. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  143. package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
  144. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  145. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  146. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  147. package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
  148. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
  149. package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
  150. package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
  151. package/dist/runtime/api/composer-runtime.d.ts +5 -0
  152. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  153. package/dist/runtime/api/composer-runtime.js.map +1 -1
  154. package/dist/runtime/api/message-runtime.js +1 -1
  155. package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
  156. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/thread-list-runtime.js +4 -2
  158. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  159. package/dist/runtime/api/thread-runtime.d.ts +12 -2
  160. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/thread-runtime.js +8 -2
  162. package/dist/runtime/api/thread-runtime.js.map +1 -1
  163. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  164. package/dist/runtime/base/base-composer-runtime-core.js +8 -7
  165. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  166. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  167. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  168. package/dist/runtime/base/base-thread-runtime-core.js +3 -3
  169. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  170. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -1
  171. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  172. package/dist/runtime/base/default-thread-composer-runtime-core.js +10 -4
  173. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  174. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  175. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  176. package/dist/runtime/internal.js +5 -5
  177. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  178. package/dist/runtime/queue/message-queue.js +2 -1
  179. package/dist/runtime/queue/message-queue.js.map +1 -1
  180. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
  181. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
  182. package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
  183. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
  184. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
  187. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +8 -0
  189. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  190. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +35 -13
  191. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  192. package/dist/runtimes/internal.d.ts +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  194. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  195. package/dist/runtimes/local/local-thread-runtime-core.js +53 -13
  196. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  197. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  198. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  199. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
  200. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
  202. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  203. package/dist/runtimes/remote-thread-list/types.d.ts +29 -1
  204. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  205. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
  206. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  207. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
  208. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  209. package/dist/store/clients/suggestions.js +2 -2
  210. package/dist/store/clients/suggestions.js.map +1 -1
  211. package/dist/store/clients/thread-selection-events.d.ts +10 -0
  212. package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
  213. package/dist/store/clients/thread-selection-events.js +40 -0
  214. package/dist/store/clients/thread-selection-events.js.map +1 -0
  215. package/dist/store/env.d.ts +5 -0
  216. package/dist/store/env.d.ts.map +1 -0
  217. package/dist/store/env.js +12 -0
  218. package/dist/store/env.js.map +1 -0
  219. package/dist/store/index.d.ts +5 -3
  220. package/dist/store/index.js +4 -2
  221. package/dist/store/internal.d.ts +4 -1
  222. package/dist/store/internal.js +4 -1
  223. package/dist/store/primitive-predicates.d.ts +19 -0
  224. package/dist/store/primitive-predicates.d.ts.map +1 -0
  225. package/dist/store/primitive-predicates.js +19 -0
  226. package/dist/store/primitive-predicates.js.map +1 -0
  227. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  228. package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
  229. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  230. package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
  231. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
  232. package/dist/store/runtime-clients/handle-runtime-action.js +12 -0
  233. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
  234. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
  235. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
  236. package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
  237. package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
  238. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +62 -29
  240. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
  243. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  244. package/dist/store/scopes/composer.d.ts +9 -4
  245. package/dist/store/scopes/composer.d.ts.map +1 -1
  246. package/dist/store/scopes/thread-list-item.d.ts +7 -6
  247. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  248. package/dist/store/scopes/thread.d.ts +8 -7
  249. package/dist/store/scopes/thread.d.ts.map +1 -1
  250. package/dist/store/scopes/threads.d.ts +17 -1
  251. package/dist/store/scopes/threads.d.ts.map +1 -1
  252. package/dist/subscribable/subscribable.d.ts +2 -1
  253. package/dist/subscribable/subscribable.d.ts.map +1 -1
  254. package/dist/subscribable/subscribable.js +23 -18
  255. package/dist/subscribable/subscribable.js.map +1 -1
  256. package/dist/utils/composite-context-provider.d.ts +1 -0
  257. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  258. package/dist/utils/composite-context-provider.js +25 -9
  259. package/dist/utils/composite-context-provider.js.map +1 -1
  260. package/dist/utils/notify-event-listeners.d.ts +2 -2
  261. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  262. package/dist/utils/notify-event-listeners.js +2 -2
  263. package/dist/utils/notify-event-listeners.js.map +1 -1
  264. package/package.json +7 -7
  265. package/src/adapters/speech.test.ts +118 -1
  266. package/src/adapters/speech.ts +21 -22
  267. package/src/adapters/thread-history.ts +2 -0
  268. package/src/adapters/voice.test.ts +69 -0
  269. package/src/adapters/voice.ts +27 -27
  270. package/src/index.ts +1 -0
  271. package/src/model-context/frame/host.test.ts +23 -0
  272. package/src/model-context/frame/host.ts +2 -1
  273. package/src/model-context/registry.test.ts +131 -0
  274. package/src/model-context/registry.ts +16 -6
  275. package/src/react/RuntimeAdapter.test.ts +2 -0
  276. package/src/react/RuntimeAdapter.ts +20 -8
  277. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
  278. package/src/react/client/AdaptedRemoteThread.ts +41 -0
  279. package/src/react/client/InMemoryThreadList.test.tsx +148 -0
  280. package/src/react/client/InMemoryThreadList.ts +290 -0
  281. package/src/react/client/Interactables.test.ts +326 -2
  282. package/src/react/client/Interactables.ts +227 -110
  283. package/src/react/client/RemoteThreadList.test.ts +1026 -0
  284. package/src/react/client/RemoteThreadList.ts +1067 -0
  285. package/src/react/client/Tools.ts +36 -29
  286. package/src/react/index.ts +13 -0
  287. package/src/react/interactables-legacy/Interactables.test.ts +317 -0
  288. package/src/react/interactables-legacy/Interactables.ts +204 -128
  289. package/src/react/primitive-hooks/useActionBarCopy.ts +2 -7
  290. package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
  291. package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
  292. package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
  293. package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
  294. package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
  295. package/src/react/primitive-hooks/useComposerSend.ts +2 -5
  296. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
  297. package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
  298. package/src/react/primitives/message/MessageParts.tsx +0 -1
  299. package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
  300. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
  301. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
  302. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
  303. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
  304. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +159 -124
  305. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +59 -20
  306. package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
  307. package/src/react/runtimes/RemoteThreadResource.ts +231 -0
  308. package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
  309. package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
  310. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
  311. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
  312. package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
  313. package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
  314. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
  315. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
  316. package/src/react/runtimes/assistant-transport/types.ts +170 -0
  317. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
  318. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +625 -0
  319. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +481 -0
  320. package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
  321. package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
  322. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
  323. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
  324. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
  325. package/src/react/runtimes/cloud/auiV0.ts +17 -1
  326. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +36 -21
  327. package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
  328. package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
  329. package/src/react/runtimes/external-message-converter.test.ts +143 -0
  330. package/src/react/runtimes/external-message-converter.ts +41 -41
  331. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
  332. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
  333. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +13 -3
  334. package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
  335. package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
  336. package/src/react/runtimes/useLocalRuntime.test.tsx +130 -4
  337. package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
  338. package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
  339. package/src/runtime/api/composer-runtime.ts +5 -0
  340. package/src/runtime/api/thread-list-runtime.ts +3 -1
  341. package/src/runtime/api/thread-runtime.ts +20 -2
  342. package/src/runtime/base/base-composer-runtime-core.ts +6 -5
  343. package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
  344. package/src/runtime/base/base-thread-runtime-core.ts +11 -2
  345. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -4
  346. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  347. package/src/runtime/queue/message-queue.ts +2 -1
  348. package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
  349. package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
  350. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  351. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +99 -34
  352. package/src/runtimes/local/local-thread-runtime-core.test.ts +373 -4
  353. package/src/runtimes/local/local-thread-runtime-core.ts +79 -14
  354. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
  355. package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
  356. package/src/runtimes/remote-thread-list/types.ts +30 -0
  357. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
  358. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
  359. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
  360. package/src/store/clients/model-context-client.test.ts +16 -8
  361. package/src/store/clients/suggestions.ts +2 -2
  362. package/src/store/clients/thread-selection-events.ts +21 -0
  363. package/src/store/env.ts +11 -0
  364. package/src/store/index.ts +14 -1
  365. package/src/store/internal.ts +13 -0
  366. package/src/store/primitive-predicates.test.ts +184 -0
  367. package/src/store/primitive-predicates.ts +43 -0
  368. package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
  369. package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
  370. package/src/store/runtime-clients/handle-runtime-action.ts +11 -0
  371. package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
  372. package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
  373. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +15 -7
  374. package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
  375. package/src/store/scopes/composer.ts +9 -4
  376. package/src/store/scopes/thread-list-item.ts +7 -6
  377. package/src/store/scopes/thread.ts +8 -7
  378. package/src/store/scopes/threads.ts +14 -0
  379. package/src/subscribable/subscribable.test.ts +77 -1
  380. package/src/subscribable/subscribable.ts +38 -23
  381. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
  382. package/src/tests/auiV0Encode.test.ts +66 -0
  383. package/src/tests/base-composer-runtime-core-send.test.ts +30 -0
  384. package/src/tests/base-composer-runtime-core.test.ts +28 -0
  385. package/src/tests/composer-can-cancel.test.ts +101 -0
  386. package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
  387. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +191 -1
  388. package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
  389. package/src/tests/interactables-toolui-migration.test.tsx +66 -0
  390. package/src/tests/message-queue.test.ts +28 -0
  391. package/src/tests/session-reset-notification.test.ts +145 -0
  392. package/src/tests/thread-switch-events.test.tsx +99 -1
  393. package/src/tests/tools-scope-migration.test.tsx +58 -0
  394. package/src/utils/composite-context-provider.test.ts +139 -0
  395. package/src/utils/composite-context-provider.ts +26 -8
  396. package/src/utils/notify-event-listeners.ts +10 -5
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { SubscribableWithState } from "./subscribable";
3
- import { ShallowMemoizeSubject } from "./subscribable";
3
+ import { LazyMemoizeSubject, ShallowMemoizeSubject } from "./subscribable";
4
4
 
5
5
  type TestState = {
6
6
  status: string;
@@ -26,6 +26,9 @@ const createBinding = (initialState: TestState) => {
26
26
  state = nextState;
27
27
  for (const callback of subscribers) callback();
28
28
  },
29
+ replace(nextState: TestState) {
30
+ state = nextState;
31
+ },
29
32
  };
30
33
  };
31
34
 
@@ -55,4 +58,77 @@ describe("ShallowMemoizeSubject", () => {
55
58
 
56
59
  expect(subscriber).not.toHaveBeenCalled();
57
60
  });
61
+
62
+ it("notifies later subscribers when one throws", () => {
63
+ const source = createBinding({ status: "empty" });
64
+ const subject = new ShallowMemoizeSubject(source.binding);
65
+ const error = new Error("subscriber failed");
66
+ const laterSubscriber = vi.fn();
67
+ subject.subscribe(() => {
68
+ throw error;
69
+ });
70
+ subject.subscribe(laterSubscriber);
71
+
72
+ expect(() => source.update({ status: "ready" })).toThrow(error);
73
+ expect(laterSubscriber).toHaveBeenCalledOnce();
74
+ expect(subject.getState()).toEqual({ status: "ready" });
75
+ });
76
+
77
+ it("reads a value that landed while nobody was subscribed", () => {
78
+ const source = createBinding({ status: "empty" });
79
+ const subject = new ShallowMemoizeSubject(source.binding);
80
+ expect(subject.getState()).toEqual({ status: "empty" });
81
+
82
+ source.update({ status: "ready" });
83
+ const subscriber = vi.fn();
84
+ subject.subscribe(subscriber);
85
+
86
+ expect(subject.getState()).toEqual({ status: "ready" });
87
+ expect(subscriber).not.toHaveBeenCalled();
88
+ });
89
+ });
90
+
91
+ describe("LazyMemoizeSubject", () => {
92
+ it("notifies later subscribers when one throws", () => {
93
+ const source = createBinding({ status: "empty" });
94
+ const subject = new LazyMemoizeSubject(source.binding);
95
+ const error = new Error("subscriber failed");
96
+ const laterSubscriber = vi.fn();
97
+ subject.subscribe(() => {
98
+ throw error;
99
+ });
100
+ subject.subscribe(laterSubscriber);
101
+
102
+ expect(() => source.update({ status: "ready" })).toThrow(error);
103
+ expect(laterSubscriber).toHaveBeenCalledOnce();
104
+ expect(subject.getState()).toEqual({ status: "ready" });
105
+ });
106
+
107
+ it("reads a value replaced before connecting", () => {
108
+ const source = createBinding({ status: "empty" });
109
+ const subject = new LazyMemoizeSubject(source.binding);
110
+ expect(subject.getState()).toEqual({ status: "empty" });
111
+
112
+ source.replace({ status: "ready" });
113
+ const subscriber = vi.fn();
114
+ const unsubscribe = subject.subscribe(subscriber);
115
+
116
+ expect(subject.getState()).toEqual({ status: "ready" });
117
+ expect(subscriber).not.toHaveBeenCalled();
118
+ unsubscribe();
119
+ });
120
+
121
+ it("resynchronizes after reconnecting", () => {
122
+ const source = createBinding({ status: "empty" });
123
+ const subject = new LazyMemoizeSubject(source.binding);
124
+ expect(subject.getState()).toEqual({ status: "empty" });
125
+
126
+ const unsubscribe = subject.subscribe(() => {});
127
+ unsubscribe();
128
+ source.replace({ status: "ready" });
129
+ const reconnect = subject.subscribe(() => {});
130
+
131
+ expect(subject.getState()).toEqual({ status: "ready" });
132
+ reconnect();
133
+ });
58
134
  });
@@ -32,6 +32,31 @@ export type EventSubscribable<TEvent extends string> = {
32
32
  >;
33
33
  };
34
34
 
35
+ export const notifySubscribers = <TArgs extends unknown[]>(
36
+ subscribers: Iterable<(...args: TArgs) => void>,
37
+ ...args: TArgs
38
+ ): void => {
39
+ const errors: unknown[] = [];
40
+ for (const callback of subscribers) {
41
+ try {
42
+ callback(...args);
43
+ } catch (error) {
44
+ errors.push(error);
45
+ }
46
+ }
47
+
48
+ if (errors.length === 1) {
49
+ throw errors[0];
50
+ }
51
+
52
+ if (errors.length > 1) {
53
+ for (const error of errors) {
54
+ console.error(error);
55
+ }
56
+ throw new AggregateError(errors);
57
+ }
58
+ };
59
+
35
60
  function shallowEqual<T extends object>(
36
61
  objA: T | undefined,
37
62
  objB: T | undefined,
@@ -70,25 +95,7 @@ export class BaseSubscribable {
70
95
  }
71
96
 
72
97
  protected _notifySubscribers() {
73
- const errors = [];
74
- for (const callback of this._subscribers) {
75
- try {
76
- callback();
77
- } catch (error) {
78
- errors.push(error);
79
- }
80
- }
81
-
82
- if (errors.length > 0) {
83
- if (errors.length === 1) {
84
- throw errors[0];
85
- } else {
86
- for (const error of errors) {
87
- console.error(error);
88
- }
89
- throw new AggregateError(errors);
90
- }
91
- }
98
+ notifySubscribers(this._subscribers);
92
99
  }
93
100
  }
94
101
 
@@ -109,7 +116,7 @@ export abstract class BaseSubject {
109
116
  return;
110
117
  }
111
118
 
112
- for (const callback of this._subscriptions) callback(payload);
119
+ notifySubscribers(this._subscriptions, payload);
113
120
  }
114
121
 
115
122
  private _updateConnection() {
@@ -173,7 +180,9 @@ export class ShallowMemoizeSubject<TState extends object, TPath>
173
180
  }
174
181
  };
175
182
 
176
- return this.binding.subscribe(callback);
183
+ const unsubscribe = this.binding.subscribe(callback);
184
+ this._syncState();
185
+ return unsubscribe;
177
186
  }
178
187
  }
179
188
 
@@ -197,7 +206,11 @@ export class LazyMemoizeSubject<TState extends object, TPath>
197
206
  public getState = () => {
198
207
  if (!this.isConnected || this._previousStateDirty) {
199
208
  const newState = this.binding.getState();
200
- if (newState !== SKIP_UPDATE) {
209
+ if (
210
+ newState !== SKIP_UPDATE &&
211
+ (this._previousState === undefined ||
212
+ !shallowEqual(newState, this._previousState))
213
+ ) {
201
214
  this._previousState = newState;
202
215
  }
203
216
  this._previousStateDirty = false;
@@ -213,7 +226,9 @@ export class LazyMemoizeSubject<TState extends object, TPath>
213
226
  this.notifySubscribers();
214
227
  };
215
228
 
216
- return this.binding.subscribe(callback);
229
+ const unsubscribe = this.binding.subscribe(callback);
230
+ this._previousStateDirty = true;
231
+ return unsubscribe;
217
232
  }
218
233
  }
219
234
 
@@ -63,6 +63,31 @@ describe("RemoteThreadListThreadListRuntimeCore.switchToThread unarchive option"
63
63
  expect(core.mainThreadId).toBe(core.getItemById(THREAD_ID)?.id);
64
64
  });
65
65
 
66
+ it("restores an initialized draft's archived thread when unarchive fails", async () => {
67
+ const THREAD_ID = "archived-initialized-draft";
68
+ const error = new Error("unarchive failed");
69
+ const adapter = makeAdapter({
70
+ list: vi.fn(async () => ({ threads: [archivedThread(THREAD_ID)] })),
71
+ unarchive: vi.fn(async () => {
72
+ throw error;
73
+ }),
74
+ });
75
+
76
+ const core = createCore(adapter);
77
+ await core.getLoadThreadsPromise();
78
+ const draftId = core.newThreadId;
79
+ if (draftId === undefined) throw new Error("Expected seeded draft");
80
+ await core.initialize(draftId);
81
+ expect(core.newThreadId).toBeUndefined();
82
+ await core.switchToThread(THREAD_ID, { unarchive: false });
83
+
84
+ await expect(core.unarchive(THREAD_ID)).rejects.toBe(error);
85
+ expect(core.mainThreadId).not.toBe(THREAD_ID);
86
+ expect(core.getItemById(THREAD_ID)?.status).toBe("archived");
87
+ expect(core.archivedThreadIds).toContain(THREAD_ID);
88
+ expect(core.threadIds).not.toContain(THREAD_ID);
89
+ });
90
+
66
91
  it("does not call unarchive when the target is already regular, regardless of option", async () => {
67
92
  const THREAD_ID = "regular-target";
68
93
  const adapter = makeAdapter({
@@ -330,6 +330,33 @@ describe("auiV0Encode", () => {
330
330
  { type: "data", name: "telemetry", data: { runs: 3 } },
331
331
  ]);
332
332
  });
333
+
334
+ it("preserves data message parts verbatim in the core cloud encoder", () => {
335
+ const encoded = auiV0Encode({
336
+ id: "m1",
337
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
338
+ role: "assistant",
339
+ status: { type: "complete", reason: "stop" },
340
+ metadata: {
341
+ unstable_state: null,
342
+ unstable_annotations: [],
343
+ unstable_data: [],
344
+ steps: [],
345
+ custom: {},
346
+ },
347
+ content: [
348
+ { type: "text", text: "planned" },
349
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
350
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
351
+ ],
352
+ });
353
+
354
+ expect(encoded.content).toEqual([
355
+ { type: "text", text: "planned" },
356
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
357
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
358
+ ]);
359
+ });
333
360
  });
334
361
 
335
362
  describe("auiV0Decode", () => {
@@ -573,4 +600,43 @@ describe("auiV0Decode", () => {
573
600
  const toolCall = message.content.find((p) => p.type === "tool-call");
574
601
  expect(toolCall).toHaveProperty("result", false);
575
602
  });
603
+
604
+ it("round-trips data message parts, keeping repeated names in order", () => {
605
+ const content = auiV0Encode({
606
+ id: "local",
607
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
608
+ role: "assistant",
609
+ status: { type: "complete", reason: "stop" },
610
+ metadata: {
611
+ unstable_state: null,
612
+ unstable_annotations: [],
613
+ unstable_data: [],
614
+ steps: [],
615
+ custom: {},
616
+ },
617
+ content: [
618
+ { type: "text", text: "planned" },
619
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
620
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
621
+ ],
622
+ });
623
+
624
+ const decoded = auiV0Decode({
625
+ id: "cloud",
626
+ parent_id: null,
627
+ height: 0,
628
+ created_at: new Date("2026-03-15T00:00:00.000Z"),
629
+ updated_at: new Date("2026-03-15T00:00:00.000Z"),
630
+ format: "aui/v0",
631
+ content: content as never,
632
+ });
633
+
634
+ if (decoded.message.role !== "assistant")
635
+ throw new Error("expected assistant");
636
+ expect(decoded.message.content).toEqual([
637
+ { type: "text", text: "planned" },
638
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
639
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
640
+ ]);
641
+ });
576
642
  });
@@ -158,6 +158,36 @@ describe("BaseComposerRuntimeCore.send restore-on-failure", () => {
158
158
  expect(append).toHaveBeenCalledTimes(1);
159
159
  });
160
160
 
161
+ it("snapshots role and run config before uploading attachments", async () => {
162
+ let resolveSend!: () => void;
163
+ const adapter = makeAdapter({
164
+ send: (a) =>
165
+ new Promise((resolve) => {
166
+ resolveSend = () =>
167
+ resolve({ ...a, status: { type: "complete" }, content: [] });
168
+ }),
169
+ });
170
+ const { composer, append } = makeComposer(adapter);
171
+
172
+ composer.setText("hello");
173
+ composer.setRole("assistant");
174
+ composer.setRunConfig({ custom: { modelName: "model-a" } });
175
+ await composer.addAttachment(textFile());
176
+
177
+ const sendPromise = composer.send();
178
+ composer.setRole("system");
179
+ composer.setRunConfig({ custom: { modelName: "model-b" } });
180
+ resolveSend();
181
+ await sendPromise;
182
+
183
+ expect(append).toHaveBeenCalledWith(
184
+ expect.objectContaining({
185
+ role: "assistant",
186
+ runConfig: { custom: { modelName: "model-a" } },
187
+ }),
188
+ );
189
+ });
190
+
161
191
  it("keeps an attachment added while the upload was in flight", async () => {
162
192
  let resolveSend!: () => void;
163
193
  const adapter = makeAdapter({
@@ -186,6 +186,34 @@ describe("BaseComposerRuntimeCore", () => {
186
186
  expect(composer.attachmentAccept).toBe("image/*");
187
187
  });
188
188
 
189
+ it("handles rejected dictation shutdowns", async () => {
190
+ const session: DictationAdapter.Session = {
191
+ status: { type: "running" },
192
+ stop: vi.fn().mockRejectedValue(new Error("shutdown failed")),
193
+ cancel: vi.fn(),
194
+ onSpeechStart: vi.fn(() => () => {}),
195
+ onSpeechEnd: vi.fn(() => () => {}),
196
+ onSpeech: vi.fn(() => () => {}),
197
+ };
198
+ const unhandledRejection = vi.fn();
199
+ composer.setDictationAdapter({ listen: () => session });
200
+ process.on("unhandledRejection", unhandledRejection);
201
+
202
+ try {
203
+ composer.startDictation();
204
+ expect(composer.dictation).toBeDefined();
205
+
206
+ composer.stopDictation();
207
+ await new Promise((resolve) => setTimeout(resolve, 0));
208
+
209
+ expect(session.stop).toHaveBeenCalledOnce();
210
+ expect(composer.dictation).toBeUndefined();
211
+ expect(unhandledRejection).not.toHaveBeenCalled();
212
+ } finally {
213
+ process.off("unhandledRejection", unhandledRejection);
214
+ }
215
+ });
216
+
189
217
  describe("CreateAttachment (external source)", () => {
190
218
  const makeCreateAttachment = (
191
219
  overrides?: Partial<CreateAttachment>,
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { DefaultThreadComposerRuntimeCore } from "../runtime/base/default-thread-composer-runtime-core";
3
+ import type { ThreadRuntimeCore } from "../runtime/interfaces/thread-runtime-core";
4
+ import type { ThreadMessage } from "../types/message";
5
+
6
+ type ThreadRuntimeStub = Omit<ThreadRuntimeCore, "composer"> & {
7
+ notify: () => void;
8
+ };
9
+
10
+ const makeRuntimeStub = (
11
+ overrides: Partial<ThreadRuntimeCore> = {},
12
+ ): ThreadRuntimeStub => {
13
+ const subscribers = new Set<() => void>();
14
+ return {
15
+ append: vi.fn(),
16
+ cancelRun: vi.fn(),
17
+ getModelContext: () => ({}),
18
+ subscribe: (cb: () => void) => {
19
+ subscribers.add(cb);
20
+ return () => subscribers.delete(cb);
21
+ },
22
+ capabilities: { cancel: true },
23
+ messages: [],
24
+ isDisabled: false,
25
+ isSendDisabled: false,
26
+ isLoading: false,
27
+ composer: { runConfig: {} },
28
+ notify: () => {
29
+ for (const cb of subscribers) cb();
30
+ },
31
+ ...overrides,
32
+ } as unknown as ThreadRuntimeStub;
33
+ };
34
+
35
+ const runningAssistant = {
36
+ id: "a1",
37
+ role: "assistant",
38
+ status: { type: "running" },
39
+ } as ThreadMessage;
40
+
41
+ describe("DefaultThreadComposerRuntimeCore.canCancel", () => {
42
+ it("is false when the runtime is idle even if cancel is supported", () => {
43
+ const composer = new DefaultThreadComposerRuntimeCore(makeRuntimeStub());
44
+ expect(composer.canCancel).toBe(false);
45
+ });
46
+
47
+ it("is false when cancel is not a capability", () => {
48
+ const composer = new DefaultThreadComposerRuntimeCore(
49
+ makeRuntimeStub({
50
+ capabilities: { cancel: false },
51
+ isRunning: true,
52
+ }),
53
+ );
54
+ expect(composer.canCancel).toBe(false);
55
+ });
56
+
57
+ it("is true while the runtime reports isRunning", () => {
58
+ const composer = new DefaultThreadComposerRuntimeCore(
59
+ makeRuntimeStub({ isRunning: true }),
60
+ );
61
+ expect(composer.canCancel).toBe(true);
62
+ });
63
+
64
+ it("is true while the trailing assistant message is running", () => {
65
+ const composer = new DefaultThreadComposerRuntimeCore(
66
+ makeRuntimeStub({ messages: [runningAssistant] }),
67
+ );
68
+ expect(composer.canCancel).toBe(true);
69
+ });
70
+
71
+ it("stays false after a runtime notify while idle", () => {
72
+ const stub = makeRuntimeStub();
73
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
74
+ stub.notify();
75
+ expect(composer.canCancel).toBe(false);
76
+ });
77
+
78
+ it("notifies subscribers when a run starts", () => {
79
+ const stub = makeRuntimeStub();
80
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
81
+ const onChange = vi.fn();
82
+ composer.subscribe(onChange);
83
+
84
+ (stub as { isRunning?: boolean }).isRunning = true;
85
+ stub.notify();
86
+
87
+ expect(onChange).toHaveBeenCalled();
88
+ expect(composer.canCancel).toBe(true);
89
+ });
90
+
91
+ it("returns to false when the run ends", () => {
92
+ const stub = makeRuntimeStub({ isRunning: true });
93
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
94
+ expect(composer.canCancel).toBe(true);
95
+
96
+ (stub as { isRunning?: boolean }).isRunning = false;
97
+ stub.notify();
98
+
99
+ expect(composer.canCancel).toBe(false);
100
+ });
101
+ });
@@ -1,7 +1,10 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import { ExternalStoreThreadListRuntimeCore } from "../runtimes/external-store/external-store-thread-list-runtime-core";
3
3
  import type { ExternalStoreThreadRuntimeCore } from "../runtimes/external-store/external-store-thread-runtime-core";
4
+ import { ExternalStoreThreadRuntimeCore as ExternalStoreThreadRuntimeCoreImpl } from "../runtimes/external-store/external-store-thread-runtime-core";
4
5
  import type { ExternalStoreThreadListAdapter } from "../runtimes/external-store/external-store-adapter";
6
+ import type { ExternalStoreAdapter } from "../runtimes/external-store/external-store-adapter";
7
+ import type { ModelContextProvider } from "../model-context/types";
5
8
  import { ThreadListRuntimeImpl } from "../runtime/api/thread-list-runtime";
6
9
 
7
10
  const makeFactory = (overrides: Record<string, unknown> = {}) =>
@@ -18,6 +21,21 @@ const makeAdapter = (
18
21
  overrides: Partial<ExternalStoreThreadListAdapter> = {},
19
22
  ): ExternalStoreThreadListAdapter => ({ ...overrides });
20
23
 
24
+ const mockContextProvider: ModelContextProvider = {
25
+ getModelContext: () => ({}),
26
+ };
27
+
28
+ const appendMessage = () => ({
29
+ parentId: null,
30
+ sourceId: null,
31
+ runConfig: {},
32
+ role: "user" as const,
33
+ content: [{ type: "text" as const, text: "hello" }],
34
+ attachments: [],
35
+ metadata: { custom: {} },
36
+ createdAt: new Date(0),
37
+ });
38
+
21
39
  describe("ExternalStoreThreadListRuntimeCore - construction", () => {
22
40
  it("assigns a resolvable fallback mainThreadId when adapter has no threadId", () => {
23
41
  const core = new ExternalStoreThreadListRuntimeCore(
@@ -149,6 +167,26 @@ describe("ExternalStoreThreadListRuntimeCore - __internal_setAdapter", () => {
149
167
  expect(callback).toHaveBeenCalled();
150
168
  });
151
169
 
170
+ it("notifies later subscribers when an earlier subscriber throws", () => {
171
+ const core = new ExternalStoreThreadListRuntimeCore(
172
+ makeAdapter({ threadId: "thread-alpha" }),
173
+ makeFactory(),
174
+ );
175
+ const error = new Error("subscriber failed");
176
+ const laterSubscriber = vi.fn();
177
+
178
+ core.subscribe(() => {
179
+ throw error;
180
+ });
181
+ core.subscribe(laterSubscriber);
182
+
183
+ expect(() =>
184
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" })),
185
+ ).toThrow(error);
186
+ expect(core.mainThreadId).toBe("thread-beta");
187
+ expect(laterSubscriber).toHaveBeenCalledOnce();
188
+ });
189
+
152
190
  it("synthesizes mainThreadId entry after a switch to a threadId not in the threads list", () => {
153
191
  const core = new ExternalStoreThreadListRuntimeCore(
154
192
  makeAdapter({ threadId: "thread-alpha" }),
@@ -175,6 +213,69 @@ describe("ExternalStoreThreadListRuntimeCore - __internal_setAdapter", () => {
175
213
  "thread-gamma",
176
214
  ]);
177
215
  });
216
+
217
+ it("dispatches an append before a thread switch", async () => {
218
+ let resolveInitialization!: () => void;
219
+ const initialization = new Promise<void>((resolve) => {
220
+ resolveInitialization = resolve;
221
+ });
222
+ const onNew = vi.fn(async () => {});
223
+ const firstRuntime = new ExternalStoreThreadRuntimeCoreImpl(
224
+ mockContextProvider,
225
+ { messages: [], onNew } as ExternalStoreAdapter,
226
+ );
227
+ firstRuntime.__internal_setGetInitializePromise(() => initialization);
228
+ const secondRuntime = new ExternalStoreThreadRuntimeCoreImpl(
229
+ mockContextProvider,
230
+ { messages: [], onNew: vi.fn() } as ExternalStoreAdapter,
231
+ );
232
+ const factory = vi
233
+ .fn()
234
+ .mockReturnValueOnce(firstRuntime)
235
+ .mockReturnValueOnce(secondRuntime);
236
+ const core = new ExternalStoreThreadListRuntimeCore(
237
+ makeAdapter({ threadId: "thread-alpha" }),
238
+ factory,
239
+ );
240
+
241
+ const appendPromise = firstRuntime.append(appendMessage());
242
+ await Promise.resolve();
243
+
244
+ expect(onNew).toHaveBeenCalledTimes(1);
245
+
246
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
247
+ resolveInitialization();
248
+
249
+ await appendPromise;
250
+ expect(onNew).toHaveBeenCalledTimes(1);
251
+ });
252
+ });
253
+
254
+ describe("ExternalStoreThreadListRuntimeCore - detach", () => {
255
+ it("leaves the main thread's pending append alone when an item detaches", async () => {
256
+ let resolveInitialization!: () => void;
257
+ const initialization = new Promise<void>((resolve) => {
258
+ resolveInitialization = resolve;
259
+ });
260
+ const onNew = vi.fn(async () => {});
261
+ const runtime = new ExternalStoreThreadRuntimeCoreImpl(
262
+ mockContextProvider,
263
+ { messages: [], onNew } as ExternalStoreAdapter,
264
+ );
265
+ runtime.__internal_setGetInitializePromise(() => initialization);
266
+ const core = new ExternalStoreThreadListRuntimeCore(
267
+ makeAdapter(),
268
+ vi.fn(() => runtime),
269
+ );
270
+
271
+ const appendPromise = runtime.append(appendMessage());
272
+ await Promise.resolve();
273
+ await core.detach();
274
+ resolveInitialization();
275
+
276
+ await appendPromise;
277
+ expect(onNew).toHaveBeenCalledTimes(1);
278
+ });
178
279
  });
179
280
 
180
281
  describe("ExternalStoreThreadListRuntimeCore - isMain via ThreadListRuntimeImpl", () => {