@assistant-ui/core 0.3.17 → 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 (284) 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 +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal.d.ts +3 -2
  7. package/dist/internal.js +3 -2
  8. package/dist/model-context/frame/host.d.ts.map +1 -1
  9. package/dist/model-context/frame/host.js +10 -4
  10. package/dist/model-context/frame/host.js.map +1 -1
  11. package/dist/model-context/frame/provider.d.ts +2 -0
  12. package/dist/model-context/frame/provider.d.ts.map +1 -1
  13. package/dist/model-context/frame/provider.js +30 -7
  14. package/dist/model-context/frame/provider.js.map +1 -1
  15. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.js +16 -9
  17. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  18. package/dist/model-context/registry.d.ts.map +1 -1
  19. package/dist/model-context/registry.js +3 -5
  20. package/dist/model-context/registry.js.map +1 -1
  21. package/dist/model-context/types.d.ts.map +1 -1
  22. package/dist/model-context/types.js +5 -4
  23. package/dist/model-context/types.js.map +1 -1
  24. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  25. package/dist/react/client/InMemoryThreadList.js +62 -56
  26. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  27. package/dist/react/client/Interactables.d.ts.map +1 -1
  28. package/dist/react/client/Interactables.js +24 -27
  29. package/dist/react/client/Interactables.js.map +1 -1
  30. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  31. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  32. package/dist/react/client/RemoteThreadList.js +151 -141
  33. package/dist/react/client/RemoteThreadList.js.map +1 -1
  34. package/dist/react/client/Tools.d.ts.map +1 -1
  35. package/dist/react/client/Tools.js +18 -16
  36. package/dist/react/client/Tools.js.map +1 -1
  37. package/dist/react/interactables-legacy/Interactables.js +242 -121
  38. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  39. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  40. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  41. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  42. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  43. package/dist/react/model-context/makeAssistantTool.js +1 -1
  44. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  45. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  46. package/dist/react/model-context/useAssistantTool.js +1 -1
  47. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  48. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  49. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  50. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  51. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  52. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  53. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  54. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  55. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  56. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +103 -103
  57. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  58. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  59. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  60. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  61. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  62. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  63. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  64. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +401 -34
  65. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  66. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  67. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +2 -1
  68. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  69. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  70. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  71. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  72. package/dist/react/runtimes/useRemoteThreadListRuntime.js +63 -23
  73. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  74. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  75. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  76. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  77. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  78. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  79. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  80. package/dist/runtime/api/thread-list-runtime.js +18 -14
  81. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  82. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  83. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  84. package/dist/runtime/api/thread-runtime.js +1 -1
  85. package/dist/runtime/api/thread-runtime.js.map +1 -1
  86. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  87. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  88. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  89. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  90. package/dist/runtime/base/base-thread-runtime-core.js +6 -5
  91. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  92. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  93. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  94. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  95. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  96. package/dist/runtime/utils/external-message-conversion.js +1 -1
  97. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  98. package/dist/runtime/utils/message-repository.d.ts +1 -0
  99. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  100. package/dist/runtime/utils/message-repository.js +15 -6
  101. package/dist/runtime/utils/message-repository.js.map +1 -1
  102. package/dist/runtime/utils/thread-message-like.js +1 -1
  103. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  104. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  105. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  106. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  107. package/dist/runtimes/internal.d.ts +2 -2
  108. package/dist/runtimes/internal.js +2 -2
  109. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  110. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  111. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  112. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  113. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  114. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  115. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  116. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  117. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  118. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  119. package/dist/runtimes/remote-thread-list/title.js +9 -1
  120. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  121. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  122. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  123. package/dist/store/clients/external-thread.d.ts.map +1 -1
  124. package/dist/store/clients/external-thread.js +19 -3
  125. package/dist/store/clients/external-thread.js.map +1 -1
  126. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  127. package/dist/store/clients/model-context-client.js +2 -1
  128. package/dist/store/clients/model-context-client.js.map +1 -1
  129. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  130. package/dist/store/clients/single-thread-list.js +1 -0
  131. package/dist/store/clients/single-thread-list.js.map +1 -1
  132. package/dist/store/clients/suggestions.d.ts.map +1 -1
  133. package/dist/store/clients/suggestions.js +5 -4
  134. package/dist/store/clients/suggestions.js.map +1 -1
  135. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  136. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  137. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  138. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  139. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  140. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  141. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  142. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  143. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  144. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  145. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  146. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  147. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  148. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  149. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  150. package/dist/store/runtime-clients/thread-runtime-client.js +193 -121
  151. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  152. package/dist/store/scopes/composer.d.ts +10 -2
  153. package/dist/store/scopes/composer.d.ts.map +1 -1
  154. package/dist/store/scopes/message.d.ts +30 -1
  155. package/dist/store/scopes/message.d.ts.map +1 -1
  156. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  157. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  158. package/dist/store/scopes/thread.d.ts +8 -0
  159. package/dist/store/scopes/thread.d.ts.map +1 -1
  160. package/dist/store/scopes/threads.d.ts +1 -0
  161. package/dist/store/scopes/threads.d.ts.map +1 -1
  162. package/dist/types/attachment.d.ts +2 -1
  163. package/dist/types/attachment.d.ts.map +1 -1
  164. package/dist/types/attachment.js +2 -1
  165. package/dist/types/attachment.js.map +1 -1
  166. package/dist/utils/abortable-iterable.d.ts +19 -0
  167. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  168. package/dist/utils/abortable-iterable.js +72 -0
  169. package/dist/utils/abortable-iterable.js.map +1 -0
  170. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  171. package/dist/utils/composite-context-provider.js +20 -2
  172. package/dist/utils/composite-context-provider.js.map +1 -1
  173. package/dist/utils/record.d.ts +11 -0
  174. package/dist/utils/record.d.ts.map +1 -0
  175. package/dist/utils/record.js +12 -0
  176. package/dist/utils/record.js.map +1 -0
  177. package/package.json +10 -10
  178. package/src/adapters/attachment.test.ts +50 -1
  179. package/src/adapters/attachment.ts +7 -4
  180. package/src/index.ts +1 -0
  181. package/src/internal.ts +4 -0
  182. package/src/model-context/frame/host.test.ts +24 -0
  183. package/src/model-context/frame/host.ts +10 -4
  184. package/src/model-context/frame/provider.test.ts +71 -0
  185. package/src/model-context/frame/provider.ts +37 -7
  186. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  187. package/src/model-context/interactable-composer-metadata.ts +9 -3
  188. package/src/model-context/registry.test.ts +12 -0
  189. package/src/model-context/registry.ts +3 -2
  190. package/src/model-context/types.test.ts +25 -0
  191. package/src/model-context/types.ts +9 -4
  192. package/src/react/RuntimeAdapter.test.ts +34 -0
  193. package/src/react/client/InMemoryThreadList.ts +1 -0
  194. package/src/react/client/Interactables.test.ts +28 -0
  195. package/src/react/client/Interactables.ts +15 -13
  196. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  197. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  198. package/src/react/client/RemoteThreadList.test.ts +345 -3
  199. package/src/react/client/RemoteThreadList.ts +166 -110
  200. package/src/react/client/Tools.ts +22 -11
  201. package/src/react/interactables-legacy/Interactables.ts +16 -16
  202. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  203. package/src/react/model-context/makeAssistantTool.ts +2 -2
  204. package/src/react/model-context/useAssistantTool.ts +2 -2
  205. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  206. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  207. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  208. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  209. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  210. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  211. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  212. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  213. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +136 -111
  214. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  215. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  216. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +483 -6
  217. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +504 -68
  218. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +15 -0
  219. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +1 -0
  220. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  221. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  222. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  223. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  224. package/src/react/runtimes/useRemoteThreadListRuntime.ts +33 -0
  225. package/src/runtime/api/message-runtime.test.ts +75 -0
  226. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  227. package/src/runtime/api/thread-list-runtime.ts +21 -11
  228. package/src/runtime/api/thread-runtime.test.ts +78 -0
  229. package/src/runtime/api/thread-runtime.ts +8 -1
  230. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  231. package/src/runtime/base/base-thread-runtime-core.test.ts +55 -1
  232. package/src/runtime/base/base-thread-runtime-core.ts +6 -4
  233. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  234. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  235. package/src/runtime/utils/external-message-conversion.test.ts +68 -0
  236. package/src/runtime/utils/external-message-conversion.ts +1 -1
  237. package/src/runtime/utils/message-repository.ts +29 -17
  238. package/src/runtime/utils/thread-message-like.ts +1 -1
  239. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  240. package/src/runtimes/internal.ts +1 -0
  241. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  242. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  243. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  244. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  245. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  246. package/src/runtimes/remote-thread-list/title.ts +13 -0
  247. package/src/runtimes/remote-thread-list/types.ts +10 -0
  248. package/src/store/clients/external-thread.ts +48 -3
  249. package/src/store/clients/model-context-client.ts +5 -1
  250. package/src/store/clients/single-thread-list.ts +1 -0
  251. package/src/store/clients/suggestions.test.ts +40 -0
  252. package/src/store/clients/suggestions.ts +13 -10
  253. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  254. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  255. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  256. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  257. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  258. package/src/store/scopes/composer.ts +16 -4
  259. package/src/store/scopes/message.ts +14 -0
  260. package/src/store/scopes/thread-list-item.ts +5 -2
  261. package/src/store/scopes/thread.ts +4 -0
  262. package/src/store/scopes/threads.ts +1 -0
  263. package/src/tests/MessageRepository.test.ts +116 -0
  264. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  265. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  266. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  267. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  268. package/src/tests/attachment-adapters.test.ts +14 -0
  269. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  270. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  271. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +27 -18
  272. package/src/tests/external-thread-attachments.test.tsx +139 -0
  273. package/src/tests/external-thread-parity.test.tsx +23 -0
  274. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  275. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  276. package/src/tests/thread-switch-events.test.tsx +78 -0
  277. package/src/tests/tools-scope-migration.test.tsx +28 -0
  278. package/src/types/attachment.ts +4 -0
  279. package/src/utils/abortable-iterable.test.ts +213 -0
  280. package/src/utils/abortable-iterable.ts +93 -0
  281. package/src/utils/composite-context-provider.test.ts +25 -0
  282. package/src/utils/composite-context-provider.ts +23 -2
  283. package/src/utils/record.test.ts +48 -0
  284. package/src/utils/record.ts +11 -0
