@assistant-ui/core 0.3.14 → 0.3.16

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 (341) hide show
  1. package/dist/adapters/voice.js +11 -0
  2. package/dist/adapters/voice.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/internal.d.ts +8 -2
  5. package/dist/internal.js +9 -3
  6. package/dist/model-context/frame/host.d.ts +1 -1
  7. package/dist/model-context/frame/host.d.ts.map +1 -1
  8. package/dist/model-context/frame/host.js +20 -5
  9. package/dist/model-context/frame/host.js.map +1 -1
  10. package/dist/model-context/frame/provider.d.ts +6 -0
  11. package/dist/model-context/frame/provider.d.ts.map +1 -1
  12. package/dist/model-context/frame/provider.js +126 -16
  13. package/dist/model-context/frame/provider.js.map +1 -1
  14. package/dist/model-context/frame/types.d.ts +4 -1
  15. package/dist/model-context/frame/types.d.ts.map +1 -1
  16. package/dist/model-context/frame/types.js.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  18. package/dist/model-context/interactable-composer-metadata.js +1 -1
  19. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  20. package/dist/model-context/registry.d.ts +2 -2
  21. package/dist/model-context/registry.d.ts.map +1 -1
  22. package/dist/model-context/registry.js +9 -28
  23. package/dist/model-context/registry.js.map +1 -1
  24. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  25. package/dist/react/client/InMemoryThreadList.js +24 -20
  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 +59 -177
  29. package/dist/react/client/Interactables.js.map +1 -1
  30. package/dist/react/client/RemoteThreadList.d.ts +1 -1
  31. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  32. package/dist/react/client/RemoteThreadList.js +117 -78
  33. package/dist/react/client/RemoteThreadList.js.map +1 -1
  34. package/dist/react/index.d.ts +2 -1
  35. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  36. package/dist/react/interactables-legacy/Interactables.js +56 -174
  37. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  38. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  39. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  40. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  41. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  42. package/dist/react/primitive-hooks/useActionBarCopy.js +7 -1
  43. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  44. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
  45. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
  46. package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
  47. package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
  48. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
  49. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
  51. package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
  52. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  53. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  54. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  55. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  56. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -1
  57. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  58. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +6 -0
  59. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  60. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -1
  61. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  62. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +174 -64
  63. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  64. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  65. package/dist/react/runtimes/RemoteThreadResource.js +2 -0
  66. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  67. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  68. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  69. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  70. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  71. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +1 -1
  72. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  73. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  74. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  75. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
  76. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  77. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  78. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  79. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  80. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  81. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  82. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +38 -5
  83. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  84. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  85. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  86. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  87. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  88. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  89. package/dist/react/runtimes/external-message-converter.js +33 -233
  90. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  91. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  92. package/dist/runtime/api/composer-runtime.js.map +1 -1
  93. package/dist/runtime/api/thread-runtime.d.ts +1 -2
  94. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  95. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  96. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  97. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  98. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  99. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
  100. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  101. package/dist/runtime/base/base-thread-runtime-core.js +3 -9
  102. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  103. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  104. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  105. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  106. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  107. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  108. package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
  109. package/dist/runtime/internal.d.ts +2 -1
  110. package/dist/runtime/internal.js +2 -1
  111. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  112. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  113. package/dist/runtime/queue/message-queue.js +1 -1
  114. package/dist/runtime/queue/message-queue.js.map +1 -1
  115. package/dist/runtime/queue/queue-item.d.ts +12 -0
  116. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  117. package/dist/runtime/queue/queue-item.js +6 -0
  118. package/dist/runtime/queue/queue-item.js.map +1 -0
  119. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  120. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  121. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  122. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  123. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  124. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  125. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  126. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  127. package/dist/runtime/utils/auto-status.d.ts +6 -1
  128. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  129. package/dist/runtime/utils/auto-status.js +7 -1
  130. package/dist/runtime/utils/auto-status.js.map +1 -1
  131. package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
  132. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  133. package/dist/runtime/utils/external-message-conversion.js +221 -0
  134. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  135. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  136. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  137. package/dist/runtime/utils/external-store-message.js +2 -1
  138. package/dist/runtime/utils/external-store-message.js.map +1 -1
  139. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  140. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  141. package/dist/runtime/utils/message-repository-session.js +130 -0
  142. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  143. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  144. package/dist/runtime/utils/message-repository.js +30 -22
  145. package/dist/runtime/utils/message-repository.js.map +1 -1
  146. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  147. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  148. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  149. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  150. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  151. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  152. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  153. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  154. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  155. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  156. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  157. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +19 -1
  158. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  159. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +124 -20
  160. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  161. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
  162. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
  163. package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
  164. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
  165. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
  166. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  167. package/dist/runtimes/local/local-thread-runtime-core.js +45 -8
  168. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  169. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -60
  170. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  171. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -145
  172. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  173. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
  174. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
  175. package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
  176. package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
  177. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  178. package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -155
  179. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  180. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
  181. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  182. package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
  183. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  184. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  185. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  186. package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
  187. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  188. package/dist/runtimes/remote-thread-list/types.d.ts +3 -0
  189. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  190. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  191. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
  192. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  193. package/dist/store/clients/external-thread.d.ts.map +1 -1
  194. package/dist/store/clients/external-thread.js +75 -100
  195. package/dist/store/clients/external-thread.js.map +1 -1
  196. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  197. package/dist/store/clients/single-thread-list.js +1 -1
  198. package/dist/store/clients/single-thread-list.js.map +1 -1
  199. package/dist/store/clients/suggestions.d.ts.map +1 -1
  200. package/dist/store/clients/suggestions.js +16 -7
  201. package/dist/store/clients/suggestions.js.map +1 -1
  202. package/dist/store/index.d.ts +4 -2
  203. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
  204. package/dist/store/runtime-clients/handle-runtime-action.js +2 -0
  205. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -1
  206. package/dist/store/scope-registration.d.ts +1 -1
  207. package/dist/store/scopes/composer.d.ts +2 -1
  208. package/dist/store/scopes/composer.d.ts.map +1 -1
  209. package/dist/store/scopes/message.d.ts +1 -1
  210. package/dist/store/scopes/queue-item.d.ts +2 -9
  211. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  212. package/dist/store/scopes/queue-item.js +0 -6
  213. package/dist/types/message.d.ts +8 -4
  214. package/dist/types/message.d.ts.map +1 -1
  215. package/dist/types/message.js.map +1 -1
  216. package/dist/utils/data-url.d.ts +14 -1
  217. package/dist/utils/data-url.d.ts.map +1 -1
  218. package/dist/utils/data-url.js +13 -1
  219. package/dist/utils/data-url.js.map +1 -1
  220. package/dist/utils/invoke-user-callback.d.ts +5 -0
  221. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  222. package/dist/utils/invoke-user-callback.js +19 -0
  223. package/dist/utils/invoke-user-callback.js.map +1 -0
  224. package/dist/utils/normalizePartStatus.js.map +1 -1
  225. package/dist/utils/silent-runtime-action.d.ts +6 -0
  226. package/dist/utils/silent-runtime-action.d.ts.map +1 -0
  227. package/dist/utils/silent-runtime-action.js +7 -0
  228. package/dist/utils/silent-runtime-action.js.map +1 -0
  229. package/package.json +10 -10
  230. package/src/adapters/voice.test.ts +66 -1
  231. package/src/adapters/voice.ts +8 -0
  232. package/src/internal.ts +13 -0
  233. package/src/model-context/frame/host.test.ts +152 -9
  234. package/src/model-context/frame/host.ts +23 -5
  235. package/src/model-context/frame/provider.test.ts +601 -19
  236. package/src/model-context/frame/provider.ts +158 -20
  237. package/src/model-context/frame/types.ts +5 -0
  238. package/src/model-context/interactable-composer-metadata.ts +1 -1
  239. package/src/model-context/registry.test.ts +44 -0
  240. package/src/model-context/registry.ts +13 -35
  241. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  242. package/src/react/client/InMemoryThreadList.ts +7 -1
  243. package/src/react/client/Interactables.ts +19 -178
  244. package/src/react/client/RemoteThreadList.test.ts +292 -0
  245. package/src/react/client/RemoteThreadList.ts +119 -82
  246. package/src/react/interactables-legacy/Interactables.ts +20 -177
  247. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  248. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  249. package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
  250. package/src/react/primitive-hooks/useActionBarCopy.ts +13 -3
  251. package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
  252. package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
  253. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +12 -0
  254. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
  255. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +251 -76
  256. package/src/react/runtimes/RemoteThreadResource.ts +2 -0
  257. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  258. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +58 -0
  259. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +3 -1
  260. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
  261. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
  262. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  263. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  264. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
  265. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +68 -6
  266. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  267. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  268. package/src/react/runtimes/external-message-converter.ts +58 -481
  269. package/src/react/runtimes/useLocalRuntime.test.tsx +5 -5
  270. package/src/runtime/api/composer-runtime.ts +1 -1
  271. package/src/runtime/api/message-runtime.test.ts +28 -1
  272. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  273. package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
  274. package/src/runtime/base/base-thread-runtime-core.ts +6 -11
  275. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  276. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  277. package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
  278. package/src/runtime/internal.ts +5 -0
  279. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  280. package/src/runtime/queue/message-queue.ts +1 -4
  281. package/src/runtime/queue/queue-item.ts +10 -0
  282. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  283. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  284. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  285. package/src/runtime/utils/auto-status.ts +33 -0
  286. package/src/runtime/utils/external-message-conversion.test.ts +113 -0
  287. package/src/runtime/utils/external-message-conversion.ts +494 -0
  288. package/src/runtime/utils/external-store-message.ts +2 -0
  289. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  290. package/src/runtime/utils/message-repository-session.ts +176 -0
  291. package/src/runtime/utils/message-repository.ts +33 -22
  292. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  293. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  294. package/src/runtime/utils/thread-message-like.ts +2 -0
  295. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  296. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +179 -28
  297. package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
  298. package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
  299. package/src/runtimes/local/local-thread-runtime-core.test.ts +491 -1
  300. package/src/runtimes/local/local-thread-runtime-core.ts +86 -11
  301. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -209
  302. package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
  303. package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -221
  304. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
  305. package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
  306. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
  307. package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
  308. package/src/runtimes/remote-thread-list/types.ts +4 -0
  309. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
  310. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
  311. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
  312. package/src/store/clients/external-thread.ts +17 -62
  313. package/src/store/clients/single-thread-list.ts +4 -1
  314. package/src/store/clients/suggestions.test.ts +31 -0
  315. package/src/store/clients/suggestions.ts +18 -19
  316. package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
  317. package/src/store/runtime-clients/handle-runtime-action.ts +3 -0
  318. package/src/store/scopes/message.ts +1 -1
  319. package/src/store/scopes/queue-item.ts +2 -9
  320. package/src/tests/MessageRepository.test.ts +125 -0
  321. package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
  322. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
  323. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
  324. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  325. package/src/tests/empty-thread-core.test.ts +6 -0
  326. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +184 -0
  327. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  328. package/src/tests/external-thread-attachments.test.tsx +173 -0
  329. package/src/tests/external-thread-isEmpty.test.tsx +100 -0
  330. package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
  331. package/src/tests/single-thread-list-item.test.tsx +47 -0
  332. package/src/tests/thread-message-like.test.ts +39 -1
  333. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
  334. package/src/types/message.ts +9 -4
  335. package/src/utils/data-url.test.ts +52 -1
  336. package/src/utils/data-url.ts +21 -0
  337. package/src/utils/invoke-user-callback.test.ts +82 -0
  338. package/src/utils/invoke-user-callback.ts +31 -0
  339. package/src/utils/normalizePartStatus.ts +1 -1
  340. package/src/utils/silent-runtime-action.ts +6 -0
  341. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -9,6 +9,45 @@ describe("AssistantFrameProvider", () => {
9
9
  let messageHandler: ((event: MessageEvent) => void) | undefined;
10
10
  let parentWindow: Window;
11
11
 
12
+ const dispatchToolCall = (
13
+ origin: string,
14
+ source: Window = parentWindow,
15
+ id = "tool-call-1",
16
+ ) => {
17
+ messageHandler?.(
18
+ new MessageEvent("message", {
19
+ data: {
20
+ channel: FRAME_MESSAGE_CHANNEL,
21
+ message: {
22
+ type: "tool-call",
23
+ id,
24
+ toolName: "sensitiveTool",
25
+ args: {},
26
+ },
27
+ },
28
+ origin,
29
+ source,
30
+ }),
31
+ );
32
+ };
33
+
34
+ const dispatchToolCancel = (
35
+ origin: string,
36
+ source: Window = parentWindow,
37
+ id = "tool-call-1",
38
+ ) => {
39
+ messageHandler?.(
40
+ new MessageEvent("message", {
41
+ data: {
42
+ channel: FRAME_MESSAGE_CHANNEL,
43
+ message: { type: "tool-cancel", id },
44
+ },
45
+ origin,
46
+ source,
47
+ }),
48
+ );
49
+ };
50
+
12
51
  beforeEach(() => {
13
52
  parentWindow = {
14
53
  postMessage: vi.fn(),
@@ -47,37 +86,580 @@ describe("AssistantFrameProvider", () => {
47
86
  "https://parent.example",
48
87
  );
49
88
 
50
- const toolCall = {
51
- channel: FRAME_MESSAGE_CHANNEL,
52
- message: {
53
- type: "tool-call",
54
- id: "tool-call-1",
55
- toolName: "sensitiveTool",
56
- args: {},
57
- },
58
- };
59
89
  const otherWindow = {
60
90
  postMessage: vi.fn(),
61
91
  } as unknown as Window;
62
92
 
63
- messageHandler?.(
64
- new MessageEvent("message", {
65
- data: toolCall,
66
- origin: "https://parent.example",
67
- source: otherWindow,
93
+ dispatchToolCall("https://parent.example", otherWindow);
94
+
95
+ expect(execute).not.toHaveBeenCalled();
96
+
97
+ dispatchToolCall("https://parent.example");
98
+
99
+ await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
100
+ });
101
+
102
+ it("defaults to the current origin", async () => {
103
+ const execute = vi.fn(async () => "result");
104
+ AssistantFrameProvider.addModelContextProvider({
105
+ getModelContext: () => ({
106
+ tools: { sensitiveTool: { execute } },
68
107
  }),
108
+ });
109
+
110
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
111
+ expect.anything(),
112
+ window.location.origin,
69
113
  );
70
114
 
115
+ dispatchToolCall("https://untrusted.example");
71
116
  expect(execute).not.toHaveBeenCalled();
72
117
 
73
- messageHandler?.(
74
- new MessageEvent("message", {
75
- data: toolCall,
76
- origin: "https://parent.example",
77
- source: parentWindow,
118
+ dispatchToolCall(window.location.origin);
119
+ await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
120
+ });
121
+
122
+ it("reports a failure even when the thrown error has an empty message", async () => {
123
+ const execute = vi.fn(async () => {
124
+ throw new Error();
125
+ });
126
+ AssistantFrameProvider.addModelContextProvider({
127
+ getModelContext: () => ({
128
+ tools: {
129
+ sensitiveTool: { execute },
130
+ },
131
+ }),
132
+ });
133
+
134
+ dispatchToolCall(window.location.origin);
135
+
136
+ await vi.waitFor(() => {
137
+ const frame = (
138
+ parentWindow.postMessage as ReturnType<typeof vi.fn>
139
+ ).mock.calls
140
+ .map(([data]) => data)
141
+ .find((data) => data?.message?.type === "tool-result");
142
+ expect(frame).toBeDefined();
143
+ expect(frame.message).toHaveProperty("error");
144
+ expect(frame.message).not.toHaveProperty("result");
145
+ });
146
+ });
147
+
148
+ it("aborts in-flight tool calls when the parent cancels them", async () => {
149
+ let toolSignal: AbortSignal | undefined;
150
+ const execute = vi.fn(
151
+ async (_args: unknown, context: { abortSignal: AbortSignal }) => {
152
+ toolSignal = context.abortSignal;
153
+ await new Promise<never>((_resolve, reject) => {
154
+ context.abortSignal.addEventListener(
155
+ "abort",
156
+ () => reject(context.abortSignal.reason),
157
+ { once: true },
158
+ );
159
+ });
160
+ },
161
+ );
162
+ AssistantFrameProvider.addModelContextProvider({
163
+ getModelContext: () => ({
164
+ tools: { sensitiveTool: { execute } },
165
+ }),
166
+ });
167
+
168
+ dispatchToolCall(window.location.origin);
169
+ await vi.waitFor(() => expect(toolSignal).toBeDefined());
170
+
171
+ dispatchToolCancel(window.location.origin);
172
+
173
+ expect(toolSignal?.aborted).toBe(true);
174
+ await new Promise((resolve) => setTimeout(resolve, 0));
175
+ expect(parentWindow.postMessage).not.toHaveBeenCalledWith(
176
+ expect.objectContaining({
177
+ message: expect.objectContaining({ type: "tool-result" }),
178
+ }),
179
+ expect.anything(),
180
+ );
181
+ });
182
+
183
+ it("cancels only the matching in-flight tool call", async () => {
184
+ const signals = new Map<string, AbortSignal>();
185
+ const execute = vi.fn(
186
+ async (
187
+ _args: unknown,
188
+ context: { toolCallId: string; abortSignal: AbortSignal },
189
+ ) => {
190
+ signals.set(context.toolCallId, context.abortSignal);
191
+ await new Promise<never>((_resolve, reject) => {
192
+ context.abortSignal.addEventListener(
193
+ "abort",
194
+ () => reject(context.abortSignal.reason),
195
+ { once: true },
196
+ );
197
+ });
198
+ },
199
+ );
200
+ AssistantFrameProvider.addModelContextProvider({
201
+ getModelContext: () => ({ tools: { sensitiveTool: { execute } } }),
202
+ });
203
+
204
+ dispatchToolCall(window.location.origin, parentWindow, "tool-a");
205
+ dispatchToolCall(window.location.origin, parentWindow, "tool-b");
206
+ await vi.waitFor(() => expect(signals.size).toBe(2));
207
+
208
+ dispatchToolCancel(window.location.origin, parentWindow, "tool-a");
209
+
210
+ expect(signals.get("tool-a")?.aborted).toBe(true);
211
+ expect(signals.get("tool-b")?.aborted).toBe(false);
212
+ });
213
+
214
+ it("aborts an earlier call when a duplicate ID arrives", async () => {
215
+ const signals: AbortSignal[] = [];
216
+ const execute = vi.fn(
217
+ async (_args: unknown, context: { abortSignal: AbortSignal }) => {
218
+ signals.push(context.abortSignal);
219
+ await new Promise<never>((_resolve, reject) => {
220
+ context.abortSignal.addEventListener(
221
+ "abort",
222
+ () => reject(context.abortSignal.reason),
223
+ { once: true },
224
+ );
225
+ });
226
+ },
227
+ );
228
+ AssistantFrameProvider.addModelContextProvider({
229
+ getModelContext: () => ({ tools: { sensitiveTool: { execute } } }),
230
+ });
231
+
232
+ dispatchToolCall(window.location.origin, parentWindow, "duplicate");
233
+ await vi.waitFor(() => expect(signals).toHaveLength(1));
234
+ dispatchToolCall(window.location.origin, parentWindow, "duplicate");
235
+ await vi.waitFor(() => expect(signals).toHaveLength(2));
236
+
237
+ expect(signals[0]?.aborted).toBe(true);
238
+ expect(signals[1]?.aborted).toBe(false);
239
+ });
240
+
241
+ it("aborts in-flight tool calls when the provider is disposed", async () => {
242
+ let toolSignal: AbortSignal | undefined;
243
+ const execute = vi.fn(
244
+ async (_args: unknown, context: { abortSignal: AbortSignal }) => {
245
+ toolSignal = context.abortSignal;
246
+ await new Promise<never>((_resolve, reject) => {
247
+ context.abortSignal.addEventListener(
248
+ "abort",
249
+ () => reject(context.abortSignal.reason),
250
+ { once: true },
251
+ );
252
+ });
253
+ },
254
+ );
255
+ AssistantFrameProvider.addModelContextProvider({
256
+ getModelContext: () => ({ tools: { sensitiveTool: { execute } } }),
257
+ });
258
+
259
+ dispatchToolCall(window.location.origin);
260
+ await vi.waitFor(() => expect(toolSignal).toBeDefined());
261
+
262
+ AssistantFrameProvider.dispose();
263
+
264
+ expect(toolSignal?.aborted).toBe(true);
265
+ expect(parentWindow.postMessage).toHaveBeenCalledWith(
266
+ {
267
+ channel: FRAME_MESSAGE_CHANNEL,
268
+ message: {
269
+ type: "tool-result",
270
+ id: "tool-call-1",
271
+ error: "AssistantFrameProvider has been disposed",
272
+ },
273
+ },
274
+ { targetOrigin: window.location.origin },
275
+ );
276
+ await new Promise((resolve) => setTimeout(resolve, 0));
277
+ const toolResults = vi
278
+ .mocked(parentWindow.postMessage)
279
+ .mock.calls.filter(
280
+ ([data]) =>
281
+ (data as { message?: { type?: string } }).message?.type ===
282
+ "tool-result",
283
+ );
284
+ expect(toolResults).toHaveLength(1);
285
+ });
286
+
287
+ it("upgrades a wildcard origin policy when a strict provider registers", async () => {
288
+ AssistantFrameProvider.addModelContextProvider(
289
+ { getModelContext: () => ({}) },
290
+ "*",
291
+ );
292
+
293
+ const execute = vi.fn(async () => "result");
294
+ AssistantFrameProvider.addModelContextProvider(
295
+ {
296
+ getModelContext: () => ({
297
+ tools: {
298
+ sensitiveTool: { execute },
299
+ },
300
+ }),
301
+ },
302
+ "https://parent.example",
303
+ );
304
+
305
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
306
+ expect.anything(),
307
+ "https://parent.example",
308
+ );
309
+
310
+ dispatchToolCall("https://untrusted.example");
311
+
312
+ expect(execute).not.toHaveBeenCalled();
313
+
314
+ dispatchToolCall("https://parent.example");
315
+
316
+ await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
317
+ });
318
+
319
+ it("does not downgrade a strict origin policy for a wildcard provider", async () => {
320
+ const execute = vi.fn(async () => "result");
321
+ AssistantFrameProvider.addModelContextProvider(
322
+ {
323
+ getModelContext: () => ({
324
+ tools: {
325
+ sensitiveTool: { execute },
326
+ },
327
+ }),
328
+ },
329
+ "https://parent.example",
330
+ );
331
+ AssistantFrameProvider.addModelContextProvider(
332
+ { getModelContext: () => ({}) },
333
+ "*",
334
+ );
335
+
336
+ dispatchToolCall("https://untrusted.example");
337
+
338
+ expect(execute).not.toHaveBeenCalled();
339
+
340
+ dispatchToolCall("https://parent.example");
341
+
342
+ await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
343
+ });
344
+
345
+ it("rejects conflicting strict origin policies", () => {
346
+ AssistantFrameProvider.addModelContextProvider(
347
+ { getModelContext: () => ({}) },
348
+ "https://first.example",
349
+ );
350
+
351
+ expect(() =>
352
+ AssistantFrameProvider.addModelContextProvider(
353
+ { getModelContext: () => ({}) },
354
+ "https://second.example",
355
+ ),
356
+ ).toThrow(
357
+ 'AssistantFrameProvider cannot register conflicting target origins: "https://first.example" and "https://second.example"',
358
+ );
359
+ });
360
+
361
+ it("rolls back a provider when registration fails", () => {
362
+ const execute = vi.fn(async () => "result");
363
+ expect(() =>
364
+ AssistantFrameProvider.addModelContextProvider(
365
+ {
366
+ getModelContext: () => ({
367
+ tools: { sensitiveTool: { execute } },
368
+ }),
369
+ subscribe: () => {
370
+ throw new Error("subscribe failed");
371
+ },
372
+ },
373
+ "https://first.example",
374
+ ),
375
+ ).toThrow("subscribe failed");
376
+
377
+ dispatchToolCall("https://first.example");
378
+ expect(execute).not.toHaveBeenCalled();
379
+
380
+ expect(() =>
381
+ AssistantFrameProvider.addModelContextProvider(
382
+ { getModelContext: () => ({}) },
383
+ "https://second.example",
384
+ ),
385
+ ).not.toThrow();
386
+ });
387
+
388
+ it("keeps an existing registration when the same provider fails to register again", async () => {
389
+ const execute = vi.fn(async () => "result");
390
+ const firstUnsubscribe = vi.fn();
391
+ let subscriptionCount = 0;
392
+ const provider = {
393
+ getModelContext: () => ({
394
+ tools: { sensitiveTool: { execute } },
78
395
  }),
396
+ subscribe: () => {
397
+ subscriptionCount += 1;
398
+ if (subscriptionCount === 1) return firstUnsubscribe;
399
+ throw new Error("second subscribe failed");
400
+ },
401
+ };
402
+ const releaseFirst = AssistantFrameProvider.addModelContextProvider(
403
+ provider,
404
+ "https://parent.example",
79
405
  );
80
406
 
407
+ expect(() =>
408
+ AssistantFrameProvider.addModelContextProvider(
409
+ provider,
410
+ "https://parent.example",
411
+ ),
412
+ ).toThrow("second subscribe failed");
413
+ expect(firstUnsubscribe).not.toHaveBeenCalled();
414
+
415
+ dispatchToolCall("https://parent.example");
416
+
417
+ await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
418
+ releaseFirst();
419
+ expect(firstUnsubscribe).toHaveBeenCalledOnce();
420
+ });
421
+
422
+ it("merges a provider registered more than once only once", () => {
423
+ const provider = {
424
+ getModelContext: () => ({ system: "shared system" }),
425
+ };
426
+
427
+ AssistantFrameProvider.addModelContextProvider(provider);
428
+ AssistantFrameProvider.addModelContextProvider(provider);
429
+
430
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
431
+ {
432
+ channel: FRAME_MESSAGE_CHANNEL,
433
+ message: {
434
+ type: "model-context-update",
435
+ context: {
436
+ system: "shared system",
437
+ tools: {},
438
+ },
439
+ },
440
+ },
441
+ window.location.origin,
442
+ );
443
+ });
444
+
445
+ it("releases a subscription when the initial broadcast fails", () => {
446
+ const unsubscribe = vi.fn();
447
+ expect(() =>
448
+ AssistantFrameProvider.addModelContextProvider(
449
+ {
450
+ getModelContext: () => {
451
+ throw new Error("context failed");
452
+ },
453
+ subscribe: () => unsubscribe,
454
+ },
455
+ "https://first.example",
456
+ ),
457
+ ).toThrow("context failed");
458
+
459
+ expect(unsubscribe).toHaveBeenCalledOnce();
460
+ expect(() =>
461
+ AssistantFrameProvider.addModelContextProvider(
462
+ { getModelContext: () => ({}) },
463
+ "https://second.example",
464
+ ),
465
+ ).not.toThrow();
466
+ });
467
+
468
+ it("reports rollback cleanup failures without replacing the original error", () => {
469
+ const contextError = new Error("context failed");
470
+ const unsubscribeError = new Error("unsubscribe failed");
471
+ const consoleError = vi
472
+ .spyOn(console, "error")
473
+ .mockImplementation(() => undefined);
474
+
475
+ expect(() =>
476
+ AssistantFrameProvider.addModelContextProvider({
477
+ getModelContext: () => {
478
+ throw contextError;
479
+ },
480
+ subscribe: () => () => {
481
+ throw unsubscribeError;
482
+ },
483
+ }),
484
+ ).toThrow(contextError);
485
+
486
+ expect(consoleError).toHaveBeenCalledWith(unsubscribeError);
487
+ });
488
+
489
+ it("cleans up provider state when its unsubscribe throws", () => {
490
+ const unsubscribe = vi.fn(() => {
491
+ throw new Error("unsubscribe failed");
492
+ });
493
+ const release = AssistantFrameProvider.addModelContextProvider(
494
+ {
495
+ getModelContext: () => ({}),
496
+ subscribe: () => unsubscribe,
497
+ },
498
+ "https://first.example",
499
+ );
500
+
501
+ expect(release).toThrow("unsubscribe failed");
502
+ expect(unsubscribe).toHaveBeenCalledOnce();
503
+ expect(() =>
504
+ AssistantFrameProvider.addModelContextProvider(
505
+ { getModelContext: () => ({}) },
506
+ "https://second.example",
507
+ ),
508
+ ).not.toThrow();
509
+ });
510
+
511
+ it("finishes disposal when a provider unsubscribe throws", () => {
512
+ const error = new Error("unsubscribe failed");
513
+ const firstUnsubscribe = vi.fn(() => {
514
+ throw error;
515
+ });
516
+ const secondUnsubscribe = vi.fn();
517
+ AssistantFrameProvider.addModelContextProvider({
518
+ getModelContext: () => ({}),
519
+ subscribe: () => firstUnsubscribe,
520
+ });
521
+ AssistantFrameProvider.addModelContextProvider({
522
+ getModelContext: () => ({}),
523
+ subscribe: () => secondUnsubscribe,
524
+ });
525
+
526
+ expect(() => AssistantFrameProvider.dispose()).toThrow(error);
527
+ expect(firstUnsubscribe).toHaveBeenCalledOnce();
528
+ expect(secondUnsubscribe).toHaveBeenCalledOnce();
529
+
530
+ expect(() =>
531
+ AssistantFrameProvider.addModelContextProvider(
532
+ { getModelContext: () => ({}) },
533
+ "https://new.example",
534
+ ),
535
+ ).not.toThrow();
536
+ expect(window.addEventListener).toHaveBeenCalledTimes(2);
537
+ });
538
+
539
+ it("returns to the same-origin policy after every provider unsubscribes", () => {
540
+ const unsubscribe = AssistantFrameProvider.addModelContextProvider(
541
+ { getModelContext: () => ({}) },
542
+ "https://first.example",
543
+ );
544
+
545
+ unsubscribe();
546
+
547
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
548
+ expect.anything(),
549
+ window.location.origin,
550
+ );
551
+
552
+ expect(() =>
553
+ AssistantFrameProvider.addModelContextProvider(
554
+ { getModelContext: () => ({}) },
555
+ "https://second.example",
556
+ ),
557
+ ).not.toThrow();
558
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
559
+ expect.anything(),
560
+ "https://second.example",
561
+ );
562
+ });
563
+
564
+ it("treats a second unsubscribe as a no-op", () => {
565
+ const unsubscribe = AssistantFrameProvider.addModelContextProvider(
566
+ { getModelContext: () => ({}) },
567
+ "https://first.example",
568
+ );
569
+
570
+ unsubscribe();
571
+ unsubscribe();
572
+
573
+ const unsubscribeSecond = AssistantFrameProvider.addModelContextProvider(
574
+ { getModelContext: () => ({}) },
575
+ "https://second.example",
576
+ );
577
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
578
+ expect.anything(),
579
+ "https://second.example",
580
+ );
581
+
582
+ unsubscribeSecond();
583
+ unsubscribeSecond();
584
+
585
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
586
+ expect.anything(),
587
+ window.location.origin,
588
+ );
589
+ });
590
+
591
+ it("recomputes the origin policy from providers that remain", () => {
592
+ AssistantFrameProvider.addModelContextProvider(
593
+ { getModelContext: () => ({}) },
594
+ "*",
595
+ );
596
+ const unsubscribeStrict = AssistantFrameProvider.addModelContextProvider(
597
+ { getModelContext: () => ({}) },
598
+ "https://parent.example",
599
+ );
600
+
601
+ unsubscribeStrict();
602
+
603
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
604
+ expect.anything(),
605
+ "*",
606
+ );
607
+ });
608
+
609
+ it("returns to the same-origin policy after a wildcard provider unsubscribes", () => {
610
+ const unsubscribe = AssistantFrameProvider.addModelContextProvider(
611
+ { getModelContext: () => ({}) },
612
+ "*",
613
+ );
614
+
615
+ unsubscribe();
616
+
617
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
618
+ expect.anything(),
619
+ window.location.origin,
620
+ );
621
+ });
622
+
623
+ it("allows opting back into a wildcard policy after every provider unsubscribes", () => {
624
+ const unsubscribe = AssistantFrameProvider.addModelContextProvider({
625
+ getModelContext: () => ({}),
626
+ });
627
+ unsubscribe();
628
+
629
+ AssistantFrameProvider.addModelContextProvider(
630
+ { getModelContext: () => ({}) },
631
+ "*",
632
+ );
633
+
634
+ expect(parentWindow.postMessage).toHaveBeenLastCalledWith(
635
+ expect.anything(),
636
+ "*",
637
+ );
638
+ });
639
+
640
+ it("keeps a shared strict origin after one provider unsubscribes", async () => {
641
+ const unsubscribeFirst = AssistantFrameProvider.addModelContextProvider(
642
+ { getModelContext: () => ({}) },
643
+ "https://parent.example",
644
+ );
645
+ const execute = vi.fn(async () => "result");
646
+ AssistantFrameProvider.addModelContextProvider(
647
+ {
648
+ getModelContext: () => ({
649
+ tools: {
650
+ sensitiveTool: { execute },
651
+ },
652
+ }),
653
+ },
654
+ "https://parent.example",
655
+ );
656
+
657
+ unsubscribeFirst();
658
+
659
+ dispatchToolCall("https://untrusted.example");
660
+ expect(execute).not.toHaveBeenCalled();
661
+
662
+ dispatchToolCall("https://parent.example");
81
663
  await vi.waitFor(() => expect(execute).toHaveBeenCalledOnce());
82
664
  });
83
665
  });