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

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 (278) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +2 -19
  3. package/dist/client/api/client.d.ts +2 -2
  4. package/dist/client/api/client.d.ts.map +1 -1
  5. package/dist/client/api/client.js +2 -2
  6. package/dist/client/api/client.js.map +1 -1
  7. package/dist/client/api/runners.d.ts +2 -2
  8. package/dist/client/api/runners.d.ts.map +1 -1
  9. package/dist/client/api/runners.js +5 -2
  10. package/dist/client/api/runners.js.map +1 -1
  11. package/dist/client/chat/api.d.ts +1 -1
  12. package/dist/client/chat/api.d.ts.map +1 -1
  13. package/dist/client/chat/api.js +6 -2
  14. package/dist/client/chat/api.js.map +1 -1
  15. package/dist/client/chat/controller.d.ts +7 -5
  16. package/dist/client/chat/controller.d.ts.map +1 -1
  17. package/dist/client/chat/controller.js +193 -69
  18. package/dist/client/chat/controller.js.map +1 -1
  19. package/dist/client/chat/submissions.d.ts +2 -0
  20. package/dist/client/chat/submissions.d.ts.map +1 -1
  21. package/dist/client/chat/submissions.js +1 -0
  22. package/dist/client/chat/submissions.js.map +1 -1
  23. package/dist/client/chat/use-chat.d.ts.map +1 -1
  24. package/dist/client/chat/use-chat.js +2 -4
  25. package/dist/client/chat/use-chat.js.map +1 -1
  26. package/dist/client/components/ag-tag-tones.d.ts +7 -0
  27. package/dist/client/components/ag-tag-tones.d.ts.map +1 -0
  28. package/dist/client/components/ag-tag-tones.js +14 -0
  29. package/dist/client/components/ag-tag-tones.js.map +1 -0
  30. package/dist/client/components/ag-tag.d.ts +2 -1
  31. package/dist/client/components/ag-tag.d.ts.map +1 -1
  32. package/dist/client/components/ag-tag.js +1 -13
  33. package/dist/client/components/ag-tag.js.map +1 -1
  34. package/dist/client/components/quota-status.d.ts +2 -1
  35. package/dist/client/components/quota-status.d.ts.map +1 -1
  36. package/dist/client/components/quota-status.js +2 -2
  37. package/dist/client/components/quota-status.js.map +1 -1
  38. package/dist/client/components/ui/pagination.d.ts +6 -0
  39. package/dist/client/components/ui/pagination.d.ts.map +1 -0
  40. package/dist/client/components/ui/pagination.js +13 -0
  41. package/dist/client/components/ui/pagination.js.map +1 -0
  42. package/dist/client/hooks/use-notify.d.ts.map +1 -1
  43. package/dist/client/hooks/use-notify.js +4 -0
  44. package/dist/client/hooks/use-notify.js.map +1 -1
  45. package/dist/client/locales/de-DE.d.ts.map +1 -1
  46. package/dist/client/locales/de-DE.js +26 -10
  47. package/dist/client/locales/de-DE.js.map +1 -1
  48. package/dist/client/locales/en-US.d.ts +11 -0
  49. package/dist/client/locales/en-US.d.ts.map +1 -1
  50. package/dist/client/locales/en-US.js +21 -10
  51. package/dist/client/locales/en-US.js.map +1 -1
  52. package/dist/client/locales/es-ES.d.ts.map +1 -1
  53. package/dist/client/locales/es-ES.js +26 -10
  54. package/dist/client/locales/es-ES.js.map +1 -1
  55. package/dist/client/locales/fr-FR.d.ts.map +1 -1
  56. package/dist/client/locales/fr-FR.js +26 -10
  57. package/dist/client/locales/fr-FR.js.map +1 -1
  58. package/dist/client/locales/id-ID.d.ts.map +1 -1
  59. package/dist/client/locales/id-ID.js +26 -10
  60. package/dist/client/locales/id-ID.js.map +1 -1
  61. package/dist/client/locales/ja-JP.d.ts.map +1 -1
  62. package/dist/client/locales/ja-JP.js +26 -10
  63. package/dist/client/locales/ja-JP.js.map +1 -1
  64. package/dist/client/locales/pt-BR.d.ts.map +1 -1
  65. package/dist/client/locales/pt-BR.js +26 -10
  66. package/dist/client/locales/pt-BR.js.map +1 -1
  67. package/dist/client/locales/runtimes.en-US.d.ts +5 -0
  68. package/dist/client/locales/runtimes.en-US.d.ts.map +1 -1
  69. package/dist/client/locales/runtimes.en-US.js +5 -0
  70. package/dist/client/locales/runtimes.en-US.js.map +1 -1
  71. package/dist/client/locales/runtimes.zh-CN.d.ts.map +1 -1
  72. package/dist/client/locales/runtimes.zh-CN.js +5 -0
  73. package/dist/client/locales/runtimes.zh-CN.js.map +1 -1
  74. package/dist/client/locales/vi-VN.d.ts.map +1 -1
  75. package/dist/client/locales/vi-VN.js +26 -10
  76. package/dist/client/locales/vi-VN.js.map +1 -1
  77. package/dist/client/locales/zh-CN.d.ts.map +1 -1
  78. package/dist/client/locales/zh-CN.js +21 -10
  79. package/dist/client/locales/zh-CN.js.map +1 -1
  80. package/dist/client/locales/zh-TW.d.ts.map +1 -1
  81. package/dist/client/locales/zh-TW.js +26 -10
  82. package/dist/client/locales/zh-TW.js.map +1 -1
  83. package/dist/client/model-suggestion.d.ts +2 -0
  84. package/dist/client/model-suggestion.d.ts.map +1 -1
  85. package/dist/client/model-suggestion.js +27 -2
  86. package/dist/client/model-suggestion.js.map +1 -1
  87. package/dist/client/pages/agents/agent-dialog.d.ts.map +1 -1
  88. package/dist/client/pages/agents/agent-dialog.js +6 -7
  89. package/dist/client/pages/agents/agent-dialog.js.map +1 -1
  90. package/dist/client/pages/agents/available-models-drawer.d.ts.map +1 -1
  91. package/dist/client/pages/agents/available-models-drawer.js +2 -1
  92. package/dist/client/pages/agents/available-models-drawer.js.map +1 -1
  93. package/dist/client/pages/agents/good-at-templates.d.ts +8 -0
  94. package/dist/client/pages/agents/good-at-templates.d.ts.map +1 -0
  95. package/dist/client/pages/agents/good-at-templates.js +29 -0
  96. package/dist/client/pages/agents/good-at-templates.js.map +1 -0
  97. package/dist/client/pages/runtimes/runner-cells.d.ts.map +1 -1
  98. package/dist/client/pages/runtimes/runner-cells.js +3 -1
  99. package/dist/client/pages/runtimes/runner-cells.js.map +1 -1
  100. package/dist/client/pages/runtimes/runner-pagination.d.ts +15 -0
  101. package/dist/client/pages/runtimes/runner-pagination.d.ts.map +1 -0
  102. package/dist/client/pages/runtimes/runner-pagination.js +9 -0
  103. package/dist/client/pages/runtimes/runner-pagination.js.map +1 -0
  104. package/dist/client/pages/runtimes/runner-runs.d.ts +3 -2
  105. package/dist/client/pages/runtimes/runner-runs.d.ts.map +1 -1
  106. package/dist/client/pages/runtimes/runner-runs.js +38 -15
  107. package/dist/client/pages/runtimes/runner-runs.js.map +1 -1
  108. package/dist/client/pages/runtimes/runner-sheet.js +1 -1
  109. package/dist/client/pages/runtimes/runner-sheet.js.map +1 -1
  110. package/dist/client/pages/runtimes/runner-workspaces.d.ts.map +1 -1
  111. package/dist/client/pages/runtimes/runner-workspaces.js +7 -3
  112. package/dist/client/pages/runtimes/runner-workspaces.js.map +1 -1
  113. package/dist/client/runs/refresh-run.d.ts +4 -0
  114. package/dist/client/runs/refresh-run.d.ts.map +1 -0
  115. package/dist/client/runs/refresh-run.js +8 -0
  116. package/dist/client/runs/refresh-run.js.map +1 -0
  117. package/dist/client/runs/run-hooks.d.ts.map +1 -1
  118. package/dist/client/runs/run-hooks.js +2 -1
  119. package/dist/client/runs/run-hooks.js.map +1 -1
  120. package/dist/database/migrations/.manifest.json +8 -0
  121. package/dist/database/migrations/202611010003_ag_runner_run_history_index.d.ts +4 -0
  122. package/dist/database/migrations/202611010003_ag_runner_run_history_index.d.ts.map +1 -0
  123. package/dist/database/migrations/202611010003_ag_runner_run_history_index.js +17 -0
  124. package/dist/database/migrations/202611010003_ag_runner_run_history_index.js.map +1 -0
  125. package/dist/database/migrations/202611070001_ag_conversation_project.d.ts +4 -0
  126. package/dist/database/migrations/202611070001_ag_conversation_project.d.ts.map +1 -0
  127. package/dist/database/migrations/202611070001_ag_conversation_project.js +17 -0
  128. package/dist/database/migrations/202611070001_ag_conversation_project.js.map +1 -0
  129. package/dist/package.json +4 -6
  130. package/dist/server/composition.d.ts +0 -17
  131. package/dist/server/composition.d.ts.map +1 -1
  132. package/dist/server/composition.js +7 -20
  133. package/dist/server/composition.js.map +1 -1
  134. package/dist/server/core/conversations/conversation.service.d.ts.map +1 -1
  135. package/dist/server/core/conversations/conversation.service.js +160 -19
  136. package/dist/server/core/conversations/conversation.service.js.map +1 -1
  137. package/dist/server/core/conversations/conversation.store.d.ts +1 -0
  138. package/dist/server/core/conversations/conversation.store.d.ts.map +1 -1
  139. package/dist/server/core/conversations/conversation.store.js.map +1 -1
  140. package/dist/server/core/conversations/index.d.ts +1 -1
  141. package/dist/server/core/conversations/index.d.ts.map +1 -1
  142. package/dist/server/core/conversations/index.js.map +1 -1
  143. package/dist/server/core/conversations/ports.d.ts +14 -0
  144. package/dist/server/core/conversations/ports.d.ts.map +1 -1
  145. package/dist/server/core/runs/claim.d.ts +7 -1
  146. package/dist/server/core/runs/claim.d.ts.map +1 -1
  147. package/dist/server/core/runs/claim.js +69 -17
  148. package/dist/server/core/runs/claim.js.map +1 -1
  149. package/dist/server/core/runs/ports.d.ts +2 -0
  150. package/dist/server/core/runs/ports.d.ts.map +1 -1
  151. package/dist/server/core/runs/ports.js.map +1 -1
  152. package/dist/server/core/runs/run.service.d.ts +5 -0
  153. package/dist/server/core/runs/run.service.d.ts.map +1 -1
  154. package/dist/server/core/runs/run.service.js +22 -0
  155. package/dist/server/core/runs/run.service.js.map +1 -1
  156. package/dist/server/core/runs/run.store.d.ts +1 -1
  157. package/dist/server/core/runs/run.store.d.ts.map +1 -1
  158. package/dist/server/core/runs/run.store.js +1 -1
  159. package/dist/server/core/runs/run.store.js.map +1 -1
  160. package/dist/server/core/runs/runner-view.d.ts +8 -4
  161. package/dist/server/core/runs/runner-view.d.ts.map +1 -1
  162. package/dist/server/core/runs/runner-view.js +89 -32
  163. package/dist/server/core/runs/runner-view.js.map +1 -1
  164. package/dist/server/locales/de-DE.d.ts.map +1 -1
  165. package/dist/server/locales/de-DE.js +5 -0
  166. package/dist/server/locales/de-DE.js.map +1 -1
  167. package/dist/server/locales/en-US.d.ts +7 -1
  168. package/dist/server/locales/en-US.d.ts.map +1 -1
  169. package/dist/server/locales/en-US.js +5 -0
  170. package/dist/server/locales/en-US.js.map +1 -1
  171. package/dist/server/locales/es-ES.d.ts.map +1 -1
  172. package/dist/server/locales/es-ES.js +5 -0
  173. package/dist/server/locales/es-ES.js.map +1 -1
  174. package/dist/server/locales/fr-FR.d.ts.map +1 -1
  175. package/dist/server/locales/fr-FR.js +5 -0
  176. package/dist/server/locales/fr-FR.js.map +1 -1
  177. package/dist/server/locales/id-ID.d.ts.map +1 -1
  178. package/dist/server/locales/id-ID.js +5 -0
  179. package/dist/server/locales/id-ID.js.map +1 -1
  180. package/dist/server/locales/ja-JP.d.ts.map +1 -1
  181. package/dist/server/locales/ja-JP.js +5 -0
  182. package/dist/server/locales/ja-JP.js.map +1 -1
  183. package/dist/server/locales/pt-BR.d.ts.map +1 -1
  184. package/dist/server/locales/pt-BR.js +5 -0
  185. package/dist/server/locales/pt-BR.js.map +1 -1
  186. package/dist/server/locales/vi-VN.d.ts.map +1 -1
  187. package/dist/server/locales/vi-VN.js +5 -0
  188. package/dist/server/locales/vi-VN.js.map +1 -1
  189. package/dist/server/locales/zh-CN.d.ts +2 -2
  190. package/dist/server/locales/zh-CN.d.ts.map +1 -1
  191. package/dist/server/locales/zh-CN.js +5 -0
  192. package/dist/server/locales/zh-CN.js.map +1 -1
  193. package/dist/server/locales/zh-TW.d.ts.map +1 -1
  194. package/dist/server/locales/zh-TW.js +5 -0
  195. package/dist/server/locales/zh-TW.js.map +1 -1
  196. package/dist/server/online/agent/native/backend.d.ts.map +1 -1
  197. package/dist/server/online/agent/native/backend.js +2 -0
  198. package/dist/server/online/agent/native/backend.js.map +1 -1
  199. package/dist/server/online/agent/native/session.d.ts.map +1 -1
  200. package/dist/server/online/agent/native/session.js +3 -1
  201. package/dist/server/online/agent/native/session.js.map +1 -1
  202. package/dist/server/online/agent/runner/backend.d.ts.map +1 -1
  203. package/dist/server/online/agent/runner/backend.js +4 -1
  204. package/dist/server/online/agent/runner/backend.js.map +1 -1
  205. package/dist/server/online/agent/runner/projection.d.ts +1 -1
  206. package/dist/server/online/agent/runner/projection.d.ts.map +1 -1
  207. package/dist/server/online/agent/runner/projection.js +33 -3
  208. package/dist/server/online/agent/runner/projection.js.map +1 -1
  209. package/dist/server/online/agent/service.d.ts.map +1 -1
  210. package/dist/server/online/agent/service.js +3 -0
  211. package/dist/server/online/agent/service.js.map +1 -1
  212. package/dist/server/online/agent/types.d.ts +3 -0
  213. package/dist/server/online/agent/types.d.ts.map +1 -1
  214. package/dist/server/providers/agents.d.ts +5 -9
  215. package/dist/server/providers/agents.d.ts.map +1 -1
  216. package/dist/server/providers/agents.js +9 -13
  217. package/dist/server/providers/agents.js.map +1 -1
  218. package/dist/server/routes/chat.d.ts.map +1 -1
  219. package/dist/server/routes/chat.js +37 -0
  220. package/dist/server/routes/chat.js.map +1 -1
  221. package/dist/server/routes/runners/admin.d.ts.map +1 -1
  222. package/dist/server/routes/runners/admin.js +16 -7
  223. package/dist/server/routes/runners/admin.js.map +1 -1
  224. package/dist/server/routes/runners/runner.d.ts.map +1 -1
  225. package/dist/server/routes/runners/runner.js +6 -4
  226. package/dist/server/routes/runners/runner.js.map +1 -1
  227. package/dist/server/routes/schemas.d.ts +7 -1
  228. package/dist/server/routes/schemas.d.ts.map +1 -1
  229. package/dist/server/routes/schemas.js +41 -1
  230. package/dist/server/routes/schemas.js.map +1 -1
  231. package/dist/server/tokens.d.ts +1 -2
  232. package/dist/server/tokens.d.ts.map +1 -1
  233. package/dist/server/tokens.js.map +1 -1
  234. package/dist/shared/conversations.d.ts +16 -0
  235. package/dist/shared/conversations.d.ts.map +1 -1
  236. package/dist/shared/conversations.js.map +1 -1
  237. package/dist/shared/runners.d.ts +18 -2
  238. package/dist/shared/runners.d.ts.map +1 -1
  239. package/dist/shared/runners.js +20 -0
  240. package/dist/shared/runners.js.map +1 -1
  241. package/dist/shared/ui-message.d.ts +2 -0
  242. package/dist/shared/ui-message.d.ts.map +1 -1
  243. package/dist/shared/ui-message.js +1 -0
  244. package/dist/shared/ui-message.js.map +1 -1
  245. package/docs/ui-message-clients.md +5 -1
  246. package/docs/ui-messages.md +3 -1
  247. package/docs/vectors-migration.md +91 -0
  248. package/package.json +8 -10
  249. package/skills/nocobase-app-plugin-agents/SKILL.md +1 -1
  250. package/skills/nocobase-app-plugin-agents/references/extension-points.md +0 -1
  251. package/skills/nocobase-app-plugin-agents/references/model-services.md +3 -3
  252. package/skills/nocobase-app-plugin-agents/references/online-agent.md +6 -0
  253. package/skills/nocobase-app-plugin-agents/references/setup-and-config.md +0 -4
  254. package/skills/nocobase-app-plugin-agents/references/verification-and-troubleshooting.md +0 -2
  255. package/dist/server/vectors/index.d.ts +0 -7
  256. package/dist/server/vectors/index.d.ts.map +0 -1
  257. package/dist/server/vectors/index.js +0 -6
  258. package/dist/server/vectors/index.js.map +0 -1
  259. package/dist/server/vectors/naming.d.ts +0 -6
  260. package/dist/server/vectors/naming.d.ts.map +0 -1
  261. package/dist/server/vectors/naming.js +0 -15
  262. package/dist/server/vectors/naming.js.map +0 -1
  263. package/dist/server/vectors/pgvector.d.ts +0 -26
  264. package/dist/server/vectors/pgvector.d.ts.map +0 -1
  265. package/dist/server/vectors/pgvector.js +0 -346
  266. package/dist/server/vectors/pgvector.js.map +0 -1
  267. package/dist/server/vectors/sqlite-vec.d.ts +0 -14
  268. package/dist/server/vectors/sqlite-vec.d.ts.map +0 -1
  269. package/dist/server/vectors/sqlite-vec.js +0 -286
  270. package/dist/server/vectors/sqlite-vec.js.map +0 -1
  271. package/dist/server/vectors/types.d.ts +0 -256
  272. package/dist/server/vectors/types.d.ts.map +0 -1
  273. package/dist/server/vectors/types.js +0 -2
  274. package/dist/server/vectors/types.js.map +0 -1
  275. package/dist/server/vectors/vectors.d.ts +0 -34
  276. package/dist/server/vectors/vectors.d.ts.map +0 -1
  277. package/dist/server/vectors/vectors.js +0 -866
  278. package/dist/server/vectors/vectors.js.map +0 -1
