@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
@@ -7,7 +7,11 @@ import type {
7
7
  } from "../../runtime/utils/chat-model-adapter";
8
8
  import type { AppendMessage } from "../../types/message";
9
9
  import type { LocalRuntimeOptionsBase } from "./local-runtime-options";
10
- import type { ExportedMessageRepositoryItem } from "../../runtime/utils/message-repository";
10
+ import {
11
+ ExportedMessageRepository,
12
+ type ExportedMessageRepositoryItem,
13
+ } from "../../runtime/utils/message-repository";
14
+ import type { ThreadMessageLike } from "../../runtime/utils/thread-message-like";
11
15
  import type { ThreadSuggestion } from "../../runtime/interfaces/thread-runtime-core";
12
16
  import { isMessageNotSentError } from "../../types/error";
13
17
 
@@ -673,6 +677,325 @@ describe("LocalThreadRuntimeCore tool approvals", () => {
673
677
  });
674
678
 
675
679
  describe("LocalThreadRuntimeCore cancellation", () => {
680
+ it("settles a superseded tool-call result without aborting its replacement", async () => {
681
+ let resolveFirst!: (result: ChatModelRunResult) => void;
682
+ let resolveSecond!: (result: ChatModelRunResult) => void;
683
+ const firstResult = new Promise<ChatModelRunResult>((resolve) => {
684
+ resolveFirst = resolve;
685
+ });
686
+ const secondResult = new Promise<ChatModelRunResult>((resolve) => {
687
+ resolveSecond = resolve;
688
+ });
689
+ const runs: ChatModelRunOptions[] = [];
690
+ const thread = createThread({
691
+ run(options) {
692
+ runs.push(options);
693
+ return runs.length === 1 ? firstResult : secondResult;
694
+ },
695
+ });
696
+
697
+ const firstAppend = thread.append(userMessage("first"));
698
+ await flush();
699
+ const supersededMessageId = thread.messages.at(-1)!.id;
700
+ const secondAppend = thread.append({
701
+ ...userMessage("second"),
702
+ parentId: supersededMessageId,
703
+ });
704
+ await flush();
705
+
706
+ expect(runs).toHaveLength(2);
707
+ expect(runs[0]?.abortSignal.aborted).toBe(true);
708
+ expect(runs[1]?.abortSignal.aborted).toBe(false);
709
+
710
+ resolveFirst(toolCallResult("lookup_weather"));
711
+ await firstAppend;
712
+
713
+ expect(runs).toHaveLength(2);
714
+ expect(runs[1]?.abortSignal.aborted).toBe(false);
715
+ expect(
716
+ thread.messages.find((item) => item.id === supersededMessageId)?.status,
717
+ ).toEqual({
718
+ type: "incomplete",
719
+ reason: "cancelled",
720
+ });
721
+
722
+ resolveSecond({ content: [{ type: "text", text: "replacement" }] });
723
+ await secondAppend;
724
+ });
725
+
726
+ it("cancels a superseded approval pause", async () => {
727
+ let resolveFirst!: (result: ChatModelRunResult) => void;
728
+ let resolveSecond!: (result: ChatModelRunResult) => void;
729
+ const firstResult = new Promise<ChatModelRunResult>((resolve) => {
730
+ resolveFirst = resolve;
731
+ });
732
+ const secondResult = new Promise<ChatModelRunResult>((resolve) => {
733
+ resolveSecond = resolve;
734
+ });
735
+ const runs: ChatModelRunOptions[] = [];
736
+ const thread = createThread({
737
+ run(options) {
738
+ runs.push(options);
739
+ return runs.length === 1 ? firstResult : secondResult;
740
+ },
741
+ });
742
+
743
+ const firstAppend = thread.append(userMessage("first"));
744
+ await flush();
745
+ const supersededMessageId = thread.messages.at(-1)!.id;
746
+ const secondAppend = thread.append({
747
+ ...userMessage("second"),
748
+ parentId: supersededMessageId,
749
+ });
750
+ await flush();
751
+
752
+ resolveFirst(toolCallResult("deploy", { id: "approval-1" }));
753
+ await firstAppend;
754
+
755
+ expect(
756
+ thread.messages.find((item) => item.id === supersededMessageId)?.status,
757
+ ).toEqual({
758
+ type: "incomplete",
759
+ reason: "cancelled",
760
+ });
761
+
762
+ resolveSecond({ content: [{ type: "text", text: "replacement" }] });
763
+ await secondAppend;
764
+ });
765
+
766
+ it("ignores a stale result after addToolResult replaces the same message", async () => {
767
+ let releaseFirst!: () => void;
768
+ let resolveSecond!: (result: ChatModelRunResult) => void;
769
+ const firstTeardown = new Promise<void>((resolve) => {
770
+ releaseFirst = resolve;
771
+ });
772
+ const secondResult = new Promise<ChatModelRunResult>((resolve) => {
773
+ resolveSecond = resolve;
774
+ });
775
+ const runs: ChatModelRunOptions[] = [];
776
+ const thread = createThread({
777
+ run(options) {
778
+ runs.push(options);
779
+ if (runs.length === 1) {
780
+ return (async function* () {
781
+ yield toolCallResult("send_email");
782
+ await firstTeardown;
783
+ yield toolCallResult("stale_tool");
784
+ })();
785
+ }
786
+ return secondResult;
787
+ },
788
+ });
789
+
790
+ const firstAppend = thread.append(userMessage("send an email"));
791
+ await flush();
792
+ const messageId = thread.messages.at(-1)!.id;
793
+
794
+ thread.addToolResult({
795
+ messageId,
796
+ toolCallId: "call-send_email",
797
+ toolName: "send_email",
798
+ result: { approved: true },
799
+ isError: false,
800
+ });
801
+ await flush();
802
+
803
+ expect(runs).toHaveLength(2);
804
+ expect(runs[1]?.abortSignal.aborted).toBe(false);
805
+
806
+ releaseFirst();
807
+ await firstAppend;
808
+
809
+ const toolCall = thread.messages
810
+ .find((item) => item.id === messageId)
811
+ ?.content.find(
812
+ (part) =>
813
+ part.type === "tool-call" && part.toolCallId === "call-send_email",
814
+ );
815
+ expect(toolCall?.result).toEqual({ approved: true });
816
+ expect(runs[1]?.abortSignal.aborted).toBe(false);
817
+
818
+ resolveSecond({ content: [{ type: "text", text: "replacement" }] });
819
+ await vi.waitFor(() => {
820
+ expect(
821
+ thread.messages.find((item) => item.id === messageId)?.status.type,
822
+ ).toBe("complete");
823
+ });
824
+ });
825
+
826
+ it("ignores stale chunks after a partial tool result updates the active message", async () => {
827
+ let releaseTeardown!: () => void;
828
+ const teardown = new Promise<void>((resolve) => {
829
+ releaseTeardown = resolve;
830
+ });
831
+ const runs: ChatModelRunOptions[] = [];
832
+ const thread = createThread({
833
+ run(options) {
834
+ runs.push(options);
835
+ return (async function* () {
836
+ yield {
837
+ content: [
838
+ toolCallPart("send_email"),
839
+ toolCallPart("deploy", { id: "approval-1" }),
840
+ ],
841
+ status: {
842
+ type: "requires-action",
843
+ reason: "tool-calls",
844
+ },
845
+ } satisfies ChatModelRunResult;
846
+ await teardown;
847
+ yield { content: [{ type: "text", text: "stale" }] };
848
+ })();
849
+ },
850
+ });
851
+
852
+ const appendPromise = thread.append(userMessage("send and deploy"));
853
+ await flush();
854
+ const messageId = thread.messages.at(-1)!.id;
855
+
856
+ thread.addToolResult({
857
+ messageId,
858
+ toolCallId: "call-send_email",
859
+ toolName: "send_email",
860
+ result: { approved: true },
861
+ isError: false,
862
+ });
863
+ await flush();
864
+
865
+ expect(runs).toHaveLength(1);
866
+
867
+ releaseTeardown();
868
+ await appendPromise;
869
+
870
+ const message = thread.messages.find((item) => item.id === messageId);
871
+ const sendEmail = message?.content.find(
872
+ (part) =>
873
+ part.type === "tool-call" && part.toolCallId === "call-send_email",
874
+ );
875
+ const deploy = message?.content.find(
876
+ (part) => part.type === "tool-call" && part.toolCallId === "call-deploy",
877
+ );
878
+ expect(sendEmail?.result).toEqual({ approved: true });
879
+ expect(deploy?.approval).toEqual({ id: "approval-1" });
880
+ expect(message?.status.type).toBe("requires-action");
881
+ });
882
+
883
+ it("ignores a superseded result after its message is removed", async () => {
884
+ let resolveFirst!: (result: ChatModelRunResult) => void;
885
+ const firstResult = new Promise<ChatModelRunResult>((resolve) => {
886
+ resolveFirst = resolve;
887
+ });
888
+ let runCount = 0;
889
+ const thread = createThread({
890
+ run() {
891
+ runCount++;
892
+ if (runCount === 1) return firstResult;
893
+ return Promise.resolve({
894
+ content: [{ type: "text", text: "replacement" }],
895
+ });
896
+ },
897
+ });
898
+
899
+ const firstAppend = thread.append(userMessage("first"));
900
+ await flush();
901
+ await thread.append(userMessage("second"));
902
+ thread.reset();
903
+
904
+ resolveFirst(toolCallResult("lookup_weather"));
905
+
906
+ await expect(firstAppend).resolves.toBeUndefined();
907
+ expect(thread.messages).toHaveLength(0);
908
+ });
909
+
910
+ it("does not continue after cancelRun when a delayed tool call resolves", async () => {
911
+ let resolveFirst!: (result: ChatModelRunResult) => void;
912
+ const firstResult = new Promise<ChatModelRunResult>((resolve) => {
913
+ resolveFirst = resolve;
914
+ });
915
+ const runs: ChatModelRunOptions[] = [];
916
+ const thread = createThread({
917
+ run(options) {
918
+ runs.push(options);
919
+ if (runs.length === 1) return firstResult;
920
+ return Promise.resolve({
921
+ content: [{ type: "text", text: "unexpected continuation" }],
922
+ status: { type: "complete", reason: "stop" },
923
+ });
924
+ },
925
+ });
926
+
927
+ const appendPromise = thread.append(userMessage("first"));
928
+ await flush();
929
+
930
+ thread.cancelRun();
931
+ resolveFirst(toolCallResult("lookup_weather"));
932
+ await appendPromise;
933
+
934
+ expect(runs).toHaveLength(1);
935
+ expect(thread.messages.at(-1)?.status).toEqual({
936
+ type: "incomplete",
937
+ reason: "cancelled",
938
+ });
939
+ });
940
+
941
+ it("preserves a terminal adapter status when cancelled during teardown", async () => {
942
+ let releaseTeardown!: () => void;
943
+ const teardown = new Promise<void>((resolve) => {
944
+ releaseTeardown = resolve;
945
+ });
946
+ const thread = createThread({
947
+ async *run() {
948
+ yield {
949
+ content: [{ type: "text", text: "done" }],
950
+ status: { type: "complete", reason: "stop" },
951
+ };
952
+ await teardown;
953
+ },
954
+ });
955
+
956
+ const appendPromise = thread.append(userMessage("first"));
957
+ await flush();
958
+
959
+ expect(thread.messages.at(-1)?.status).toEqual({
960
+ type: "complete",
961
+ reason: "stop",
962
+ });
963
+
964
+ thread.cancelRun();
965
+ releaseTeardown();
966
+ await appendPromise;
967
+
968
+ expect(thread.messages.at(-1)?.status).toEqual({
969
+ type: "complete",
970
+ reason: "stop",
971
+ });
972
+ });
973
+
974
+ it("preserves an approval pause when the adapter yields after cancellation", async () => {
975
+ let releaseTeardown!: () => void;
976
+ const teardown = new Promise<void>((resolve) => {
977
+ releaseTeardown = resolve;
978
+ });
979
+ const thread = createThread({
980
+ async *run() {
981
+ yield toolCallResult("deploy", { id: "approval-1" });
982
+ await teardown;
983
+ yield { content: [{ type: "text", text: "late" }] };
984
+ },
985
+ });
986
+
987
+ const appendPromise = thread.append(userMessage("deploy"));
988
+ await flush();
989
+
990
+ expect(thread.messages.at(-1)?.status?.type).toBe("requires-action");
991
+
992
+ thread.cancelRun();
993
+ releaseTeardown();
994
+ await appendPromise;
995
+
996
+ expect(thread.messages.at(-1)?.status?.type).toBe("requires-action");
997
+ });
998
+
676
999
  it("keeps a replacement run cancellable after the previous run settles", async () => {
677
1000
  let releaseFirst!: () => void;
678
1001
  let releaseSecond!: () => void;
@@ -1683,3 +2006,170 @@ describe("LocalRuntimeCore composer.canCancel", () => {
1683
2006
  thread.cancelRun();
1684
2007
  });
1685
2008
  });
2009
+
2010
+ describe("LocalThreadRuntimeCore imported approvals", () => {
2011
+ const pausedOnApproval = (
2012
+ approval: { id: string } | undefined,
2013
+ extra: Record<string, unknown> = {},
2014
+ ): ThreadMessageLike[] => [
2015
+ { role: "user", content: [{ type: "text", text: "send an email" }] },
2016
+ {
2017
+ role: "assistant",
2018
+ content: [
2019
+ {
2020
+ type: "tool-call",
2021
+ toolCallId: "call-send_email",
2022
+ toolName: "send_email",
2023
+ args: {},
2024
+ argsText: "{}",
2025
+ ...(approval !== undefined ? { approval } : {}),
2026
+ ...extra,
2027
+ },
2028
+ ],
2029
+ },
2030
+ ];
2031
+
2032
+ const createImportedThread = (
2033
+ initialMessages: ThreadMessageLike[],
2034
+ history?: LocalRuntimeOptionsBase["adapters"]["history"],
2035
+ ) => {
2036
+ const runs: ChatModelRunOptions[] = [];
2037
+ const core = new LocalRuntimeCore(
2038
+ {
2039
+ adapters: {
2040
+ chatModel: {
2041
+ async run(options) {
2042
+ runs.push(options);
2043
+ return { content: [{ type: "text", text: "done" }] };
2044
+ },
2045
+ },
2046
+ ...(history !== undefined && { history }),
2047
+ },
2048
+ unstable_humanToolNames: ["send_email"],
2049
+ },
2050
+ initialMessages,
2051
+ );
2052
+ return { thread: core.threads.getMainThreadRuntimeCore(), runs };
2053
+ };
2054
+
2055
+ it("resumes the run after approving an imported pending approval", async () => {
2056
+ const { thread, runs } = createImportedThread(
2057
+ pausedOnApproval({ id: "a1" }),
2058
+ );
2059
+
2060
+ expect(thread.messages.at(-1)?.status).toMatchObject({
2061
+ type: "requires-action",
2062
+ reason: "tool-calls",
2063
+ });
2064
+
2065
+ thread.respondToToolApproval({ approvalId: "a1", approved: true });
2066
+ await flush();
2067
+
2068
+ expect(runs).toHaveLength(1);
2069
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
2070
+ });
2071
+
2072
+ it("keeps the interrupt reason for an imported interrupted tool call", () => {
2073
+ const { thread } = createImportedThread(
2074
+ pausedOnApproval(undefined, {
2075
+ interrupt: { type: "human", payload: { question: "which one?" } },
2076
+ }),
2077
+ );
2078
+
2079
+ expect(thread.messages.at(-1)?.status).toMatchObject({
2080
+ type: "requires-action",
2081
+ reason: "interrupt",
2082
+ });
2083
+ });
2084
+
2085
+ it("resumes the run after approving an approval restored by the history adapter", async () => {
2086
+ const { thread, runs } = createImportedThread([], {
2087
+ async load() {
2088
+ return ExportedMessageRepository.fromArray(
2089
+ pausedOnApproval({ id: "a1" }),
2090
+ );
2091
+ },
2092
+ async append() {},
2093
+ async update() {},
2094
+ });
2095
+
2096
+ thread.__internal_load();
2097
+ await flush();
2098
+
2099
+ expect(thread.messages.at(-1)?.status).toMatchObject({
2100
+ type: "requires-action",
2101
+ reason: "tool-calls",
2102
+ });
2103
+
2104
+ thread.respondToToolApproval({ approvalId: "a1", approved: true });
2105
+ await flush();
2106
+
2107
+ expect(runs).toHaveLength(1);
2108
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
2109
+ });
2110
+
2111
+ it("normalizes a serialized approval pause that lost its auto-status marker", async () => {
2112
+ const { thread, runs } = createImportedThread([]);
2113
+ thread.import(
2114
+ JSON.parse(
2115
+ JSON.stringify(
2116
+ ExportedMessageRepository.fromArray(pausedOnApproval({ id: "a1" })),
2117
+ ),
2118
+ ),
2119
+ );
2120
+
2121
+ expect(thread.messages.at(-1)?.status).toMatchObject({
2122
+ type: "requires-action",
2123
+ reason: "tool-calls",
2124
+ });
2125
+
2126
+ thread.respondToToolApproval({ approvalId: "a1", approved: true });
2127
+ await flush();
2128
+
2129
+ expect(runs).toHaveLength(1);
2130
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
2131
+ });
2132
+
2133
+ it("completes an imported interrupt that has nothing left to act on", () => {
2134
+ const { thread } = createImportedThread([
2135
+ { role: "user", content: [{ type: "text", text: "send an email" }] },
2136
+ {
2137
+ role: "assistant",
2138
+ status: { type: "requires-action", reason: "interrupt" },
2139
+ content: [
2140
+ {
2141
+ type: "tool-call",
2142
+ toolCallId: "call-send_email",
2143
+ toolName: "send_email",
2144
+ args: {},
2145
+ argsText: "{}",
2146
+ result: { sent: true },
2147
+ },
2148
+ ],
2149
+ } as ThreadMessageLike,
2150
+ ]);
2151
+
2152
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
2153
+ });
2154
+
2155
+ it("resumes the run after adding a result to an imported resultless tool call", async () => {
2156
+ const { thread, runs } = createImportedThread(pausedOnApproval(undefined));
2157
+
2158
+ expect(thread.messages.at(-1)?.status).toMatchObject({
2159
+ type: "requires-action",
2160
+ reason: "tool-calls",
2161
+ });
2162
+
2163
+ thread.addToolResult({
2164
+ messageId: thread.messages.at(-1)!.id,
2165
+ toolCallId: "call-send_email",
2166
+ toolName: "send_email",
2167
+ result: { sent: true },
2168
+ isError: false,
2169
+ });
2170
+ await flush();
2171
+
2172
+ expect(runs).toHaveLength(1);
2173
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
2174
+ });
2175
+ });
@@ -5,6 +5,8 @@ import type {
5
5
  ChatModelRunResult,
6
6
  } from "../../runtime/utils/chat-model-adapter";
7
7
  import { shouldContinue } from "./should-continue";
8
+ import { getAutoStatus } from "../../runtime/utils/auto-status";
9
+ import type { ExportedMessageRepository } from "../../runtime/utils/message-repository";
8
10
  import type { LocalRuntimeOptionsBase } from "./local-runtime-options";
9
11
  import { consumeSuggestionResult } from "../../adapters/suggestion";
10
12
  import type {
@@ -21,7 +23,7 @@ import type {
21
23
  AppendMessage,
22
24
  ThreadAssistantMessage,
23
25
  } from "../../types/message";
24
- import type { RunConfig } from "../../types/message";
26
+ import type { RunConfig, ThreadMessage } from "../../types/message";
25
27
  import { MessageNotSentError, toAssistantError } from "../../types/error";
26
28
  import type { ModelContextProvider } from "../../model-context/types";
27
29
  import {
@@ -32,7 +34,7 @@ import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-a
32
34
  import {
33
35
  EMPTY_QUEUE_ITEMS,
34
36
  type QueueItemState,
35
- } from "../../store/scopes/queue-item";
37
+ } from "../../runtime/queue/queue-item";
36
38
  import {
37
39
  captureThreadRuntimeGeneration,
38
40
  invalidateThreadRuntime,
@@ -49,6 +51,45 @@ class AbortError extends Error {
49
51
  }
50
52
  }
51
53
 
54
+ // `shouldContinue` resumes only on `tool-calls` and `resumeToolCall` throws, so
55
+ // an imported `interrupt` with no interrupt payload would be stranded here.
56
+ // Provenance cannot gate it: a repository that went through JSON has no marker.
57
+ // The replacement stays content-derived, so a message with nothing resultless
58
+ // left to act on lands on `complete` rather than an unactionable pause.
59
+ const withLocalPauseReason = (message: ThreadMessage): ThreadMessage => {
60
+ if (
61
+ message.role !== "assistant" ||
62
+ message.status.type !== "requires-action" ||
63
+ message.status.reason !== "interrupt" ||
64
+ message.content.some(
65
+ (c) =>
66
+ c.type === "tool-call" && c.result === undefined && c.interrupt != null,
67
+ )
68
+ )
69
+ return message;
70
+ return {
71
+ ...message,
72
+ status: getAutoStatus(
73
+ false,
74
+ false,
75
+ false,
76
+ message.content.some(
77
+ (c) => c.type === "tool-call" && c.result === undefined,
78
+ ),
79
+ ),
80
+ };
81
+ };
82
+
83
+ const withLocalPauseReasons = (
84
+ data: ExportedMessageRepository,
85
+ ): ExportedMessageRepository => ({
86
+ ...data,
87
+ messages: data.messages.map((item) => ({
88
+ ...item,
89
+ message: withLocalPauseReason(item.message),
90
+ })),
91
+ });
92
+
52
93
  export class LocalThreadRuntimeCore
53
94
  extends BaseThreadRuntimeCore
54
95
  implements ThreadRuntimeCore
@@ -272,7 +313,7 @@ export class LocalThreadRuntimeCore
272
313
  this._loadPromise = promise
273
314
  .then((repo) => {
274
315
  if (!repo) return;
275
- this.repository.import(repo);
316
+ this.repository.import(withLocalPauseReasons(repo));
276
317
  if (repo.messages.length > 0) {
277
318
  this.ensureInitialized();
278
319
  }
@@ -434,6 +475,10 @@ export class LocalThreadRuntimeCore
434
475
  throw new Error("Runtime does not support exporting external states.");
435
476
  }
436
477
 
478
+ public override import(data: ExportedMessageRepository) {
479
+ super.import(withLocalPauseReasons(data));
480
+ }
481
+
437
482
  public importExternalState(): void {
438
483
  throw new Error("Runtime does not support importing external states.");
439
484
  }
@@ -501,9 +546,11 @@ export class LocalThreadRuntimeCore
501
546
  message,
502
547
  runConfig,
503
548
  alreadyPersisted,
549
+ run,
504
550
  runCallback,
505
551
  );
506
552
  runCallback = undefined;
553
+ if (this._activeRun !== run) break;
507
554
  } while (shouldContinue(message, this._options.unstable_humanToolNames));
508
555
  } finally {
509
556
  this._notifyEventSubscribers("runEnd", {});
@@ -548,6 +595,7 @@ export class LocalThreadRuntimeCore
548
595
  message: ThreadAssistantMessage,
549
596
  runConfig: RunConfig | undefined,
550
597
  alreadyPersisted: boolean,
598
+ run: { cancelled: boolean },
551
599
  runCallback?: ChatModelAdapter["run"],
552
600
  ) {
553
601
  const messages = parentId ? this.repository.getMessages(parentId) : [];
@@ -562,7 +610,23 @@ export class LocalThreadRuntimeCore
562
610
  const initialData = message.metadata?.unstable_data;
563
611
  const initialSteps = message.metadata?.steps;
564
612
  const initialCustom = message.metadata?.custom;
613
+ let hasStoredMessage = true;
614
+ try {
615
+ this.repository.getMessage(message.id);
616
+ } catch {
617
+ hasStoredMessage = false;
618
+ }
619
+ // Other writers replace the stored message object, so identity distinguishes this run from a newer owner.
620
+ const ownsMessage = () => {
621
+ if (!hasStoredMessage) return this._activeRun === run;
622
+ try {
623
+ return this.repository.getMessage(message.id).message === message;
624
+ } catch {
625
+ return false;
626
+ }
627
+ };
565
628
  const updateMessage = (m: Partial<ChatModelRunResult>) => {
629
+ if (!ownsMessage()) return;
566
630
  const newSteps = m.metadata?.steps;
567
631
  const steps = newSteps
568
632
  ? [...(initialSteps ?? []), ...newSteps]
@@ -609,6 +673,7 @@ export class LocalThreadRuntimeCore
609
673
  : undefined),
610
674
  };
611
675
  this.repository.addOrUpdateMessage(parentId, message);
676
+ hasStoredMessage = true;
612
677
  this._notifySubscribers();
613
678
  };
