@nocobase/app-plugin-agents 2.0.0-beta.0 → 2.0.0-beta.10

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 (365) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +26 -4
  3. package/dist/client/chat/api.d.ts +6 -1
  4. package/dist/client/chat/api.d.ts.map +1 -1
  5. package/dist/client/chat/api.js +18 -0
  6. package/dist/client/chat/api.js.map +1 -1
  7. package/dist/client/chat/coalesced-invalidation.d.ts +3 -8
  8. package/dist/client/chat/coalesced-invalidation.d.ts.map +1 -1
  9. package/dist/client/chat/coalesced-invalidation.js +16 -21
  10. package/dist/client/chat/coalesced-invalidation.js.map +1 -1
  11. package/dist/client/chat/controller.d.ts +30 -3
  12. package/dist/client/chat/controller.d.ts.map +1 -1
  13. package/dist/client/chat/controller.js +477 -78
  14. package/dist/client/chat/controller.js.map +1 -1
  15. package/dist/client/chat/notices.d.ts +12 -0
  16. package/dist/client/chat/notices.d.ts.map +1 -0
  17. package/dist/client/chat/notices.js +15 -0
  18. package/dist/client/chat/notices.js.map +1 -0
  19. package/dist/client/chat/provider.d.ts.map +1 -1
  20. package/dist/client/chat/provider.js +7 -4
  21. package/dist/client/chat/provider.js.map +1 -1
  22. package/dist/client/chat/submissions.d.ts +16 -0
  23. package/dist/client/chat/submissions.d.ts.map +1 -1
  24. package/dist/client/chat/submissions.js +22 -0
  25. package/dist/client/chat/submissions.js.map +1 -1
  26. package/dist/client/chat/use-chat.d.ts +4 -2
  27. package/dist/client/chat/use-chat.d.ts.map +1 -1
  28. package/dist/client/chat/use-chat.js +83 -51
  29. package/dist/client/chat/use-chat.js.map +1 -1
  30. package/dist/client/chat.d.ts +1 -0
  31. package/dist/client/chat.d.ts.map +1 -1
  32. package/dist/client/chat.js +1 -0
  33. package/dist/client/chat.js.map +1 -1
  34. package/dist/client/components/page-container.js +1 -1
  35. package/dist/client/components/page-container.js.map +1 -1
  36. package/dist/client/components/route-child-page.d.ts +1 -1
  37. package/dist/client/components/route-child-page.d.ts.map +1 -1
  38. package/dist/client/components/route-child-page.js +18 -2
  39. package/dist/client/components/route-child-page.js.map +1 -1
  40. package/dist/client/components/route-overlay.d.ts +4 -1
  41. package/dist/client/components/route-overlay.d.ts.map +1 -1
  42. package/dist/client/components/route-overlay.js +32 -3
  43. package/dist/client/components/route-overlay.js.map +1 -1
  44. package/dist/client/components/ui/alert-dialog.d.ts +3 -1
  45. package/dist/client/components/ui/alert-dialog.d.ts.map +1 -1
  46. package/dist/client/components/ui/alert-dialog.js +2 -2
  47. package/dist/client/components/ui/alert-dialog.js.map +1 -1
  48. package/dist/client/components/unsaved-changes.d.ts.map +1 -1
  49. package/dist/client/components/unsaved-changes.js +17 -4
  50. package/dist/client/components/unsaved-changes.js.map +1 -1
  51. package/dist/client/components/use-route-overlay.d.ts +2 -9
  52. package/dist/client/components/use-route-overlay.d.ts.map +1 -1
  53. package/dist/client/components/use-route-overlay.js +5 -3
  54. package/dist/client/components/use-route-overlay.js.map +1 -1
  55. package/dist/client/hooks/use-realtime-topic.d.ts +1 -1
  56. package/dist/client/hooks/use-realtime-topic.d.ts.map +1 -1
  57. package/dist/client/hooks/use-realtime-topic.js +8 -4
  58. package/dist/client/hooks/use-realtime-topic.js.map +1 -1
  59. package/dist/client/kit.d.ts.map +1 -1
  60. package/dist/client/kit.js +30 -5
  61. package/dist/client/kit.js.map +1 -1
  62. package/dist/client/locales/de-DE.d.ts.map +1 -1
  63. package/dist/client/locales/de-DE.js +137 -109
  64. package/dist/client/locales/de-DE.js.map +1 -1
  65. package/dist/client/locales/en-US.d.ts +2 -0
  66. package/dist/client/locales/en-US.d.ts.map +1 -1
  67. package/dist/client/locales/en-US.js +4 -2
  68. package/dist/client/locales/en-US.js.map +1 -1
  69. package/dist/client/locales/es-ES.d.ts.map +1 -1
  70. package/dist/client/locales/es-ES.js +214 -186
  71. package/dist/client/locales/es-ES.js.map +1 -1
  72. package/dist/client/locales/fr-FR.d.ts.map +1 -1
  73. package/dist/client/locales/fr-FR.js +146 -118
  74. package/dist/client/locales/fr-FR.js.map +1 -1
  75. package/dist/client/locales/id-ID.d.ts.map +1 -1
  76. package/dist/client/locales/id-ID.js +135 -107
  77. package/dist/client/locales/id-ID.js.map +1 -1
  78. package/dist/client/locales/index.d.ts.map +1 -1
  79. package/dist/client/locales/index.js +2 -0
  80. package/dist/client/locales/index.js.map +1 -1
  81. package/dist/client/locales/ja-JP.d.ts.map +1 -1
  82. package/dist/client/locales/ja-JP.js +218 -190
  83. package/dist/client/locales/ja-JP.js.map +1 -1
  84. package/dist/client/locales/ko-KR.d.ts +5 -0
  85. package/dist/client/locales/ko-KR.d.ts.map +1 -0
  86. package/dist/client/locales/ko-KR.js +1311 -0
  87. package/dist/client/locales/ko-KR.js.map +1 -0
  88. package/dist/client/locales/pt-BR.d.ts.map +1 -1
  89. package/dist/client/locales/pt-BR.js +208 -180
  90. package/dist/client/locales/pt-BR.js.map +1 -1
  91. package/dist/client/locales/ru-RU.d.ts +5 -0
  92. package/dist/client/locales/ru-RU.d.ts.map +1 -0
  93. package/dist/client/locales/ru-RU.js +1341 -0
  94. package/dist/client/locales/ru-RU.js.map +1 -0
  95. package/dist/client/locales/runWait.en-US.d.ts +2 -0
  96. package/dist/client/locales/runWait.en-US.d.ts.map +1 -1
  97. package/dist/client/locales/runWait.en-US.js +2 -0
  98. package/dist/client/locales/runWait.en-US.js.map +1 -1
  99. package/dist/client/locales/runWait.zh-CN.d.ts.map +1 -1
  100. package/dist/client/locales/runWait.zh-CN.js +2 -0
  101. package/dist/client/locales/runWait.zh-CN.js.map +1 -1
  102. package/dist/client/locales/runtimes.en-US.d.ts +1 -0
  103. package/dist/client/locales/runtimes.en-US.d.ts.map +1 -1
  104. package/dist/client/locales/runtimes.en-US.js +3 -2
  105. package/dist/client/locales/runtimes.en-US.js.map +1 -1
  106. package/dist/client/locales/runtimes.zh-CN.d.ts.map +1 -1
  107. package/dist/client/locales/runtimes.zh-CN.js +3 -2
  108. package/dist/client/locales/runtimes.zh-CN.js.map +1 -1
  109. package/dist/client/locales/vi-VN.d.ts.map +1 -1
  110. package/dist/client/locales/vi-VN.js +174 -146
  111. package/dist/client/locales/vi-VN.js.map +1 -1
  112. package/dist/client/locales/zh-CN.d.ts.map +1 -1
  113. package/dist/client/locales/zh-CN.js +4 -2
  114. package/dist/client/locales/zh-CN.js.map +1 -1
  115. package/dist/client/locales/zh-TW.d.ts.map +1 -1
  116. package/dist/client/locales/zh-TW.js +151 -123
  117. package/dist/client/locales/zh-TW.js.map +1 -1
  118. package/dist/client/pages/agents/agent-dialog.js +4 -1
  119. package/dist/client/pages/agents/agent-dialog.js.map +1 -1
  120. package/dist/client/pages/runtimes/quota.d.ts +3 -2
  121. package/dist/client/pages/runtimes/quota.d.ts.map +1 -1
  122. package/dist/client/pages/runtimes/quota.js +8 -3
  123. package/dist/client/pages/runtimes/quota.js.map +1 -1
  124. package/dist/client/pages/runtimes/runner-runs.d.ts.map +1 -1
  125. package/dist/client/pages/runtimes/runner-runs.js +2 -8
  126. package/dist/client/pages/runtimes/runner-runs.js.map +1 -1
  127. package/dist/client/pages/runtimes/runner-tools.d.ts.map +1 -1
  128. package/dist/client/pages/runtimes/runner-tools.js +7 -16
  129. package/dist/client/pages/runtimes/runner-tools.js.map +1 -1
  130. package/dist/client/pages/runtimes/workspace-diagnostics.js +3 -3
  131. package/dist/client/pages/runtimes/workspace-diagnostics.js.map +1 -1
  132. package/dist/client/pages/usage/date-range-picker.d.ts.map +1 -1
  133. package/dist/client/pages/usage/date-range-picker.js +3 -1
  134. package/dist/client/pages/usage/date-range-picker.js.map +1 -1
  135. package/dist/client/routes.d.ts.map +1 -1
  136. package/dist/client/routes.js +7 -0
  137. package/dist/client/routes.js.map +1 -1
  138. package/dist/client/runs/format-wait.d.ts.map +1 -1
  139. package/dist/client/runs/format-wait.js +2 -0
  140. package/dist/client/runs/format-wait.js.map +1 -1
  141. package/dist/database/migrations/.manifest.json +16 -0
  142. package/dist/database/migrations/202611070001_ag_run_runner_pin.d.ts +4 -0
  143. package/dist/database/migrations/202611070001_ag_run_runner_pin.d.ts.map +1 -0
  144. package/dist/database/migrations/202611070001_ag_run_runner_pin.js +17 -0
  145. package/dist/database/migrations/202611070001_ag_run_runner_pin.js.map +1 -0
  146. package/dist/database/migrations/202611080001_ag_conversation_shares.d.ts +4 -0
  147. package/dist/database/migrations/202611080001_ag_conversation_shares.d.ts.map +1 -0
  148. package/dist/database/migrations/202611080001_ag_conversation_shares.js +25 -0
  149. package/dist/database/migrations/202611080001_ag_conversation_shares.js.map +1 -0
  150. package/dist/database/migrations/202611080002_ag_run_failure_params.d.ts +4 -0
  151. package/dist/database/migrations/202611080002_ag_run_failure_params.d.ts.map +1 -0
  152. package/dist/database/migrations/202611080002_ag_run_failure_params.js +17 -0
  153. package/dist/database/migrations/202611080002_ag_run_failure_params.js.map +1 -0
  154. package/dist/database/migrations/202611090001_ag_runner_preference.d.ts +9 -0
  155. package/dist/database/migrations/202611090001_ag_runner_preference.d.ts.map +1 -0
  156. package/dist/database/migrations/202611090001_ag_runner_preference.js +28 -0
  157. package/dist/database/migrations/202611090001_ag_runner_preference.js.map +1 -0
  158. package/dist/package.json +1 -1
  159. package/dist/server/composition.d.ts +7 -1
  160. package/dist/server/composition.d.ts.map +1 -1
  161. package/dist/server/composition.js +14 -2
  162. package/dist/server/composition.js.map +1 -1
  163. package/dist/server/core/agents/agent.service.d.ts +2 -1
  164. package/dist/server/core/agents/agent.service.d.ts.map +1 -1
  165. package/dist/server/core/agents/agent.service.js.map +1 -1
  166. package/dist/server/core/agents/agent.store.d.ts +2 -2
  167. package/dist/server/core/agents/agent.store.d.ts.map +1 -1
  168. package/dist/server/core/agents/agent.store.js +8 -2
  169. package/dist/server/core/agents/agent.store.js.map +1 -1
  170. package/dist/server/core/brief/brief.d.ts +10 -5
  171. package/dist/server/core/brief/brief.d.ts.map +1 -1
  172. package/dist/server/core/brief/brief.js +36 -13
  173. package/dist/server/core/brief/brief.js.map +1 -1
  174. package/dist/server/core/brief/index.d.ts +1 -1
  175. package/dist/server/core/brief/index.d.ts.map +1 -1
  176. package/dist/server/core/brief/index.js +1 -1
  177. package/dist/server/core/brief/index.js.map +1 -1
  178. package/dist/server/core/conversations/attachments.d.ts +2 -12
  179. package/dist/server/core/conversations/attachments.d.ts.map +1 -1
  180. package/dist/server/core/conversations/attachments.js +56 -14
  181. package/dist/server/core/conversations/attachments.js.map +1 -1
  182. package/dist/server/core/conversations/chat-settings.d.ts +1 -1
  183. package/dist/server/core/conversations/chat-settings.d.ts.map +1 -1
  184. package/dist/server/core/conversations/chat-settings.js +6 -0
  185. package/dist/server/core/conversations/chat-settings.js.map +1 -1
  186. package/dist/server/core/conversations/conversation.service.d.ts +3 -1
  187. package/dist/server/core/conversations/conversation.service.d.ts.map +1 -1
  188. package/dist/server/core/conversations/conversation.service.js +212 -21
  189. package/dist/server/core/conversations/conversation.service.js.map +1 -1
  190. package/dist/server/core/conversations/conversation.store.d.ts +2 -0
  191. package/dist/server/core/conversations/conversation.store.d.ts.map +1 -1
  192. package/dist/server/core/conversations/conversation.store.js.map +1 -1
  193. package/dist/server/core/conversations/index.d.ts +1 -0
  194. package/dist/server/core/conversations/index.d.ts.map +1 -1
  195. package/dist/server/core/conversations/links.d.ts +3 -0
  196. package/dist/server/core/conversations/links.d.ts.map +1 -1
  197. package/dist/server/core/conversations/links.js +43 -5
  198. package/dist/server/core/conversations/links.js.map +1 -1
  199. package/dist/server/core/conversations/ports.d.ts +8 -2
  200. package/dist/server/core/conversations/ports.d.ts.map +1 -1
  201. package/dist/server/core/conversations/shares.d.ts +38 -0
  202. package/dist/server/core/conversations/shares.d.ts.map +1 -0
  203. package/dist/server/core/conversations/shares.js +189 -0
  204. package/dist/server/core/conversations/shares.js.map +1 -0
  205. package/dist/server/core/runs/claim.d.ts +22 -3
  206. package/dist/server/core/runs/claim.d.ts.map +1 -1
  207. package/dist/server/core/runs/claim.js +260 -42
  208. package/dist/server/core/runs/claim.js.map +1 -1
  209. package/dist/server/core/runs/extensions.d.ts +30 -2
  210. package/dist/server/core/runs/extensions.d.ts.map +1 -1
  211. package/dist/server/core/runs/extensions.js +18 -1
  212. package/dist/server/core/runs/extensions.js.map +1 -1
  213. package/dist/server/core/runs/git-credentials.d.ts.map +1 -1
  214. package/dist/server/core/runs/git-credentials.js +24 -2
  215. package/dist/server/core/runs/git-credentials.js.map +1 -1
  216. package/dist/server/core/runs/identity.d.ts.map +1 -1
  217. package/dist/server/core/runs/identity.js +9 -0
  218. package/dist/server/core/runs/identity.js.map +1 -1
  219. package/dist/server/core/runs/index.d.ts +3 -2
  220. package/dist/server/core/runs/index.d.ts.map +1 -1
  221. package/dist/server/core/runs/index.js +2 -1
  222. package/dist/server/core/runs/index.js.map +1 -1
  223. package/dist/server/core/runs/lifecycle.d.ts.map +1 -1
  224. package/dist/server/core/runs/lifecycle.js +14 -0
  225. package/dist/server/core/runs/lifecycle.js.map +1 -1
  226. package/dist/server/core/runs/npm-credentials.d.ts +35 -0
  227. package/dist/server/core/runs/npm-credentials.d.ts.map +1 -0
  228. package/dist/server/core/runs/npm-credentials.js +83 -0
  229. package/dist/server/core/runs/npm-credentials.js.map +1 -0
  230. package/dist/server/core/runs/ports.d.ts +19 -2
  231. package/dist/server/core/runs/ports.d.ts.map +1 -1
  232. package/dist/server/core/runs/ports.js.map +1 -1
  233. package/dist/server/core/runs/preview.d.ts +1 -4
  234. package/dist/server/core/runs/preview.d.ts.map +1 -1
  235. package/dist/server/core/runs/preview.js +10 -5
  236. package/dist/server/core/runs/preview.js.map +1 -1
  237. package/dist/server/core/runs/quota-wait.d.ts +7 -0
  238. package/dist/server/core/runs/quota-wait.d.ts.map +1 -0
  239. package/dist/server/core/runs/quota-wait.js +56 -0
  240. package/dist/server/core/runs/quota-wait.js.map +1 -0
  241. package/dist/server/core/runs/run-data-purge.d.ts.map +1 -1
  242. package/dist/server/core/runs/run-data-purge.js +1 -0
  243. package/dist/server/core/runs/run-data-purge.js.map +1 -1
  244. package/dist/server/core/runs/run.service.d.ts +29 -5
  245. package/dist/server/core/runs/run.service.d.ts.map +1 -1
  246. package/dist/server/core/runs/run.service.js +101 -10
  247. package/dist/server/core/runs/run.service.js.map +1 -1
  248. package/dist/server/core/runs/run.store.d.ts +24 -0
  249. package/dist/server/core/runs/run.store.d.ts.map +1 -1
  250. package/dist/server/core/runs/run.store.js +44 -0
  251. package/dist/server/core/runs/run.store.js.map +1 -1
  252. package/dist/server/core/runs/sweeper.d.ts.map +1 -1
  253. package/dist/server/core/runs/sweeper.js +2 -0
  254. package/dist/server/core/runs/sweeper.js.map +1 -1
  255. package/dist/server/core/runs/transitions.d.ts +1 -0
  256. package/dist/server/core/runs/transitions.d.ts.map +1 -1
  257. package/dist/server/core/runs/transitions.js +15 -1
  258. package/dist/server/core/runs/transitions.js.map +1 -1
  259. package/dist/server/core/runs/workload.d.ts +2 -2
  260. package/dist/server/core/runs/workload.d.ts.map +1 -1
  261. package/dist/server/core/runs/workload.js +62 -9
  262. package/dist/server/core/runs/workload.js.map +1 -1
  263. package/dist/server/kernel/clock.d.ts +2 -0
  264. package/dist/server/kernel/clock.d.ts.map +1 -1
  265. package/dist/server/kernel/clock.js +14 -0
  266. package/dist/server/kernel/clock.js.map +1 -1
  267. package/dist/server/kernel/events.d.ts +6 -1
  268. package/dist/server/kernel/events.d.ts.map +1 -1
  269. package/dist/server/kernel/events.js.map +1 -1
  270. package/dist/server/locales/de-DE.js +3 -3
  271. package/dist/server/locales/de-DE.js.map +1 -1
  272. package/dist/server/locales/es-ES.js +6 -6
  273. package/dist/server/locales/es-ES.js.map +1 -1
  274. package/dist/server/locales/fr-FR.js +4 -4
  275. package/dist/server/locales/fr-FR.js.map +1 -1
  276. package/dist/server/locales/id-ID.js +5 -5
  277. package/dist/server/locales/id-ID.js.map +1 -1
  278. package/dist/server/locales/index.d.ts.map +1 -1
  279. package/dist/server/locales/index.js +2 -0
  280. package/dist/server/locales/index.js.map +1 -1
  281. package/dist/server/locales/ja-JP.d.ts.map +1 -1
  282. package/dist/server/locales/ja-JP.js +6 -6
  283. package/dist/server/locales/ja-JP.js.map +1 -1
  284. package/dist/server/locales/ko-KR.d.ts +5 -0
  285. package/dist/server/locales/ko-KR.d.ts.map +1 -0
  286. package/dist/server/locales/ko-KR.js +59 -0
  287. package/dist/server/locales/ko-KR.js.map +1 -0
  288. package/dist/server/locales/pt-BR.js +6 -6
  289. package/dist/server/locales/pt-BR.js.map +1 -1
  290. package/dist/server/locales/ru-RU.d.ts +5 -0
  291. package/dist/server/locales/ru-RU.d.ts.map +1 -0
  292. package/dist/server/locales/ru-RU.js +59 -0
  293. package/dist/server/locales/ru-RU.js.map +1 -0
  294. package/dist/server/locales/vi-VN.js +6 -6
  295. package/dist/server/locales/vi-VN.js.map +1 -1
  296. package/dist/server/locales/zh-TW.d.ts.map +1 -1
  297. package/dist/server/locales/zh-TW.js +6 -6
  298. package/dist/server/locales/zh-TW.js.map +1 -1
  299. package/dist/server/online/agent/persistence/store.d.ts.map +1 -1
  300. package/dist/server/online/agent/persistence/store.js +3 -0
  301. package/dist/server/online/agent/persistence/store.js.map +1 -1
  302. package/dist/server/online/agent/runner/backend.d.ts.map +1 -1
  303. package/dist/server/online/agent/runner/backend.js +17 -8
  304. package/dist/server/online/agent/runner/backend.js.map +1 -1
  305. package/dist/server/online/agent/runner/projection.d.ts +1 -1
  306. package/dist/server/online/agent/runner/projection.d.ts.map +1 -1
  307. package/dist/server/online/agent/runner/projection.js +8 -1
  308. package/dist/server/online/agent/runner/projection.js.map +1 -1
  309. package/dist/server/providers/agents.d.ts.map +1 -1
  310. package/dist/server/providers/agents.js +3 -0
  311. package/dist/server/providers/agents.js.map +1 -1
  312. package/dist/server/routes/admin.d.ts +1 -0
  313. package/dist/server/routes/admin.d.ts.map +1 -1
  314. package/dist/server/routes/admin.js +32 -2
  315. package/dist/server/routes/admin.js.map +1 -1
  316. package/dist/server/routes/chat.d.ts.map +1 -1
  317. package/dist/server/routes/chat.js +99 -16
  318. package/dist/server/routes/chat.js.map +1 -1
  319. package/dist/server/routes/index.d.ts.map +1 -1
  320. package/dist/server/routes/index.js +8 -2
  321. package/dist/server/routes/index.js.map +1 -1
  322. package/dist/server/routes/run.js +1 -1
  323. package/dist/server/routes/run.js.map +1 -1
  324. package/dist/server/routes/runners/admin.d.ts.map +1 -1
  325. package/dist/server/routes/runners/admin.js +3 -3
  326. package/dist/server/routes/runners/admin.js.map +1 -1
  327. package/dist/server/routes/runners/install.d.ts.map +1 -1
  328. package/dist/server/routes/runners/install.js +5 -0
  329. package/dist/server/routes/runners/install.js.map +1 -1
  330. package/dist/server/routes/runners/runner.d.ts.map +1 -1
  331. package/dist/server/routes/runners/runner.js +13 -22
  332. package/dist/server/routes/runners/runner.js.map +1 -1
  333. package/dist/server/routes/runners/runs.d.ts +1 -1
  334. package/dist/server/routes/runners/runs.d.ts.map +1 -1
  335. package/dist/server/routes/runners/runs.js +20 -1
  336. package/dist/server/routes/runners/runs.js.map +1 -1
  337. package/dist/server/routes/schemas.d.ts +5 -1
  338. package/dist/server/routes/schemas.d.ts.map +1 -1
  339. package/dist/server/routes/schemas.js +33 -8
  340. package/dist/server/routes/schemas.js.map +1 -1
  341. package/dist/server/runners/runner.service.d.ts.map +1 -1
  342. package/dist/server/runners/runner.service.js +17 -5
  343. package/dist/server/runners/runner.service.js.map +1 -1
  344. package/dist/server/tokens.d.ts +1 -1
  345. package/dist/server/tokens.d.ts.map +1 -1
  346. package/dist/server/tokens.js.map +1 -1
  347. package/dist/shared/briefs.d.ts +2 -6
  348. package/dist/shared/briefs.d.ts.map +1 -1
  349. package/dist/shared/conversations.d.ts +38 -1
  350. package/dist/shared/conversations.d.ts.map +1 -1
  351. package/dist/shared/conversations.js +10 -0
  352. package/dist/shared/conversations.js.map +1 -1
  353. package/dist/shared/runs.d.ts +11 -3
  354. package/dist/shared/runs.d.ts.map +1 -1
  355. package/dist/shared/runs.js +2 -0
  356. package/dist/shared/runs.js.map +1 -1
  357. package/dist/shared/ui-message.d.ts +2 -2
  358. package/dist/shared/ui-message.d.ts.map +1 -1
  359. package/dist/shared/ui-message.js +1 -1
  360. package/dist/shared/ui-message.js.map +1 -1
  361. package/docs/conversation-sharing.md +55 -0
  362. package/package.json +16 -16
  363. package/skills/nocobase-app-plugin-agents/references/extension-points.md +2 -0
  364. package/skills/nocobase-app-plugin-agents/references/runners.md +1 -1
  365. package/skills/nocobase-app-plugin-agents/references/verification-and-troubleshooting.md +2 -1