@@ -31,9 +31,7 @@ export class ChatController {
31
31
  histories = new Map();
32
32
  loads = new Map();
33
33
  refreshAgain = new Set();
34
- resumed = new Set();
35
- resuming = new Map();
36
- recoveryErrors = new Map();
34
+ recoveries = new Map();
37
35
  admissions = new Map();
38
36
  watches = new Map();
39
37
  messageVersions = new Map();
@@ -47,6 +45,7 @@ export class ChatController {
47
45
  disposed = false;
48
46
  generation = 0;
49
47
  detailVersions = new Map();
48
+ receiptVersions = new Map();
50
49
  commandVersions = new Map();
51
50
  constructor(api) {
52
51
  this.api = api;
@@ -69,7 +68,22 @@ export class ChatController {
69
68
  const admission = this.admissions.get(id);
70
69
  if (!this.disposed && admission?.clientId === clientId)
71
70
  admission.gate.resolve();
72
- }, (options) => this.resendAccepted(id, options)),
71
+ }, (options) => this.resendAccepted(id, options), () => {
72
+ const active = this.lifetime(), recovery = this.recoveries.get(id);
73
+ return (available) => {
74
+ if (!active() ||
75
+ this.chats.get(id) !== chat ||
76
+ !recovery ||
77
+ this.recoveries.get(id) !== recovery)
78
+ return;
79
+ recovery.phase = available ? 'live' : 'history';
80
+ recovery.error = undefined;
81
+ // Reads started before reconnection must not replace its replay or revive old errors.
82
+ this.nextDetailVersion(id);
83
+ this.invalidateHistoryReads(id);
84
+ this.publish();
85
+ };
86
+ }),
73
87
  onFinish: () => {
74
88
  if (!this.disposed && this.chats.get(id) === chat)
75
89
  this.scheduleRefresh(id);
@@ -180,15 +194,18 @@ export class ChatController {
180
194
  this.publish();
181
195
  }
182
196
  historyLifetime(id) {
183
- const active = this.lifetime(), epoch = this.historyEpochs.get(id) ?? 0;
184
- return () => active() && (this.historyEpochs.get(id) ?? 0) === epoch;
197
+ const active = this.lifetime(), epoch = this.historyEpochs.get(id) ?? 0, command = this.commandVersions.get(id);
198
+ return () => active() &&
199
+ (this.historyEpochs.get(id) ?? 0) === epoch &&
200
+ this.commandVersions.get(id) === command;
185
201
  }
186
- invalidateHistoryReads(id) {
202
+ invalidateHistoryReads(id, discardDeferred = false) {
187
203
  this.historyEpochs.set(id, (this.historyEpochs.get(id) ?? 0) + 1);
188
204
  this.loads.delete(id);
189
205
  this.refreshAgain.delete(id);
190
206
  this.revisionQueue.delete(id);
191
- this.deferredReads.delete(id);
207
+ if (discardDeferred)
208
+ this.deferredReads.delete(id);
192
209
  for (const key of this.messageVersions.keys())
193
210
  if (key.startsWith(`${id}:`))
194
211
  this.messageVersions.delete(key);
@@ -221,8 +238,8 @@ export class ChatController {
221
238
  if (previous?.executionId === event.executionId ||
222
239
  (previous && previous.lastSeq > event.lastSeq))
223
240
  return false;
224
- this.invalidateHistoryReads(id);
225
- this.resuming.delete(id);
241
+ this.invalidateHistoryReads(id, true);
242
+ this.recoveries.delete(id);
226
243
  this.replacements.set(id, event);
227
244
  this.dispatch(id, { type: 'replaceHistory', ...event });
228
245
  this.historyMeta(id).loadedThrough = Math.max(this.historyMeta(id).loadedThrough, event.lastSeq);
@@ -448,7 +465,7 @@ export class ChatController {
448
465
  }
449
466
  const chat = this.chat(id);
450
467
  const receiving = chat.status === 'submitted' || chat.status === 'streaming';
451
- const activeId = receiving && chat.messages.at(-1)?.id === detail.execution?.messageId
468
+ const activeId = receiving && chat.messages.at(-1)?.role === 'assistant'
452
469
  ? chat.messages.at(-1)?.id
453
470
  : undefined;
454
471
  chat.messages = mergeUIMessages(chat.messages.filter((m) => !missing.has(m.id) || m.id === activeId), found, activeId);
@@ -664,14 +681,16 @@ export class ChatController {
664
681
  async sendInput(id, input) {
665
682
  if (this.disposed)
666
683
  throw new CancelledError({ silent: true });
667
- const chat = this.chat(id);
684
+ let chat = this.chat(id);
668
685
  if (chat.status === 'submitted' || chat.status === 'streaming')
669
686
  throw new Error('This conversation is already receiving a response.');
670
687
  if (this.resendRequests.has(id))
671
688
  throw new Error('This conversation is busy.');
672
689
  const admitted = deferred();
673
690
  this.admissions.set(id, { clientId: input.clientId, gate: admitted });
674
- this.recoveryErrors.delete(id);
691
+ this.recoveries.delete(id);
692
+ // A new turn owns its callbacks, including when the previous transport finishes late.
693
+ chat = this.renewChat(id, chat.messages);
675
694
  const active = this.lifetime();
676
695
  const sending = chat.sendMessage({
677
696
  id: input.clientId,
@@ -697,6 +716,7 @@ export class ChatController {
697
716
  }, {
698
717
  body: {
699
718
  ...(input.agentId ? { agentId: input.agentId } : {}),
719
+ ...(input.effort !== undefined ? { effort: input.effort } : {}),
700
720
  ...(input.context ? { context: input.context } : {}),
701
721
  ...(input.attachmentIds?.length
702
722
  ? { attachmentIds: input.attachmentIds }
@@ -727,42 +747,82 @@ export class ChatController {
727
747
  }
728
748
  state(id) {
729
749
  const chat = this.chats.get(id);
750
+ const recovery = this.recoveries.get(id);
751
+ const recovering = recovery?.phase === 'recovering';
752
+ const error = recovering ? undefined : (chat?.error ?? recovery?.error);
730
753
  return chat
731
754
  ? {
732
755
  status: chat.status,
733
- error: chat.error ?? this.recoveryErrors.get(id),
756
+ error,
757
+ recovery: recovering
758
+ ? 'recovering'
759
+ : error
760
+ ? 'interrupted'
761
+ : recovery?.phase === 'history'
762
+ ? 'history'
763
+ : undefined,
734
764
  }
735
765
  : undefined;
736
766
  }
767
+ recoveryKey(id, detail) {
768
+ return (detail.run?.id ??
769
+ detail.execution?.messageId ??
770
+ (detail.execution?.status === 'running'
771
+ ? this.recoveries.get(id)?.key
772
+ : undefined) ??
773
+ [...this.chat(id).messages].reverse().find((m) => m.role === 'user')
774
+ ?.id ??
775
+ '');
776
+ }
737
777
  receive(id, known) {
738
- if (this.disposed || this.resumed.has(id))
778
+ if (this.disposed)
739
779
  return;
740
780
  const detail = known ??
741
781
  agentsQueryClient().getQueryData(chatKeys.conversation(id));
742
- if (!detail?.execution)
782
+ if (!detail ||
783
+ this.recoveries.get(id)?.key === this.recoveryKey(id, detail))
743
784
  return;
744
- this.resumed.add(id);
745
- if (detail?.execution?.status === 'idle' ||
746
- detail?.execution?.waitingApproval)
747
- return;
748
- void this.resume(id);
785
+ void this.resume(id, detail);
749
786
  }
750
- async resume(id) {
787
+ async resume(id, known) {
751
788
  if (this.disposed)
752
789
  return;
753
- const pending = this.resuming.get(id);
754
- if (pending)
755
- return pending;
790
+ const detail = known ??
791
+ agentsQueryClient().getQueryData(chatKeys.conversation(id));
792
+ if (!detail)
793
+ return;
794
+ if (!chatIsRunning(detail) || detail.execution?.waitingApproval) {
795
+ const chat = this.chats.get(id);
796
+ if (chat?.status !== 'submitted' && chat?.status !== 'streaming')
797
+ await this.loadHistory(id);
798
+ return;
799
+ }
800
+ const key = this.recoveryKey(id, detail);
801
+ const pending = this.recoveries.get(id);
756
802
  let chat = this.chat(id);
757
- if (chat.status === 'submitted' || chat.status === 'streaming')
803
+ if (chat.status === 'submitted' || chat.status === 'streaming') {
804
+ if (!pending || pending.key !== key)
805
+ this.recoveries.set(id, { key, phase: 'live' });
758
806
  return;
807
+ }
808
+ if (pending?.key === key) {
809
+ if (pending.task)
810
+ return pending.task;
811
+ if (pending.phase === 'history')
812
+ return;
813
+ }
759
814
  const active = this.lifetime();
760
815
  const replacement = this.replacements.get(id);
761
- const canResume = () => active() &&
816
+ const command = this.commandVersions.get(id);
817
+ const recovery = { key, phase: 'recovering' };
818
+ this.recoveries.set(id, recovery);
819
+ this.publish();
820
+ const current = () => active() &&
821
+ this.commandVersions.get(id) === command &&
822
+ this.recoveries.get(id) === recovery &&
762
823
  this.replacements.get(id) === replacement &&
763
- this.chats.get(id) === chat &&
764
- chat.status !== 'submitted' &&
765
- chat.status !== 'streaming';
824
+ this.chats.get(id) === chat;
825
+ const canResume = () => current() && chat.status !== 'submitted' && chat.status !== 'streaming';
766
826
  const task = (async () => {
767
827
  let interrupted;
768
828
  try {
@@ -770,6 +830,8 @@ export class ChatController {
770
830
  // A new input can begin while the recovery snapshot is still being read.
771
831
  if (!canResume())
772
832
  return;
833
+ if (this.history(id).error)
834
+ throw this.history(id).error;
773
835
  if (chat.error) {
774
836
  const previous = chat;
775
837
  interrupted = [...previous.messages]
@@ -790,36 +852,57 @@ export class ChatController {
790
852
  if (!active())
791
853
  return;
792
854
  }
793
- this.recoveryErrors.delete(id);
794
855
  chat.clearError();
795
856
  const response = [...chat.messages]
796
857
  .reverse()
797
858
  .find((message) => message.role === 'assistant');
798
- if (response)
859
+ if (response &&
860
+ (response === interrupted ||
861
+ !key ||
862
+ response.metadata?.executionId === key ||
863
+ response.id === detail.execution?.messageId)) {
864
+ interrupted ??= response;
799
865
  chat.messages = mergeUIMessages(chat.messages, [], response.id);
866
+ }
800
867
  await chat.resumeStream();
801
- if (active())
868
+ if (current()) {
869
+ if (chat.error) {
870
+ recovery.phase = 'interrupted';
871
+ recovery.error = chat.error;
872
+ }
802
873
  await this.refresh(id);
874
+ }
803
875
  }
804
876
  catch (error) {
805
- if (canResume())
806
- this.recoveryErrors.set(id, error instanceof Error
807
- ? error
808
- : new Error('Stream recovery failed.'));
877
+ if (current()) {
878
+ if (chat.error) {
879
+ recovery.phase = 'interrupted';
880
+ recovery.error = chat.error;
881
+ }
882
+ else {
883
+ // A failed history read is not evidence that the transport failed.
884
+ this.historyMeta(id).error = error;
885
+ if (recovery.phase === 'recovering')
886
+ recovery.phase = 'live';
887
+ }
888
+ }
809
889
  }
810
890
  finally {
811
- if (active()) {
891
+ if (current()) {
812
892
  const display = interrupted;
813
893
  if (display &&
814
894
  canResume() &&
815
895
  !chat.messages.some((message) => message.id === display.id))
816
896
  chat.messages = mergeUIMessages(chat.messages, [display]);
817
- this.resuming.delete(id);
897
+ recovery.task = undefined;
898
+ if (recovery.phase === 'recovering' &&
899
+ (chat.status === 'submitted' || chat.status === 'streaming'))
900
+ recovery.phase = 'live';
818
901
  this.publish();
819
902
  }
820
903
  }
821
904
  })();
822
- this.resuming.set(id, task);
905
+ recovery.task = task;
823
906
  return task;
824
907
  }
825
908
  async approve(id, input) {
@@ -867,15 +950,24 @@ export class ChatController {
867
950
  async refresh(id) {
868
951
  if (this.disposed)
869
952
  return;
870
- const active = this.lifetime(), ordered = this.detailRequest(id);
953
+ const lifetime = this.lifetime(), commandVersion = this.commandVersions.get(id), active = () => lifetime() && this.commandVersions.get(id) === commandVersion, ordered = this.detailRequest(id);
954
+ const submittedOwner = this.chat(id);
955
+ const submittedInput = submittedOwner.status === 'submitted' && !this.admissions.has(id)
956
+ ? [...submittedOwner.messages]
957
+ .reverse()
958
+ .find((message) => message.role === 'user' && message.metadata?.seq !== undefined)
959
+ : undefined;
871
960
  const detail = await this.api.conversation(id);
872
- if (!active() || !ordered())
961
+ if (!active())
873
962
  return;
874
- await this.reconcile(id, detail);
875
- if (!active() || !ordered())
963
+ if (ordered())
964
+ await this.reconcile(id, detail);
965
+ if (!active())
876
966
  return;
967
+ // A newer detail query must not discard this refresh's message catch-up.
968
+ // loadHistory coalesces overlapping reads independently of detail ordering.
877
969
  await this.loadHistory(id, false, true);
878
- if (!active() || !ordered())
970
+ if (!active())
879
971
  return;
880
972
  const history = this.history(id);
881
973
  if (history.error)
@@ -900,14 +992,29 @@ export class ChatController {
900
992
  if (!active() || !ordered())
901
993
  return;
902
994
  const owner = this.chat(id), receiving = owner.messages.at(-1);
903
- if (owner.status === 'streaming' &&
904
- receiving?.role === 'assistant' &&
905
- (detail.execution?.status === 'idle' ||
906
- detail.execution?.waitingApproval) &&
907
- saved.some((message) => message.id === receiving.id &&
995
+ const lastUser = [...owner.messages]
996
+ .reverse()
997
+ .find((message) => message.role === 'user');
998
+ const executionId = submittedInput?.metadata?.executionId ?? this.recoveries.get(id)?.key;
999
+ const submittedTerminal = owner === submittedOwner &&
1000
+ owner.status === 'submitted' &&
1001
+ submittedInput &&
1002
+ lastUser?.id === submittedInput.id &&
1003
+ detail.execution?.status === 'idle' &&
1004
+ [...saved, ...owner.messages].some((message) => message.role !== 'user' &&
908
1005
  message.metadata?.seq !== undefined &&
909
- message.metadata?.executionId === receiving.metadata?.executionId &&
910
- message.metadata.revision >= (detail.execution?.revision ?? 0))) {
1006
+ message.metadata.seq > submittedInput.metadata.seq &&
1007
+ ((executionId && message.metadata.executionId === executionId) ||
1008
+ message.id === detail.execution?.messageId));
1009
+ if (submittedTerminal ||
1010
+ (owner.status === 'streaming' &&
1011
+ receiving?.role === 'assistant' &&
1012
+ (detail.execution?.status === 'idle' ||
1013
+ detail.execution?.waitingApproval) &&
1014
+ saved.some((message) => message.id === receiving.id &&
1015
+ message.metadata?.seq !== undefined &&
1016
+ message.metadata?.executionId === receiving.metadata?.executionId &&
1017
+ message.metadata.revision >= (detail.execution?.revision ?? 0)))) {
911
1018
  // Durable completion can release a lost subscriber; this never calls business Stop.
912
1019
  await owner.stop();
913
1020
  if (!active() || !ordered())
@@ -916,20 +1023,13 @@ export class ChatController {
916
1023
  }
917
1024
  if (!active() || !ordered())
918
1025
  return;
919
- this.remember(detail);
1026
+ this.remember(detail, false);
920
1027
  const chat = this.chat(id);
921
1028
  if (detail.execution?.status === 'idle') {
922
- this.recoveryErrors.delete(id);
923
- if (chat.error &&
924
- saved.some((message) => message.id === chat.messages.at(-1)?.id &&
925
- message.metadata?.seq !== undefined))
1029
+ this.recoveries.delete(id);
1030
+ if (chat.error)
926
1031
  this.renewChat(id, chat.messages);
927
1032
  }
928
- else if (detail.execution?.status === 'running' &&
929
- !detail.execution.waitingApproval &&
930
- chat.status !== 'submitted' &&
931
- chat.status !== 'streaming')
932
- this.recoveryErrors.set(id, new Error('Execution remains active; reconcile saved history or resume its stream.'));
933
1033
  this.publish();
934
1034
  }
935
1035
  scheduleRefresh(id) {
@@ -942,14 +1042,16 @@ export class ChatController {
942
1042
  void this.refresh(id).catch(() => { });
943
1043
  });
944
1044
  }
945
- observeDetail(detail) {
1045
+ observeDetail(detail, refresh = true) {
946
1046
  if (detail.execution?.status === 'running')
947
1047
  this.receive(detail.id, detail);
948
1048
  if (!chatIsRunning(detail)) {
949
- if (this.chats.get(detail.id)?.status === 'streaming')
1049
+ if (refresh &&
1050
+ (this.chats.get(detail.id)?.status === 'streaming' ||
1051
+ this.chats.get(detail.id)?.status === 'submitted'))
950
1052
  this.scheduleRefresh(detail.id);
951
1053
  this.stopRequests.delete(detail.id);
952
- this.recoveryErrors.delete(detail.id);
1054
+ this.recoveries.delete(detail.id);
953
1055
  this.publish();
954
1056
  }
955
1057
  }
@@ -984,16 +1086,38 @@ export class ChatController {
984
1086
  const active = this.lifetime(), version = this.nextDetailVersion(id);
985
1087
  return () => active() && this.detailVersions.get(id) === version;
986
1088
  }
987
- remember(detail) {
1089
+ remember(detail, refresh = true) {
988
1090
  this.nextDetailVersion(detail.id);
989
- this.observeDetail(detail);
1091
+ this.observeDetail(detail, refresh);
990
1092
  agentsQueryClient().setQueryData(chatKeys.conversation(detail.id), detail);
991
1093
  void agentsQueryClient().invalidateQueries({ queryKey: chatKeys.lists });
992
1094
  }
1095
+ async markRead(id) {
1096
+ const active = this.lifetime(), commandVersion = this.commandVersions.get(id), receiptVersion = (this.receiptVersions.get(id) ?? 0) + 1;
1097
+ // Detail reads must not suppress the read transition needed by a viewed conversation.
1098
+ this.receiptVersions.set(id, receiptVersion);
1099
+ 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 });
1108
+ }
1109
+ return detail;
1110
+ }
993
1111
  async command(id, request, detail) {
994
1112
  const active = this.lifetime(), version = (this.commandVersions.get(id) ?? 0) + 1;
995
1113
  this.commandVersions.set(id, version);
996
1114
  this.nextDetailVersion(id);
1115
+ this.invalidateHistoryReads(id);
1116
+ const recovery = this.recoveries.get(id);
1117
+ if (recovery?.phase === 'history')
1118
+ this.recoveries.set(id, { key: recovery.key, phase: 'history' });
1119
+ else
1120
+ this.recoveries.delete(id);
997
1121
  await agentsQueryClient().cancelQueries({
998
1122
  queryKey: chatKeys.conversation(id),
999
1123
  exact: true,
@@ -1044,10 +1168,10 @@ export class ChatController {
1044
1168
  this.loads.clear();
1045
1169
  this.refreshAgain.clear();
1046
1170
  this.commandVersions.clear();
1171
+ this.receiptVersions.clear();
1047
1172
  this.stopRequests.clear();
1048
1173
  this.approvalRequests.clear();
1049
- this.resuming.clear();
1050
- this.resumed.clear();
1174
+ this.recoveries.clear();
1051
1175
  this.submissions.invalidate();
1052
1176
  agentsQueryClient().removeQueries({ queryKey: chatKeys.all });
1053
1177
  }