614
679
 
@@ -647,6 +712,12 @@ export class LocalThreadRuntimeCore
647
712
  this.adapters.chatModel.run.bind(this.adapters.chatModel);
648
713
 
649
714
  const abortSignal = abortController.signal;
715
+ const shouldCancelMessage = () =>
716
+ abortSignal.aborted &&
717
+ (message.status.type === "running" ||
718
+ (message.status.type === "requires-action" &&
719
+ (this._activeRun !== run ||
720
+ shouldContinue(message, this._options.unstable_humanToolNames))));
650
721
  const threadId = this._getThreadId?.();
651
722
  const promiseOrGenerator = runCallback({
652
723
  messages,
@@ -665,9 +736,11 @@ export class LocalThreadRuntimeCore
665
736
  if (Symbol.asyncIterator in promiseOrGenerator) {
666
737
  for await (const r of promiseOrGenerator) {
667
738
  if (abortSignal.aborted) {
668
- updateMessage({
669
- status: { type: "incomplete", reason: "cancelled" },
670
- });
739
+ if (shouldCancelMessage()) {
740
+ updateMessage({
741
+ status: { type: "incomplete", reason: "cancelled" },
742
+ });
743
+ }
671
744
  break;
672
745
  }
673
746
 
@@ -677,11 +750,13 @@ export class LocalThreadRuntimeCore
677
750
  updateMessage(await promiseOrGenerator);
678
751
  }
679
752
 
680
- if (message.status.type === "running") {
753
+ if (shouldCancelMessage()) {
754
+ updateMessage({
755
+ status: { type: "incomplete", reason: "cancelled" },
756
+ });
757
+ } else if (message.status.type === "running") {
681
758
  updateMessage({
682
- status: abortSignal.aborted
683
- ? { type: "incomplete", reason: "cancelled" }
684
- : { type: "complete", reason: "unknown" },
759
+ status: { type: "complete", reason: "unknown" },
685
760
  });
686
761
  }
687
762
  } catch (e) {
@@ -724,7 +799,7 @@ export class LocalThreadRuntimeCore
724
799
 
725
800
  // Pauses are written only for adapters that can rewrite the entry later;
726
801
  // an append-only adapter would strand a half-finished run in history.
727
- if (isTerminal || (isPausing && history?.update)) {
802
+ if (ownsMessage() && (isTerminal || (isPausing && history?.update))) {
728
803
  const write =
729
804
  alreadyPersisted && history?.update
730
805
  ? history.update.bind(history)