@@ -88,15 +88,16 @@ const makeAdapter = (
88
88
  ...overrides,
89
89
  });
90
90
 
91
- const mountBackgroundList = (
91
+ const mountThreadList = (
92
92
  adapter: RemoteThreadListAdapter,
93
93
  tracker: Tracker,
94
+ backgroundThreads = true,
94
95
  ) => {
95
96
  const handle = createAssistantClient(
96
97
  AuiConfig({
97
98
  threads: RemoteThreadList({
98
99
  adapter,
99
- backgroundThreads: true,
100
+ backgroundThreads,
100
101
  thread: (id) =>
101
102
  withKey(id, TrackedThread({ threadId: id, tracker }) as never),
102
103
  }),
@@ -112,7 +113,148 @@ const createTracker = (): Tracker => ({
112
113
  running: new Set(),
113
114
  });
114
115
 
116
+ const deferred = <T>() => {
117
+ let resolve!: (value: T) => void;
118
+ const promise = new Promise<T>((resolvePromise) => {
119
+ resolve = resolvePromise;
120
+ });
121
+ return { promise, resolve };
122
+ };
123
+
115
124
  describe("RemoteThreadList backgroundThreads", () => {
125
+ it("keeps the running initialized body when its listed duplicate is selected", async () => {
126
+ const list = deferred<{
127
+ threads: [
128
+ {
129
+ status: "regular";
130
+ remoteId: string;
131
+ externalId: string;
132
+ title: string;
133
+ },
134
+ ];
135
+ }>();
136
+ const initialize = deferred<{
137
+ remoteId: string;
138
+ externalId: undefined;
139
+ }>();
140
+ const adapter = makeAdapter({
141
+ list: vi.fn(() => list.promise),
142
+ initialize: vi.fn(() => initialize.promise),
143
+ });
144
+ const tracker = createTracker();
145
+ const handle = mountThreadList(adapter, tracker);
146
+ const aui = handle.getClient();
147
+ const loadPromise = aui.threads.getLoadThreadsPromise();
148
+ const localId = aui.threads.getState().mainThreadId;
149
+ tracker.running.add(localId);
150
+ const initializePromise = aui.threads.item("main").initialize();
151
+
152
+ list.resolve({
153
+ threads: [
154
+ {
155
+ status: "regular",
156
+ remoteId: "remote-1",
157
+ externalId: "remote-1",
158
+ title: "Listed thread",
159
+ },
160
+ ],
161
+ });
162
+ await loadPromise;
163
+ flushTapSync(() => aui.threads.switchToThread("remote-1"));
164
+ await vi.waitFor(() => {
165
+ expect(aui.threads.getState().mainThreadId).toBe("remote-1");
166
+ expect(tracker.alive.has("remote-1")).toBe(true);
167
+ });
168
+ const localMountCount = tracker.mounts.filter(
169
+ (id) => id === localId,
170
+ ).length;
171
+
172
+ initialize.resolve({ remoteId: "remote-1", externalId: undefined });
173
+ await initializePromise;
174
+ await vi.waitFor(() => {
175
+ expect(aui.threads.getState().mainThreadId).toBe(localId);
176
+ expect(aui.threads.getState().threadIds).toEqual([localId]);
177
+ expect(tracker.alive.has(localId)).toBe(true);
178
+ expect(tracker.alive.has("remote-1")).toBe(false);
179
+ });
180
+
181
+ expect(aui.threads.item({ id: "remote-1" }).getState().id).toBe(localId);
182
+ expect(aui.threads.item("main").getState().id).toBe(localId);
183
+ expect(aui.threads.item("main").getState().externalId).toBe("remote-1");
184
+ expect(aui.threads.item("main").getState().title).toBe("Listed thread");
185
+ expect(aui.threads.item("main").getState().isRunning).toBe(true);
186
+ expect(tracker.mounts.filter((id) => id === localId)).toHaveLength(
187
+ localMountCount,
188
+ );
189
+ handle.destroy();
190
+ });
191
+
192
+ it("keeps the mounted listed body when an unmounted initialization completes", async () => {
193
+ const list = deferred<{
194
+ threads: [
195
+ {
196
+ status: "regular";
197
+ remoteId: string;
198
+ externalId: string;
199
+ title: string;
200
+ },
201
+ ];
202
+ }>();
203
+ const initialize = deferred<{
204
+ remoteId: string;
205
+ externalId: undefined;
206
+ }>();
207
+ const adapter = makeAdapter({
208
+ list: vi.fn(() => list.promise),
209
+ initialize: vi.fn(() => initialize.promise),
210
+ });
211
+ const tracker = createTracker();
212
+ const handle = mountThreadList(adapter, tracker, false);
213
+ const aui = handle.getClient();
214
+ const loadPromise = aui.threads.getLoadThreadsPromise();
215
+ const localId = aui.threads.getState().mainThreadId;
216
+ const initializePromise = aui.threads.item("main").initialize();
217
+
218
+ list.resolve({
219
+ threads: [
220
+ {
221
+ status: "regular",
222
+ remoteId: "remote-1",
223
+ externalId: "external-1",
224
+ title: "Listed thread",
225
+ },
226
+ ],
227
+ });
228
+ await loadPromise;
229
+ flushTapSync(() => aui.threads.switchToThread("remote-1"));
230
+ await vi.waitFor(() => {
231
+ expect(aui.threads.getState().mainThreadId).toBe("remote-1");
232
+ expect(tracker.alive.has(localId)).toBe(false);
233
+ expect(tracker.alive.has("remote-1")).toBe(true);
234
+ });
235
+ const listedMountCount = tracker.mounts.filter(
236
+ (id) => id === "remote-1",
237
+ ).length;
238
+
239
+ initialize.resolve({ remoteId: "remote-1", externalId: undefined });
240
+ await initializePromise;
241
+ await vi.waitFor(() => {
242
+ expect(aui.threads.getState().mainThreadId).toBe("remote-1");
243
+ expect(aui.threads.getState().threadIds).toEqual(["remote-1"]);
244
+ expect(tracker.alive.has("remote-1")).toBe(true);
245
+ });
246
+
247
+ expect(aui.threads.item("main").getState()).toMatchObject({
248
+ id: "remote-1",
249
+ externalId: "external-1",
250
+ title: "Listed thread",
251
+ });
252
+ expect(tracker.mounts.filter((id) => id === "remote-1")).toHaveLength(
253
+ listedMountCount,
254
+ );
255
+ handle.destroy();
256
+ });
257
+
116
258
  it("keeps a switched-away thread mounted with live isRunning", async () => {
117
259
  const adapter = makeAdapter({
118
260
  list: vi.fn(async () => ({
@@ -120,7 +262,7 @@ describe("RemoteThreadList backgroundThreads", () => {
120
262
  })),
121
263
  });
122
264
  const tracker = createTracker();
123
- const handle = mountBackgroundList(adapter, tracker);
265
+ const handle = mountThreadList(adapter, tracker);
124
266
  const aui = handle.getClient();
125
267
  await aui.threads.getLoadThreadsPromise();
126
268
  await vi.waitFor(() => {
@@ -164,7 +306,7 @@ describe("RemoteThreadList backgroundThreads", () => {
164
306
  })),
165
307
  });
166
308
  const tracker = createTracker();
167
- const handle = mountBackgroundList(adapter, tracker);
309
+ const handle = mountThreadList(adapter, tracker);
168
310
  const aui = handle.getClient();
169
311
  await aui.threads.getLoadThreadsPromise();
170
312
  await vi.waitFor(() => {
@@ -260,7 +402,7 @@ describe("RemoteThreadList backgroundThreads", () => {
260
402
  });
261
403
  const tracker = createTracker();
262
404
  tracker.messagesOf = () => [{ status: { type: "complete" } }];
263
- const handle = mountBackgroundList(adapter, tracker);
405
+ const handle = mountThreadList(adapter, tracker);
264
406
  const aui = handle.getClient();
265
407
  await aui.threads.getLoadThreadsPromise();
266
408
  await vi.waitFor(() => {
@@ -350,7 +492,7 @@ describe("RemoteThreadList backgroundThreads", () => {
350
492
  list: vi.fn(async () => ({ threads: listed })),
351
493
  });
352
494
  const tracker = createTracker();
353
- const handle = mountBackgroundList(adapter, tracker);
495
+ const handle = mountThreadList(adapter, tracker);
354
496
  const aui = handle.getClient();
355
497
  await aui.threads.getLoadThreadsPromise();
356
498
  const localId = aui.threads.getState().mainThreadId;
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment jsdom
2
2
 
3
- import { createRef, startTransition, Suspense } from "react";
3
+ import { createRef, startTransition, Suspense, useLayoutEffect } from "react";
4
4
  import { act, render, waitFor } from "@testing-library/react";
5
5
  import { resource, withKey } from "@assistant-ui/tap";
6
6
  import {
@@ -68,6 +68,47 @@ const makeAdapter = (): RemoteThreadListAdapter => ({
68
68
  });
69
69
 
70
70
  describe("RemoteThreadList concurrent rendering", () => {
71
+ it("reloads through the committed adapter from a descendant layout effect", async () => {
72
+ const adapterA = makeAdapter();
73
+ const adapterB = makeAdapter();
74
+
75
+ const ReloadOnSwap = ({ workspace }: { workspace: string }) => {
76
+ const aui = useAui();
77
+ useLayoutEffect(() => {
78
+ if (workspace === "A") return;
79
+ void aui.threads.reload();
80
+ }, [aui, workspace]);
81
+ return null;
82
+ };
83
+ const App = ({
84
+ adapter,
85
+ workspace,
86
+ }: {
87
+ adapter: RemoteThreadListAdapter;
88
+ workspace: string;
89
+ }) => (
90
+ <AuiProvider
91
+ config={AuiConfig({
92
+ threads: RemoteThreadList({
93
+ adapter,
94
+ thread: () => StubThread() as never,
95
+ }),
96
+ })}
97
+ >
98
+ <ReloadOnSwap workspace={workspace} />
99
+ </AuiProvider>
100
+ );
101
+
102
+ const view = render(<App adapter={adapterA} workspace="A" />);
103
+ await waitFor(() => expect(adapterA.list).toHaveBeenCalledOnce());
104
+
105
+ act(() => {
106
+ view.rerender(<App adapter={adapterB} workspace="B" />);
107
+ });
108
+ await waitFor(() => expect(adapterB.list).toHaveBeenCalled());
109
+ expect(adapterA.list).toHaveBeenCalledOnce();
110
+ });
111
+
71
112
  it("keeps actions scoped to the committed adapter", async () => {
72
113
  const adapterA = makeAdapter();
73
114
  const adapterB = makeAdapter();
@@ -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
  );
@@ -402,6 +404,141 @@ describe("RemoteThreadList", () => {
402
404
  handle.destroy();
403
405
  });
404
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
+
405
542
  it("preserves generated titles across an overlapping reload", async () => {
406
543
  const reload = deferred<{
407
544
  threads: {
@@ -623,10 +760,17 @@ describe("RemoteThreadList", () => {
623
760
  });
624
761
 
625
762
  it("keeps one slot per remote id across reload and clears it on delete", async () => {
763
+ const onDelete = vi.fn();
626
764
  const adapter = makeAdapter({
627
765
  list: vi.fn(async () => ({ threads: [] })),
628
766
  });
629
- const { handle } = mountList(adapter);
767
+ const { handle } = mountList(
768
+ adapter,
769
+ undefined,
770
+ undefined,
771
+ undefined,
772
+ onDelete,
773
+ );
630
774
  const aui = handle.getClient();
631
775
  await aui.threads.getLoadThreadsPromise();
632
776
  const localId = aui.threads.getState().mainThreadId;
@@ -646,6 +790,120 @@ describe("RemoteThreadList", () => {
646
790
  });
647
791
  expect(() => aui.threads.item({ id: localId }).getState()).toThrow();
648
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();
649
907
  handle.destroy();
650
908
  });
651
909
 
@@ -873,6 +1131,7 @@ describe("RemoteThreadList", () => {
873
1131
  });
874
1132
 
875
1133
  it("does not reset again when retrying a failed replacement load", async () => {
1134
+ const error = new Error("network");
876
1135
  const methodsA = makeAdapter({
877
1136
  list: vi.fn(async () => ({
878
1137
  threads: [
@@ -883,8 +1142,8 @@ describe("RemoteThreadList", () => {
883
1142
  const methodsB = makeAdapter({
884
1143
  list: vi
885
1144
  .fn()
886
- .mockRejectedValueOnce(new Error("network"))
887
- .mockResolvedValueOnce({
1145
+ .mockRejectedValueOnce(error)
1146
+ .mockResolvedValue({
888
1147
  threads: [
889
1148
  { status: "regular" as const, remoteId: "thread-b", title: "B" },
890
1149
  ],
@@ -933,6 +1192,7 @@ describe("RemoteThreadList", () => {
933
1192
  const mainAfterFailure = handle
934
1193
  .getClient()
935
1194
  .threads.getState().mainThreadId;
1195
+ expect(handle.getClient().threads.getState().loadError).toBe(error);
936
1196
  await handle.getClient().threads.reload();
937
1197
  await vi.waitFor(() => {
938
1198
  expect(handle.getClient().threads.getState().threadIds).toEqual([
@@ -942,12 +1202,94 @@ describe("RemoteThreadList", () => {
942
1202
  expect(handle.getClient().threads.getState().mainThreadId).toBe(
943
1203
  mainAfterFailure,
944
1204
  );
1205
+ expect(handle.getClient().threads.getState().loadError).toBeUndefined();
945
1206
  } finally {
946
1207
  consoleError.mockRestore();
947
1208
  }
948
1209
  handle.destroy();
949
1210
  });
950
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
+
951
1293
  it("does not send a superseded rename through the replacement adapter", async () => {
952
1294
  const initializeRequest = deferred<{
953
1295
  remoteId: string;