@@ -1,14 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ApiClientError } from '@nocobase/app-client';
3
- import { CancelledError } from '@tanstack/react-query';
3
+ import { CancelledError, hashKey } from '@tanstack/react-query';
4
4
  import { isToolUIPart } from 'ai';
5
5
  import { Chat, useChat as useSDKChat } from '@ai-sdk/react';
6
6
  import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore, } from 'react';
7
- import { useApiClient } from '@nocobase/app-client';
7
+ import { useApiClient, useRealtimeReadiness } from '@nocobase/app-client';
8
+ import { CONVERSATIONS_TOPIC, } from '../../shared/conversations.js';
8
9
  import { ChatApi } from './api.js';
10
+ import { CHAT_INVALIDATION_MS, coalescedInvalidation, } from './coalesced-invalidation.js';
9
11
  import { chatIsRunning } from './feedback.js';
10
12
  import { ChatSubmissions } from './submissions.js';
11
13
  import { agentsQueryClient } from '../query.js';
14
+ import { agentsKeys } from '../api/keys.js';
12
15
  import { chatKeys } from './keys.js';
13
16
  import { EMPTY_MESSAGES, messagesReducer, mergeUIMessages, } from './message-model.js';
14
17
  function deferred() {
@@ -22,14 +25,82 @@ function deferred() {
22
25
  /** Application coordination around one SDK Chat per conversation, shared by every surface. */
23
26
  export class ChatController {
24
27
  api;
28
+ invalidations;
29
+ invalidateQuery(key, exact = false) {
30
+ (this.invalidations ??= coalescedInvalidation(agentsQueryClient())).add(key, exact);
31
+ }
32
+ invalidateLists() {
33
+ for (const query of agentsQueryClient()
34
+ .getQueryCache()
35
+ .findAll({ queryKey: chatKeys.lists })) {
36
+ if (query.queryKey[3]?.archived !==
37
+ true)
38
+ this.invalidateQuery(query.queryKey, true);
39
+ }
40
+ }
41
+ /** Reuse a detail snapshot when replacing its row cannot change membership, order or a page cursor. */
42
+ syncLists(detail) {
43
+ const client = agentsQueryClient();
44
+ for (const query of client
45
+ .getQueryCache()
46
+ .findAll({ queryKey: chatKeys.lists })) {
47
+ const filter = query.queryKey[3];
48
+ if (filter?.archived === true)
49
+ continue;
50
+ const data = query.state.data;
51
+ const pageIndex = data?.pages?.findIndex((page) => page.items.some((item) => item.id === detail.id)) ?? -1;
52
+ const page = data?.pages?.[pageIndex];
53
+ const index = page?.items.findIndex((item) => item.id === detail.id) ?? -1;
54
+ const row = page?.items[index];
55
+ if (row &&
56
+ ((row.lastSeq ?? 0) > (detail.lastSeq ?? 0) ||
57
+ row.updatedAt > detail.updatedAt))
58
+ continue;
59
+ const sameOrder = row &&
60
+ (row.lastMessageAt === detail.lastMessageAt ||
61
+ (pageIndex === 0 &&
62
+ index === 0 &&
63
+ detail.lastMessageAt >= row.lastMessageAt &&
64
+ (page.items.length > 1 || !page.nextCursor)));
65
+ if (!row ||
66
+ !sameOrder ||
67
+ query.state.fetchStatus !== 'idle' ||
68
+ row.archivedAt !== detail.archivedAt ||
69
+ row.showInChatList !== detail.showInChatList ||
70
+ filter?.q ||
71
+ filter?.agentId ||
72
+ filter?.mode ||
73
+ filter?.source ||
74
+ typeof filter === 'string') {
75
+ this.invalidateQuery(query.queryKey, true);
76
+ continue;
77
+ }
78
+ client.setQueryData(query.queryKey, {
79
+ ...data,
80
+ pages: data.pages.map((item, position) => position === pageIndex
81
+ ? {
82
+ ...item,
83
+ items: item.items.map((summary) => summary.id === detail.id ? { ...detail } : summary),
84
+ }
85
+ : item),
86
+ });
87
+ }
88
+ }
25
89
  historyEpochs = new Map();
90
+ messageEpochs = new Map();
26
91
  replacements = new Map();
27
92
  resendRequests = new Map();
93
+ streamFollowers = new Map();
94
+ detailReads = new Map();
95
+ refreshes = new Map();
28
96
  reconciles = new Map();
29
97
  submissions;
30
98
  chats = new Map();
31
99
  histories = new Map();
32
100
  loads = new Map();
101
+ messageReads = new Map();
102
+ catchups = new Map();
103
+ reconnects = new Map();
33
104
  refreshAgain = new Set();
34
105
  recoveries = new Map();
35
106
  admissions = new Map();
@@ -46,6 +117,7 @@ export class ChatController {
46
117
  generation = 0;
47
118
  detailVersions = new Map();
48
119
  receiptVersions = new Map();
120
+ pendingReceipts = new Map();
49
121
  commandVersions = new Map();
50
122
  constructor(api) {
51
123
  this.api = api;
@@ -80,7 +152,7 @@ export class ChatController {
80
152
  recovery.error = undefined;
81
153
  // Reads started before reconnection must not replace its replay or revive old errors.
82
154
  this.nextDetailVersion(id);
83
- this.invalidateHistoryReads(id);
155
+ this.invalidateReplayReads(id);
84
156
  this.publish();
85
157
  };
86
158
  }),
@@ -194,22 +266,33 @@ export class ChatController {
194
266
  this.publish();
195
267
  }
196
268
  historyLifetime(id) {
197
- const active = this.lifetime(), epoch = this.historyEpochs.get(id) ?? 0, command = this.commandVersions.get(id);
269
+ return this.readLifetime(id, this.historyEpochs);
270
+ }
271
+ messageLifetime(id) {
272
+ return this.readLifetime(id, this.messageEpochs);
273
+ }
274
+ readLifetime(id, epochs) {
275
+ const active = this.lifetime(), epoch = epochs.get(id) ?? 0, command = this.commandVersions.get(id);
198
276
  return () => active() &&
199
- (this.historyEpochs.get(id) ?? 0) === epoch &&
277
+ (epochs.get(id) ?? 0) === epoch &&
200
278
  this.commandVersions.get(id) === command;
201
279
  }
202
- invalidateHistoryReads(id, discardDeferred = false) {
280
+ /** A resumed stream replaces replay snapshots, not already requested durable message revisions. */
281
+ invalidateReplayReads(id) {
203
282
  this.historyEpochs.set(id, (this.historyEpochs.get(id) ?? 0) + 1);
204
283
  this.loads.delete(id);
205
284
  this.refreshAgain.delete(id);
285
+ this.historyMeta(id).loadingOlder = false;
286
+ }
287
+ invalidateHistoryReads(id, discardDeferred = false) {
288
+ this.invalidateReplayReads(id);
289
+ this.messageEpochs.set(id, (this.messageEpochs.get(id) ?? 0) + 1);
206
290
  this.revisionQueue.delete(id);
207
291
  if (discardDeferred)
208
292
  this.deferredReads.delete(id);
209
293
  for (const key of this.messageVersions.keys())
210
294
  if (key.startsWith(`${id}:`))
211
295
  this.messageVersions.delete(key);
212
- this.historyMeta(id).loadingOlder = false;
213
296
  }
214
297
  historyReplaced(event) {
215
298
  if (this.disposed || !this.replaceHistory(event))
@@ -403,42 +486,36 @@ export class ChatController {
403
486
  return pending;
404
487
  if (this.resendRequests.has(id))
405
488
  return;
406
- this.invalidateHistoryReads(id);
489
+ this.invalidateReplayReads(id);
407
490
  const active = this.historyLifetime(id);
408
491
  const initial = this.chat(id).messages.filter((m) => m.metadata?.seq !== undefined);
409
492
  const task = (async () => {
410
493
  const found = [];
411
- for (let offset = 0; offset < initial.length; offset += 100) {
412
- const page = await this.api.messages(id, {
413
- ids: initial
414
- .slice(offset, offset + 100)
415
- .map((m) => m.id)
416
- .join(','),
417
- });
494
+ // Reconnect reads the current window once, covering insertions, revisions and removed replies together.
495
+ const first = await this.api.messages(id, {});
496
+ if (!active())
497
+ return;
498
+ found.push(...first.items);
499
+ let window = first;
500
+ const oldest = Math.min(...initial.map((m) => m.metadata.seq));
501
+ const seen = new Set();
502
+ while (window.nextCursor &&
503
+ window.items.length &&
504
+ window.items[0].metadata.seq > oldest) {
505
+ if (seen.has(window.nextCursor))
506
+ throw new Error('History pagination did not advance.');
507
+ seen.add(window.nextCursor);
508
+ window = await this.api.messages(id, { pageToken: window.nextCursor });
418
509
  if (!active())
419
510
  return;
420
- found.push(...page.items);
511
+ found.push(...window.items);
421
512
  }
422
513
  const present = new Set(found.map((m) => m.id));
423
514
  const missing = new Set(initial.filter((m) => !present.has(m.id)).map((m) => m.id));
424
- let window;
425
- if (missing.size) {
426
- const oldest = Math.min(...initial.map((m) => m.metadata.seq));
427
- const seen = new Set();
428
- do {
429
- window = await this.api.messages(id, window?.nextCursor ? { pageToken: window.nextCursor } : {});
430
- if (!active())
431
- return;
432
- found.push(...window.items);
433
- if (!window.nextCursor ||
434
- !window.items.length ||
435
- window.items[0].metadata.seq <= oldest)
436
- break;
437
- if (seen.has(window.nextCursor))
438
- throw new Error('History pagination did not advance.');
439
- seen.add(window.nextCursor);
440
- } while (window.nextCursor);
441
- }
515
+ this.dispatch(id, { type: 'page', page: first });
516
+ this.submissions.observe(id, found);
517
+ this.historyMeta(id).loadedThrough = first.lastSeq;
518
+ this.historyMeta(id).error = undefined;
442
519
  if (!active())
443
520
  return;
444
521
  const changed = found.find((m) => m.role === 'user' &&
@@ -469,6 +546,13 @@ export class ChatController {
469
546
  ? chat.messages.at(-1)?.id
470
547
  : undefined;
471
548
  chat.messages = mergeUIMessages(chat.messages.filter((m) => !missing.has(m.id) || m.id === activeId), found, activeId);
549
+ const deferred = this.deferredReads.get(id);
550
+ for (const message of found)
551
+ if ((message.metadata?.revision ?? 0) >=
552
+ (this.messageVersions.get(`${id}:${message.id}`) ?? 0))
553
+ deferred?.delete(message.id);
554
+ if (deferred?.size === 0)
555
+ this.deferredReads.delete(id);
472
556
  if (window)
473
557
  this.historyMeta(id).page = {
474
558
  ...this.historyMeta(id).page,
@@ -562,13 +646,41 @@ export class ChatController {
562
646
  if (this.disposed)
563
647
  return [];
564
648
  const found = [];
565
- const active = this.historyLifetime(id);
649
+ const active = this.messageLifetime(id);
650
+ const replayActive = this.historyLifetime(id);
566
651
  for (let offset = 0; offset < ids.length; offset += 100) {
567
- const page = await this.api.messages(id, {
568
- ids: ids.slice(offset, offset + 100).join(','),
569
- });
652
+ const selection = ids.slice(offset, offset + 100);
653
+ const key = JSON.stringify([id, [...selection].sort()]);
654
+ let pendingRead = this.messageReads.get(key);
655
+ const shared = Boolean(pendingRead);
656
+ if (!pendingRead) {
657
+ pendingRead = this.api.messages(id, { ids: selection.join(',') });
658
+ this.messageReads.set(key, pendingRead);
659
+ const clear = () => {
660
+ if (this.messageReads.get(key) === pendingRead)
661
+ this.messageReads.delete(key);
662
+ };
663
+ void pendingRead.then(clear, clear);
664
+ }
665
+ const response = await pendingRead;
570
666
  if (!active())
571
667
  return [];
668
+ // Durable user/system revisions survive reconnection; old assistant snapshots must not overwrite replay.
669
+ const page = replayActive()
670
+ ? response
671
+ : {
672
+ ...response,
673
+ items: response.items.filter((message) => message.role !== 'assistant'),
674
+ };
675
+ if (shared &&
676
+ active() &&
677
+ page.items.some((message) => (message.metadata?.revision ?? 0) <
678
+ (this.messageVersions.get(`${id}:${message.id}`) ?? 0))) {
679
+ // A revision announced during the older read needs a causal trailing read, never a cancellation.
680
+ const saved = await this.reread(id, selection);
681
+ found.push(...saved);
682
+ continue;
683
+ }
572
684
  found.push(...page.items);
573
685
  const pending = this.deferredReads.get(id);
574
686
  for (const message of page.items) {
@@ -583,6 +695,42 @@ export class ChatController {
583
695
  }
584
696
  return found;
585
697
  }
698
+ /** A cursor announcement shares the current read; only an actual gap needs a trailing page. */
699
+ messagesChanged(id, lastSeq) {
700
+ if (this.disposed || !this.watches.has(id))
701
+ return Promise.resolve();
702
+ const pending = this.catchups.get(id);
703
+ if (pending) {
704
+ pending.lastSeq = Math.max(pending.lastSeq, lastSeq);
705
+ return pending.task;
706
+ }
707
+ const active = this.historyLifetime(id);
708
+ const owner = { lastSeq, task: Promise.resolve() };
709
+ const run = async () => {
710
+ const reading = this.loads.get(id);
711
+ if (reading)
712
+ await reading;
713
+ while (active() &&
714
+ (!this.history(id).state.loaded ||
715
+ this.history(id).loadedThrough < owner.lastSeq)) {
716
+ const before = this.history(id).loadedThrough;
717
+ await this.loadHistory(id);
718
+ if (this.history(id).error)
719
+ throw this.history(id).error;
720
+ if (this.history(id).loadedThrough <= before)
721
+ break;
722
+ }
723
+ };
724
+ owner.task = run().finally(() => {
725
+ if (this.catchups.get(id) === owner)
726
+ this.catchups.delete(id);
727
+ });
728
+ this.catchups.set(id, owner);
729
+ return owner.task;
730
+ }
731
+ isWatching(id) {
732
+ return this.watches.has(id);
733
+ }
586
734
  watch(id) {
587
735
  let watch = this.watches.get(id);
588
736
  if (!watch) {
@@ -592,11 +740,15 @@ export class ChatController {
592
740
  return;
593
741
  const detail = agentsQueryClient().getQueryData(chatKeys.conversation(id));
594
742
  const current = this.watches.get(id);
595
- const interval = detail?.execution?.status === 'running' ? 5000 : 30000;
743
+ const status = this.chats.get(id)?.status;
744
+ const busy = chatIsRunning(detail ?? null) ||
745
+ status === 'submitted' ||
746
+ status === 'streaming';
747
+ const interval = busy ? 5000 : 30000;
596
748
  if (Date.now() - current.last < interval)
597
749
  return;
598
750
  current.last = Date.now();
599
- void this.refresh(id).catch(() => { });
751
+ void this.refresh(id, true, !busy, false).catch(() => { });
600
752
  }, 5000);
601
753
  watch = { refs: 0, timer, last: Date.now() };
602
754
  this.watches.set(id, watch);
@@ -611,12 +763,24 @@ export class ChatController {
611
763
  };
612
764
  }
613
765
  reconnect() {
614
- for (const id of this.watches.keys())
615
- void this.refresh(id)
766
+ this.invalidateLists();
767
+ for (const id of this.watches.keys()) {
768
+ if (this.reconnects.has(id))
769
+ continue;
770
+ const task = this.refresh(id)
616
771
  .then(() => this.resume(id))
617
- .catch(() => { });
772
+ .catch(() => { })
773
+ .finally(() => {
774
+ if (this.reconnects.get(id) === task)
775
+ this.reconnects.delete(id);
776
+ });
777
+ this.reconnects.set(id, task);
778
+ }
618
779
  }
619
780
  messageUpdated(id, messageId, revision) {
781
+ const saved = this.chat(id).messages.find((message) => message.id === messageId);
782
+ if ((saved?.metadata?.revision ?? -1) >= revision)
783
+ return;
620
784
  const key = `${id}:${messageId}`;
621
785
  if (((this.messageVersions.get(key) ?? -1) >= revision &&
622
786
  !this.deferredReads.get(id)?.has(messageId)) ||
@@ -630,13 +794,21 @@ export class ChatController {
630
794
  }
631
795
  const queue = { pending: new Set([messageId]), task: Promise.resolve() };
632
796
  this.revisionQueue.set(id, queue);
633
- const active = this.historyLifetime(id);
797
+ const active = this.messageLifetime(id);
634
798
  queue.task = (async () => {
635
799
  const attempted = new Set();
636
800
  try {
801
+ // Let a concurrent cursor read or SDK finish supply the same durable revision first.
802
+ await new Promise((resolve) => setTimeout(resolve, CHAT_INVALIDATION_MS));
803
+ const reading = this.loads.get(id);
804
+ if (reading)
805
+ await reading;
637
806
  while (active() && queue.pending.size) {
638
- const ids = [...queue.pending];
807
+ const ids = [...queue.pending].filter((messageId) => (this.chat(id).messages.find((message) => message.id === messageId)?.metadata?.revision ?? -1) <
808
+ (this.messageVersions.get(`${id}:${messageId}`) ?? 0));
639
809
  queue.pending.clear();
810
+ if (!ids.length)
811
+ continue;
640
812
  ids.forEach((id) => attempted.add(id));
641
813
  await this.reread(id, ids);
642
814
  }
@@ -774,6 +946,19 @@ export class ChatController {
774
946
  ?.id ??
775
947
  '');
776
948
  }
949
+ followStream(id) {
950
+ const count = (this.streamFollowers.get(id) ?? 0) + 1;
951
+ this.streamFollowers.set(id, count);
952
+ if (count === 1)
953
+ this.receive(id);
954
+ return () => {
955
+ const remaining = (this.streamFollowers.get(id) ?? 1) - 1;
956
+ if (remaining === 0)
957
+ this.streamFollowers.delete(id);
958
+ else
959
+ this.streamFollowers.set(id, remaining);
960
+ };
961
+ }
777
962
  receive(id, known) {
778
963
  if (this.disposed)
779
964
  return;
@@ -793,7 +978,10 @@ export class ChatController {
793
978
  return;
794
979
  if (!chatIsRunning(detail) || detail.execution?.waitingApproval) {
795
980
  const chat = this.chats.get(id);
796
- if (chat?.status !== 'submitted' && chat?.status !== 'streaming')
981
+ if (chat?.status !== 'submitted' &&
982
+ chat?.status !== 'streaming' &&
983
+ (!this.history(id).state.loaded ||
984
+ this.history(id).loadedThrough < (detail.lastSeq ?? 0)))
797
985
  await this.loadHistory(id);
798
986
  return;
799
987
  }
@@ -870,7 +1058,13 @@ export class ChatController {
870
1058
  recovery.phase = 'interrupted';
871
1059
  recovery.error = chat.error;
872
1060
  }
873
- await this.refresh(id);
1061
+ if (agentsQueryClient()
1062
+ .getQueryCache()
1063
+ .find({ queryKey: chatKeys.conversation(id), exact: true })
1064
+ ?.isActive())
1065
+ this.scheduleRefresh(id);
1066
+ else
1067
+ await this.refresh(id);
874
1068
  }
875
1069
  }
876
1070
  catch (error) {
@@ -947,9 +1141,63 @@ export class ChatController {
947
1141
  if (active())
948
1142
  await this.refresh(id);
949
1143
  }
950
- async refresh(id) {
1144
+ /** One detail request shared by the query observer and message reconciliation. */
1145
+ readDetail(id, fresh = false) {
1146
+ const pending = this.detailReads.get(id);
1147
+ if (pending)
1148
+ return fresh
1149
+ ? pending.catch(() => undefined).then(() => this.readDetail(id))
1150
+ : pending;
1151
+ const task = this.api.conversation(id);
1152
+ this.detailReads.set(id, task);
1153
+ const clear = () => {
1154
+ if (this.detailReads.get(id) === task)
1155
+ this.detailReads.delete(id);
1156
+ };
1157
+ void task.then(clear, clear);
1158
+ return task;
1159
+ }
1160
+ refresh(id, fresh = true, idleProbe = false, reconcileHistory = true) {
1161
+ if (this.disposed)
1162
+ return Promise.resolve();
1163
+ const pending = this.refreshes.get(id);
1164
+ if (pending) {
1165
+ if (fresh) {
1166
+ pending.trailing = true;
1167
+ this.nextDetailVersion(id);
1168
+ }
1169
+ return pending.task;
1170
+ }
1171
+ const lifetime = this.lifetime(), commandVersion = this.commandVersions.get(id), active = () => lifetime() && this.commandVersions.get(id) === commandVersion;
1172
+ const owner = { trailing: false, task: Promise.resolve() };
1173
+ const run = async () => {
1174
+ do {
1175
+ owner.trailing = false;
1176
+ await this.refreshOnce(id, fresh, idleProbe, undefined, reconcileHistory);
1177
+ fresh = true;
1178
+ idleProbe = false;
1179
+ } while (owner.trailing && active());
1180
+ };
1181
+ owner.task = run().finally(() => {
1182
+ if (this.refreshes.get(id) === owner)
1183
+ this.refreshes.delete(id);
1184
+ });
1185
+ this.refreshes.set(id, owner);
1186
+ return owner.task;
1187
+ }
1188
+ async refreshOnce(id, fresh, idleProbe = false, snapshot, reconcileHistory = true) {
951
1189
  if (this.disposed)
952
1190
  return;
1191
+ const waiting = this.lifetime();
1192
+ if ((!fresh || !reconcileHistory) && !snapshot) {
1193
+ const query = agentsQueryClient()
1194
+ .getQueryCache()
1195
+ .find({ queryKey: chatKeys.conversation(id), exact: true });
1196
+ if (query?.state.fetchStatus === 'fetching' && query.promise)
1197
+ snapshot = (await query.promise);
1198
+ if (!waiting())
1199
+ return;
1200
+ }
953
1201
  const lifetime = this.lifetime(), commandVersion = this.commandVersions.get(id), active = () => lifetime() && this.commandVersions.get(id) === commandVersion, ordered = this.detailRequest(id);
954
1202
  const submittedOwner = this.chat(id);
955
1203
  const submittedInput = submittedOwner.status === 'submitted' && !this.admissions.has(id)
@@ -957,16 +1205,62 @@ export class ChatController {
957
1205
  .reverse()
958
1206
  .find((message) => message.role === 'user' && message.metadata?.seq !== undefined)
959
1207
  : undefined;
960
- const detail = await this.api.conversation(id);
1208
+ const query = agentsQueryClient()
1209
+ .getQueryCache()
1210
+ .find({ queryKey: chatKeys.conversation(id), exact: true });
1211
+ const cached = !fresh && query?.isActive() && query.state.fetchStatus === 'idle'
1212
+ ? query.state.data
1213
+ : undefined;
1214
+ const previous = query?.state.data;
1215
+ const detail = snapshot ?? cached ?? (await this.readDetail(id, fresh));
1216
+ if (!active())
1217
+ return;
1218
+ if (idleProbe &&
1219
+ previous &&
1220
+ !chatIsRunning(detail) &&
1221
+ detail.lastSeq !== undefined &&
1222
+ this.history(id).state.loaded &&
1223
+ !this.history(id).error &&
1224
+ this.history(id).loadedThrough >= detail.lastSeq &&
1225
+ hashKey([previous]) === hashKey([detail])) {
1226
+ // The durable cursor is current. Still reread revisions of the latest assistant response: a same-sequence
1227
+ // update may have lost its announcement, even though the conversation snapshot did not change.
1228
+ const latest = [...this.chat(id).messages]
1229
+ .reverse()
1230
+ .find((message) => message.role === 'assistant');
1231
+ const ids = [
1232
+ ...new Set([
1233
+ ...(this.deferredReads.get(id) ?? []),
1234
+ ...(latest ? [latest.id] : []),
1235
+ ]),
1236
+ ];
1237
+ if (ids.length)
1238
+ await this.reread(id, ids);
1239
+ if (active() && ordered()) {
1240
+ this.remember(detail, false);
1241
+ this.publish();
1242
+ }
1243
+ return;
1244
+ }
961
1245
  if (!active())
962
1246
  return;
963
- if (ordered())
1247
+ const reconciled = fresh && !snapshot && reconcileHistory && ordered();
1248
+ if (reconciled)
964
1249
  await this.reconcile(id, detail);
965
1250
  if (!active())
966
1251
  return;
967
1252
  // A newer detail query must not discard this refresh's message catch-up.
968
1253
  // loadHistory coalesces overlapping reads independently of detail ordering.
969
- await this.loadHistory(id, false, true);
1254
+ const beforeThrough = this.history(id).loadedThrough;
1255
+ if ((!snapshot && !reconciled && fresh && detail.lastSeq === undefined) ||
1256
+ (reconciled && !ordered()) ||
1257
+ !this.history(id).state.loaded ||
1258
+ this.history(id).loadedThrough < (detail.lastSeq ?? 0)) {
1259
+ if (!reconcileHistory && detail.lastSeq !== undefined)
1260
+ await this.messagesChanged(id, detail.lastSeq);
1261
+ else
1262
+ await this.loadHistory(id, false, fresh);
1263
+ }
970
1264
  if (!active())
971
1265
  return;
972
1266
  const history = this.history(id);
@@ -981,14 +1275,23 @@ export class ChatController {
981
1275
  const revisable = [
982
1276
  ...new Set([
983
1277
  ...(this.deferredReads.get(id) ?? []),
984
- ...(detail.execution?.messageId
1278
+ ...(detail.execution?.messageId &&
1279
+ ((!snapshot &&
1280
+ !reconciled &&
1281
+ this.history(id).loadedThrough === beforeThrough) ||
1282
+ (this.chat(id).messages.find((m) => m.id === detail.execution?.messageId)?.metadata?.revision ?? -1) < (detail.execution.revision ?? 0))
985
1283
  ? [detail.execution.messageId]
986
- : latest
1284
+ : !snapshot &&
1285
+ !reconciled &&
1286
+ this.history(id).loadedThrough === beforeThrough &&
1287
+ latest
987
1288
  ? [latest.id]
988
1289
  : []),
989
1290
  ]),
990
1291
  ];
991
- const saved = revisable.length ? await this.reread(id, revisable) : [];
1292
+ const saved = revisable.length
1293
+ ? await this.reread(id, revisable)
1294
+ : [...this.chat(id).messages];
992
1295
  if (!active() || !ordered())
993
1296
  return;
994
1297
  const owner = this.chat(id), receiving = owner.messages.at(-1);
@@ -1023,7 +1326,8 @@ export class ChatController {
1023
1326
  }
1024
1327
  if (!active() || !ordered())
1025
1328
  return;
1026
- this.remember(detail, false);
1329
+ if (!snapshot)
1330
+ this.remember(detail, false);
1027
1331
  const chat = this.chat(id);
1028
1332
  if (detail.execution?.status === 'idle') {
1029
1333
  this.recoveries.delete(id);
@@ -1033,6 +1337,13 @@ export class ChatController {
1033
1337
  this.publish();
1034
1338
  }
1035
1339
  scheduleRefresh(id) {
1340
+ if (agentsQueryClient()
1341
+ .getQueryCache()
1342
+ .find({ queryKey: chatKeys.conversation(id), exact: true })
1343
+ ?.isActive()) {
1344
+ this.invalidateQuery(chatKeys.conversation(id), true);
1345
+ return;
1346
+ }
1036
1347
  if (this.completionReads.has(id))
1037
1348
  return;
1038
1349
  this.completionReads.add(id);
@@ -1042,14 +1353,19 @@ export class ChatController {
1042
1353
  void this.refresh(id).catch(() => { });
1043
1354
  });
1044
1355
  }
1045
- observeDetail(detail, refresh = true) {
1356
+ observeDetail(detail, refresh = true, synchronizeLists = true) {
1357
+ if (detail.snapshot)
1358
+ return;
1359
+ if (synchronizeLists)
1360
+ this.syncLists(detail);
1046
1361
  if (detail.execution?.status === 'running')
1047
1362
  this.receive(detail.id, detail);
1048
1363
  if (!chatIsRunning(detail)) {
1364
+ // A stream that failed (a phone suspending the page, a dropped network) leaves its error on the SDK Chat. Once
1365
+ // the conversation is idle, catching history up to this detail clears it, as an explicit refresh would.
1049
1366
  if (refresh &&
1050
- (this.chats.get(detail.id)?.status === 'streaming' ||
1051
- this.chats.get(detail.id)?.status === 'submitted'))
1052
- this.scheduleRefresh(detail.id);
1367
+ ['streaming', 'submitted', 'error'].includes(this.chats.get(detail.id)?.status ?? ''))
1368
+ void this.refreshOnce(detail.id, false, false, detail).catch(() => { });
1053
1369
  this.stopRequests.delete(detail.id);
1054
1370
  this.recoveries.delete(detail.id);
1055
1371
  this.publish();
@@ -1088,29 +1404,103 @@ export class ChatController {
1088
1404
  }
1089
1405
  remember(detail, refresh = true) {
1090
1406
  this.nextDetailVersion(detail.id);
1091
- this.observeDetail(detail, refresh);
1092
- agentsQueryClient().setQueryData(chatKeys.conversation(detail.id), detail);
1093
- void agentsQueryClient().invalidateQueries({ queryKey: chatKeys.lists });
1407
+ // A reconnect already owns a fresh list read; its later history response must not schedule another one.
1408
+ this.observeDetail(detail, refresh, !this.reconnects.has(detail.id));
1409
+ const client = agentsQueryClient();
1410
+ client.setQueryData(chatKeys.conversation(detail.id), detail);
1411
+ }
1412
+ /** The HTTP receipt supplies this tab's snapshot; retain a newer read position for a trailing read. */
1413
+ observeReceipt(id, seenSeq) {
1414
+ const update = (item) => {
1415
+ if (item.id !== id || (item.seenSeq ?? -1) >= seenSeq)
1416
+ return item;
1417
+ return {
1418
+ ...item,
1419
+ seenSeq,
1420
+ read: seenSeq >= (item.lastSeq ?? 0) || item.read,
1421
+ needsYou: item.needsYou &&
1422
+ item.needsYou.reason !== 'confirm' &&
1423
+ item.needsYou.seq <= seenSeq
1424
+ ? null
1425
+ : item.needsYou,
1426
+ };
1427
+ };
1428
+ agentsQueryClient().setQueriesData({ queryKey: chatKeys.lists }, (old) => !old
1429
+ ? old
1430
+ : Array.isArray(old)
1431
+ ? old.map(update)
1432
+ : {
1433
+ ...old,
1434
+ pages: old.pages.map((page) => ({
1435
+ ...page,
1436
+ items: page.items.map(update),
1437
+ })),
1438
+ });
1439
+ }
1440
+ deferReadReceipt(id, seenSeq, refresh) {
1441
+ const pending = this.pendingReceipts.get(id);
1442
+ if (!pending)
1443
+ return false;
1444
+ pending.seenSeq = Math.max(pending.seenSeq, seenSeq);
1445
+ pending.refresh = refresh;
1446
+ return true;
1094
1447
  }
1095
1448
  async markRead(id) {
1449
+ const client = agentsQueryClient();
1450
+ const key = chatKeys.conversation(id);
1451
+ const before = client.getQueryData(key);
1096
1452
  const active = this.lifetime(), commandVersion = this.commandVersions.get(id), receiptVersion = (this.receiptVersions.get(id) ?? 0) + 1;
1097
1453
  // Detail reads must not suppress the read transition needed by a viewed conversation.
1098
1454
  this.receiptVersions.set(id, receiptVersion);
1455
+ const pending = { seenSeq: -1 };
1456
+ this.pendingReceipts.set(id, pending);
1099
1457
  this.nextDetailVersion(id);
1100
- const detail = await this.api.markRead(id);
1101
- if (active() &&
1102
- this.commandVersions.get(id) === commandVersion &&
1103
- this.receiptVersions.get(id) === receiptVersion) {
1104
- // Read receipts do not change execution or invalidate pending history.
1105
- this.nextDetailVersion(id);
1106
- agentsQueryClient().setQueryData(chatKeys.conversation(id), detail);
1107
- void agentsQueryClient().invalidateQueries({ queryKey: chatKeys.lists });
1458
+ try {
1459
+ const detail = await this.api.markRead(id);
1460
+ if (active() &&
1461
+ this.commandVersions.get(id) === commandVersion &&
1462
+ this.receiptVersions.get(id) === receiptVersion) {
1463
+ this.observeReceipt(id, detail.seenSeq ?? 0);
1464
+ // Read receipts do not change execution or invalidate pending history.
1465
+ this.nextDetailVersion(id);
1466
+ client.setQueryData(key, (current) => {
1467
+ if (!current ||
1468
+ current === before ||
1469
+ (current.lastSeq ?? 0) < (detail.lastSeq ?? 0) ||
1470
+ (current.lastSeq === detail.lastSeq &&
1471
+ current.updatedAt < detail.updatedAt))
1472
+ return detail;
1473
+ // A later snapshot may already contain the next reply or a metadata change.
1474
+ const seenSeq = Math.max(current.seenSeq ?? 0, detail.seenSeq ?? 0);
1475
+ return {
1476
+ ...current,
1477
+ seenSeq,
1478
+ read: seenSeq >= (current.lastSeq ?? 0) || current.read,
1479
+ needsYou: current.needsYou &&
1480
+ current.needsYou.reason !== 'confirm' &&
1481
+ current.needsYou.seq <= seenSeq
1482
+ ? null
1483
+ : current.needsYou,
1484
+ };
1485
+ });
1486
+ }
1487
+ return detail;
1488
+ }
1489
+ finally {
1490
+ if (this.pendingReceipts.get(id) === pending) {
1491
+ this.pendingReceipts.delete(id);
1492
+ const current = agentsQueryClient().getQueryData(chatKeys.conversation(id));
1493
+ if (active() && pending.seenSeq > (current?.seenSeq ?? -1))
1494
+ pending.refresh?.();
1495
+ }
1108
1496
  }
1109
- return detail;
1110
1497
  }
1111
1498
  async command(id, request, detail) {
1112
1499
  const active = this.lifetime(), version = (this.commandVersions.get(id) ?? 0) + 1;
1113
1500
  this.commandVersions.set(id, version);
1501
+ // A command replaces the snapshot generation; its reads must not wait on obsolete HTTP.
1502
+ this.detailReads.delete(id);
1503
+ this.refreshes.delete(id);
1114
1504
  this.nextDetailVersion(id);
1115
1505
  this.invalidateHistoryReads(id);
1116
1506
  const recovery = this.recoveries.get(id);
@@ -1143,6 +1533,8 @@ export class ChatController {
1143
1533
  this.disposed = false;
1144
1534
  }
1145
1535
  invalidate() {
1536
+ this.invalidations?.stop();
1537
+ this.invalidations = undefined;
1146
1538
  if (this.disposed)
1147
1539
  return;
1148
1540
  this.disposed = true;
@@ -1155,8 +1547,12 @@ export class ChatController {
1155
1547
  request.gate.reject(new CancelledError({ silent: true }));
1156
1548
  this.resendRequests.clear();
1157
1549
  this.historyEpochs.clear();
1550
+ this.messageEpochs.clear();
1158
1551
  this.replacements.clear();
1159
1552
  this.reconciles.clear();
1553
+ this.streamFollowers.clear();
1554
+ this.detailReads.clear();
1555
+ this.refreshes.clear();
1160
1556
  for (const watch of this.watches.values())
1161
1557
  clearInterval(watch.timer);
1162
1558
  this.watches.clear();
@@ -1166,14 +1562,19 @@ export class ChatController {
1166
1562
  this.completionReads.clear();
1167
1563
  this.admissions.clear();
1168
1564
  this.loads.clear();
1565
+ this.messageReads.clear();
1566
+ this.catchups.clear();
1567
+ this.reconnects.clear();
1169
1568
  this.refreshAgain.clear();
1170
1569
  this.commandVersions.clear();
1171
1570
  this.receiptVersions.clear();
1571
+ this.pendingReceipts.clear();
1172
1572
  this.stopRequests.clear();
1173
1573
  this.approvalRequests.clear();
1174
1574
  this.recoveries.clear();
1175
1575
  this.submissions.invalidate();
1176
- agentsQueryClient().removeQueries({ queryKey: chatKeys.all });
1576
+ // All plugin snapshots are viewer-scoped, including fresh agent options.
1577
+ agentsQueryClient().removeQueries({ queryKey: agentsKeys.all });
1177
1578
  }
1178
1579
  dispose() {
1179
1580
  this.invalidate();
@@ -1192,10 +1593,7 @@ export function ChatClientProvider({ children, }) {
1192
1593
  controller.activate();
1193
1594
  const lifetimes = lifetimesRef.current;
1194
1595
  lifetimes.set(controller, (lifetimes.get(controller) ?? 0) + 1);
1195
- const reconnect = () => controller.reconnect();
1196
- window.addEventListener('online', reconnect);
1197
1596
  return () => {
1198
- window.removeEventListener('online', reconnect);
1199
1597
  controller.invalidate();
1200
1598
  lifetimes.set(controller, (lifetimes.get(controller) ?? 1) - 1);
1201
1599
  queueMicrotask(() => {
@@ -1216,12 +1614,13 @@ export function useChatController() {
1216
1614
  }
1217
1615
  export function useChatStream(id) {
1218
1616
  const controller = useChatController();
1617
+ const ready = useRealtimeReadiness([CONVERSATIONS_TOPIC]);
1219
1618
  useSDKChat({ chat: controller.chat(id ?? 'draft') });
1220
1619
  useSyncExternalStore(controller.subscribe, controller.snapshot, controller.snapshot);
1221
1620
  useEffect(() => {
1222
- if (id)
1223
- controller.receive(id);
1224
- }, [controller, id]);
1621
+ if (ready && id)
1622
+ return controller.followStream(id);
1623
+ }, [controller, id, ready]);
1225
1624
  return id ? controller.state(id) : undefined;
1226
1625
  }
1227
1626
  export function useChatStreamState(id) {