@dxos/plugin-assistant 0.13.0 → 0.14.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 (234) hide show
  1. package/PLUGIN.mdl +25 -0
  2. package/dist/lib/AssistantCapabilities.mjs +8 -1
  3. package/dist/lib/AssistantCapabilities.mjs.map +1 -1
  4. package/dist/lib/Settings.mjs +4 -0
  5. package/dist/lib/Settings.mjs.map +1 -1
  6. package/dist/lib/TurnReviewSkill.mjs +55 -0
  7. package/dist/lib/TurnReviewSkill.mjs.map +1 -0
  8. package/dist/lib/chunk-AgentArticle.mjs +1 -1
  9. package/dist/lib/chunk-AssistantSettings.mjs +1 -1
  10. package/dist/lib/chunk-AssistantSettings.mjs.map +1 -1
  11. package/dist/lib/chunk-Chat.mjs +322 -174
  12. package/dist/lib/chunk-Chat.mjs.map +1 -1
  13. package/dist/lib/chunk-ChatArticle.mjs +14 -7
  14. package/dist/lib/chunk-ChatArticle.mjs.map +1 -1
  15. package/dist/lib/chunk-ChatDialog.mjs +5 -4
  16. package/dist/lib/chunk-ChatDialog.mjs.map +1 -1
  17. package/dist/lib/chunk-IntegrationPrompt.mjs +1 -1
  18. package/dist/lib/chunk-PLUGIN.mjs +1 -1
  19. package/dist/lib/chunk-PluginUrlPrompt.mjs +1 -1
  20. package/dist/lib/chunk-QuestionSurface.mjs +1 -1
  21. package/dist/lib/chunk-SpaceHomePrompt.mjs +116 -60
  22. package/dist/lib/chunk-SpaceHomePrompt.mjs.map +1 -1
  23. package/dist/lib/chunk-TriggerStatus.mjs +1 -1
  24. package/dist/lib/chunk-agent-service.mjs +3 -1
  25. package/dist/lib/chunk-agent-service.mjs.map +1 -1
  26. package/dist/lib/chunk-analyze-turn.mjs +220 -0
  27. package/dist/lib/chunk-analyze-turn.mjs.map +1 -0
  28. package/dist/lib/{chunk-processor.mjs → chunk-chat-model.mjs} +95 -43
  29. package/dist/lib/chunk-chat-model.mjs.map +1 -0
  30. package/dist/lib/chunk-context.mjs.map +1 -1
  31. package/dist/lib/chunk-create-chat.mjs +5 -19
  32. package/dist/lib/chunk-create-chat.mjs.map +1 -1
  33. package/dist/lib/chunk-create-object.mjs +1 -1
  34. package/dist/lib/chunk-default-skills.mjs +28 -0
  35. package/dist/lib/chunk-default-skills.mjs.map +1 -0
  36. package/dist/lib/chunk-generate-home-suggestions.mjs +8 -6
  37. package/dist/lib/chunk-generate-home-suggestions.mjs.map +1 -1
  38. package/dist/lib/chunk-presets.mjs +1 -1
  39. package/dist/lib/chunk-presets.mjs.map +1 -1
  40. package/dist/lib/chunk-question-resumer.mjs +1 -1
  41. package/dist/lib/chunk-react-surface.mjs +1 -1
  42. package/dist/lib/chunk-run-prompt-in-chat.mjs +2 -1
  43. package/dist/lib/chunk-run-prompt-in-chat.mjs.map +1 -1
  44. package/dist/lib/chunk-run-prompt-in-new-chat.mjs +1 -1
  45. package/dist/lib/chunk-schema-defs.mjs +2 -2
  46. package/dist/lib/chunk-skill-definition.mjs +3 -2
  47. package/dist/lib/chunk-skill-definition.mjs.map +1 -1
  48. package/dist/lib/components.mjs +13 -13
  49. package/dist/lib/components.mjs.map +1 -1
  50. package/dist/lib/hooks.mjs +40 -31
  51. package/dist/lib/hooks.mjs.map +1 -1
  52. package/dist/lib/operations.mjs +3 -1
  53. package/dist/lib/operations.mjs.map +1 -1
  54. package/dist/lib/skills.mjs +2 -1
  55. package/dist/lib/translations.mjs +4 -2
  56. package/dist/lib/translations.mjs.map +1 -1
  57. package/dist/types/src/Chat.d.ts +1 -1
  58. package/dist/types/src/capabilities/agent-service.d.ts.map +1 -1
  59. package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -1
  60. package/dist/types/src/{processor/processor.d.ts → chat-model/chat-model.d.ts} +24 -22
  61. package/dist/types/src/chat-model/chat-model.d.ts.map +1 -0
  62. package/dist/types/src/chat-model/chat-model.edge.test.d.ts +2 -0
  63. package/dist/types/src/chat-model/chat-model.edge.test.d.ts.map +1 -0
  64. package/dist/types/src/chat-model/chat-model.node.test.d.ts +2 -0
  65. package/dist/types/src/chat-model/chat-model.node.test.d.ts.map +1 -0
  66. package/dist/types/src/chat-model/chat-model.scripted.node.test.d.ts +2 -0
  67. package/dist/types/src/chat-model/chat-model.scripted.node.test.d.ts.map +1 -0
  68. package/dist/types/src/{processor → chat-model}/index.d.ts +1 -1
  69. package/dist/types/src/chat-model/index.d.ts.map +1 -0
  70. package/dist/types/src/chat-model/outbox.d.ts.map +1 -0
  71. package/dist/types/src/chat-model/outbox.test.d.ts.map +1 -0
  72. package/dist/types/src/chat-model/presets.d.ts.map +1 -0
  73. package/dist/types/src/chat-model/presets.test.d.ts.map +1 -0
  74. package/dist/types/src/{processor → chat-model}/prompt.d.ts +2 -2
  75. package/dist/types/src/chat-model/prompt.d.ts.map +1 -0
  76. package/dist/types/src/chat-model/prompt.test.d.ts.map +1 -0
  77. package/dist/types/src/chat-model/streaming.node.test.d.ts.map +1 -0
  78. package/dist/types/src/chat-model/testing/chat-model-recorder.d.ts +58 -0
  79. package/dist/types/src/chat-model/testing/chat-model-recorder.d.ts.map +1 -0
  80. package/dist/types/src/chat-model/testing/harness.d.ts +22 -0
  81. package/dist/types/src/chat-model/testing/harness.d.ts.map +1 -0
  82. package/dist/types/src/chat-model/thread.d.ts.map +1 -0
  83. package/dist/types/src/chat-model/thread.test.d.ts.map +1 -0
  84. package/dist/types/src/components/Chat/Chat.d.ts +6 -6
  85. package/dist/types/src/components/Chat/Chat.d.ts.map +1 -1
  86. package/dist/types/src/components/Chat/context.d.ts +5 -3
  87. package/dist/types/src/components/Chat/context.d.ts.map +1 -1
  88. package/dist/types/src/components/ChatPrompt/ChatActions.d.ts +1 -1
  89. package/dist/types/src/components/ChatPrompt/ChatActions.d.ts.map +1 -1
  90. package/dist/types/src/components/ChatPrompt/ChatMcpErrors.d.ts +3 -3
  91. package/dist/types/src/components/ChatPrompt/ChatMcpErrors.d.ts.map +1 -1
  92. package/dist/types/src/components/ChatPrompt/ChatOptions.d.ts +3 -1
  93. package/dist/types/src/components/ChatPrompt/ChatOptions.d.ts.map +1 -1
  94. package/dist/types/src/components/ChatPrompt/ChatOptions.stories.d.ts +4 -3
  95. package/dist/types/src/components/ChatPrompt/ChatOptions.stories.d.ts.map +1 -1
  96. package/dist/types/src/components/ChatPrompt/ChatPrompt.d.ts +15 -5
  97. package/dist/types/src/components/ChatPrompt/ChatPrompt.d.ts.map +1 -1
  98. package/dist/types/src/components/ChatPrompt/ChatPrompt.stories.d.ts +1 -1
  99. package/dist/types/src/components/ChatPrompt/ChatStatus.d.ts +1 -1
  100. package/dist/types/src/components/ChatPrompt/ChatStatus.d.ts.map +1 -1
  101. package/dist/types/src/components/ConnectorAuthMenu/ConnectorAuthMenu.stories.d.ts +9 -0
  102. package/dist/types/src/components/ConnectorAuthMenu/ConnectorAuthMenu.stories.d.ts.map +1 -1
  103. package/dist/types/src/components/Toolbox/Toolbox.d.ts +3 -3
  104. package/dist/types/src/components/Toolbox/Toolbox.d.ts.map +1 -1
  105. package/dist/types/src/containers/ChatArticle/ChatArticle.d.ts.map +1 -1
  106. package/dist/types/src/containers/ChatArticle/ChatArticle.stories.d.ts +1 -1
  107. package/dist/types/src/containers/SpaceHomePrompt/SpaceHomePrompt.d.ts.map +1 -1
  108. package/dist/types/src/hooks/index.d.ts +2 -2
  109. package/dist/types/src/hooks/index.d.ts.map +1 -1
  110. package/dist/types/src/hooks/{useChatProcessor.d.ts → useChatModel.d.ts} +6 -6
  111. package/dist/types/src/hooks/useChatModel.d.ts.map +1 -0
  112. package/dist/types/src/hooks/useDebug.d.ts +3 -3
  113. package/dist/types/src/hooks/useDebug.d.ts.map +1 -1
  114. package/dist/types/src/hooks/useHomeSuggestions.d.ts +3 -3
  115. package/dist/types/src/hooks/useHomeSuggestions.d.ts.map +1 -1
  116. package/dist/types/src/hooks/usePresets.d.ts +1 -1
  117. package/dist/types/src/hooks/usePresets.d.ts.map +1 -1
  118. package/dist/types/src/hooks/useSelectionContext.d.ts +3 -3
  119. package/dist/types/src/hooks/useSelectionContext.d.ts.map +1 -1
  120. package/dist/types/src/operations/analyze-turn.d.ts +5 -0
  121. package/dist/types/src/operations/analyze-turn.d.ts.map +1 -0
  122. package/dist/types/src/operations/analyze-turn.test.d.ts +2 -0
  123. package/dist/types/src/operations/analyze-turn.test.d.ts.map +1 -0
  124. package/dist/types/src/operations/create-chat.d.ts.map +1 -1
  125. package/dist/types/src/operations/generate-home-suggestions.d.ts.map +1 -1
  126. package/dist/types/src/operations/index.d.ts.map +1 -1
  127. package/dist/types/src/operations/run-prompt-in-chat.d.ts.map +1 -1
  128. package/dist/types/src/review/turn-review.d.ts +58 -0
  129. package/dist/types/src/review/turn-review.d.ts.map +1 -0
  130. package/dist/types/src/review/turn-review.test.d.ts +2 -0
  131. package/dist/types/src/review/turn-review.test.d.ts.map +1 -0
  132. package/dist/types/src/skills/index.d.ts +1 -0
  133. package/dist/types/src/skills/index.d.ts.map +1 -1
  134. package/dist/types/src/skills/turn-review/TurnReviewSkill.d.ts +21 -0
  135. package/dist/types/src/skills/turn-review/TurnReviewSkill.d.ts.map +1 -0
  136. package/dist/types/src/skills/turn-review/index.d.ts +2 -0
  137. package/dist/types/src/skills/turn-review/index.d.ts.map +1 -0
  138. package/dist/types/src/translations.d.ts.map +1 -1
  139. package/dist/types/src/types/Assistant.d.ts +1 -0
  140. package/dist/types/src/types/Assistant.d.ts.map +1 -1
  141. package/dist/types/src/types/AssistantCapabilities.d.ts +7 -1
  142. package/dist/types/src/types/AssistantCapabilities.d.ts.map +1 -1
  143. package/dist/types/src/types/Settings.d.ts +1 -0
  144. package/dist/types/src/types/Settings.d.ts.map +1 -1
  145. package/dist/types/src/util/agent-options.d.ts +27 -0
  146. package/dist/types/src/util/agent-options.d.ts.map +1 -0
  147. package/dist/types/src/util/agent-options.test.d.ts +2 -0
  148. package/dist/types/src/util/agent-options.test.d.ts.map +1 -0
  149. package/dist/types/src/util/default-skills.d.ts +10 -0
  150. package/dist/types/src/util/default-skills.d.ts.map +1 -0
  151. package/dist/types/tsconfig.tsbuildinfo +1 -1
  152. package/package.json +89 -80
  153. package/src/capabilities/agent-service.ts +2 -1
  154. package/src/capabilities/skill-definition.ts +2 -1
  155. package/src/chat-model/chat-model.edge.test.ts +301 -0
  156. package/src/{processor/processor.node.test.ts → chat-model/chat-model.node.test.ts} +19 -9
  157. package/src/chat-model/chat-model.scripted.node.test.ts +441 -0
  158. package/src/{processor/processor.ts → chat-model/chat-model.ts} +140 -45
  159. package/src/{processor → chat-model}/index.ts +1 -1
  160. package/src/{processor → chat-model}/prompt.ts +2 -2
  161. package/src/{processor → chat-model}/streaming.node.test.ts +108 -116
  162. package/src/chat-model/testing/chat-model-recorder.ts +249 -0
  163. package/src/chat-model/testing/harness.ts +81 -0
  164. package/src/{processor → chat-model}/thread.test.ts +1 -1
  165. package/src/components/Chat/Chat.tsx +44 -38
  166. package/src/components/Chat/Error.stories.tsx +1 -1
  167. package/src/components/Chat/context.ts +5 -3
  168. package/src/components/ChatPrompt/ChatActions.tsx +1 -1
  169. package/src/components/ChatPrompt/ChatMcpErrors.tsx +6 -6
  170. package/src/components/ChatPrompt/ChatOptions.stories.tsx +37 -3
  171. package/src/components/ChatPrompt/ChatOptions.tsx +118 -10
  172. package/src/components/ChatPrompt/ChatPrompt.stories.tsx +10 -10
  173. package/src/components/ChatPrompt/ChatPrompt.tsx +44 -27
  174. package/src/components/ChatPrompt/ChatStatus.tsx +8 -7
  175. package/src/components/Toolbox/Toolbox.tsx +12 -13
  176. package/src/containers/AssistantSettings/AssistantSettings.tsx +1 -1
  177. package/src/containers/ChatArticle/ChatArticle.stories.tsx +1 -1
  178. package/src/containers/ChatArticle/ChatArticle.tsx +16 -6
  179. package/src/containers/ChatDialog/ChatDialog.tsx +4 -4
  180. package/src/containers/SpaceHomePrompt/SpaceHomePrompt.tsx +110 -57
  181. package/src/hooks/index.ts +2 -2
  182. package/src/hooks/{useChatProcessor.ts → useChatModel.ts} +16 -14
  183. package/src/hooks/useDebug.ts +8 -8
  184. package/src/hooks/useHomeSuggestions.ts +14 -9
  185. package/src/hooks/usePresets.ts +2 -2
  186. package/src/hooks/useProcessEphemeralStatus.ts +1 -1
  187. package/src/hooks/useSelectionContext.ts +3 -5
  188. package/src/operations/analyze-turn.test.ts +194 -0
  189. package/src/operations/analyze-turn.ts +96 -0
  190. package/src/operations/create-chat.ts +4 -36
  191. package/src/operations/generate-home-suggestions.test.ts +1 -2
  192. package/src/operations/generate-home-suggestions.ts +17 -9
  193. package/src/operations/index.ts +2 -0
  194. package/src/operations/run-prompt-in-chat.ts +3 -2
  195. package/src/review/turn-review.test.ts +92 -0
  196. package/src/review/turn-review.ts +213 -0
  197. package/src/skills/index.ts +1 -0
  198. package/src/skills/turn-review/TurnReviewSkill.ts +46 -0
  199. package/src/skills/turn-review/index.ts +5 -0
  200. package/src/translations.ts +2 -0
  201. package/src/types/AssistantCapabilities.ts +8 -1
  202. package/src/types/Settings.ts +7 -0
  203. package/src/util/agent-options.test.ts +54 -0
  204. package/src/util/agent-options.ts +60 -0
  205. package/src/util/default-skills.ts +35 -0
  206. package/dist/lib/chunk-processor.mjs.map +0 -1
  207. package/dist/types/src/hooks/useChatProcessor.d.ts.map +0 -1
  208. package/dist/types/src/processor/index.d.ts.map +0 -1
  209. package/dist/types/src/processor/outbox.d.ts.map +0 -1
  210. package/dist/types/src/processor/outbox.test.d.ts.map +0 -1
  211. package/dist/types/src/processor/presets.d.ts.map +0 -1
  212. package/dist/types/src/processor/presets.test.d.ts.map +0 -1
  213. package/dist/types/src/processor/processor.d.ts.map +0 -1
  214. package/dist/types/src/processor/processor.node.test.d.ts +0 -2
  215. package/dist/types/src/processor/processor.node.test.d.ts.map +0 -1
  216. package/dist/types/src/processor/prompt.d.ts.map +0 -1
  217. package/dist/types/src/processor/prompt.test.d.ts.map +0 -1
  218. package/dist/types/src/processor/streaming.node.test.d.ts.map +0 -1
  219. package/dist/types/src/processor/thread.d.ts.map +0 -1
  220. package/dist/types/src/processor/thread.test.d.ts.map +0 -1
  221. /package/dist/types/src/{processor → chat-model}/outbox.d.ts +0 -0
  222. /package/dist/types/src/{processor → chat-model}/outbox.test.d.ts +0 -0
  223. /package/dist/types/src/{processor → chat-model}/presets.d.ts +0 -0
  224. /package/dist/types/src/{processor → chat-model}/presets.test.d.ts +0 -0
  225. /package/dist/types/src/{processor → chat-model}/prompt.test.d.ts +0 -0
  226. /package/dist/types/src/{processor → chat-model}/streaming.node.test.d.ts +0 -0
  227. /package/dist/types/src/{processor → chat-model}/thread.d.ts +0 -0
  228. /package/dist/types/src/{processor → chat-model}/thread.test.d.ts +0 -0
  229. /package/src/{processor → chat-model}/outbox.test.ts +0 -0
  230. /package/src/{processor → chat-model}/outbox.ts +0 -0
  231. /package/src/{processor → chat-model}/presets.test.ts +0 -0
  232. /package/src/{processor → chat-model}/presets.ts +0 -0
  233. /package/src/{processor → chat-model}/prompt.test.ts +0 -0
  234. /package/src/{processor → chat-model}/thread.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-assistant",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Assistant plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -175,6 +175,11 @@
175
175
  "types": "./dist/types/src/TracePanel.d.ts",
176
176
  "import": "./dist/lib/ns/TracePanel.mjs"
177
177
  },
178
+ "./TurnReviewSkill": {
179
+ "source": "./src/skills/turn-review/TurnReviewSkill.ts",
180
+ "types": "./dist/types/src/skills/turn-review/TurnReviewSkill.d.ts",
181
+ "import": "./dist/lib/TurnReviewSkill.mjs"
182
+ },
178
183
  "./translations": {
179
184
  "source": "./src/translations.ts",
180
185
  "types": "./dist/types/src/translations.d.ts",
@@ -232,67 +237,69 @@
232
237
  "@effect/ai-openai": "4.0.0",
233
238
  "@effect/atom-react": "4.0.0",
234
239
  "effect": "4.0.0",
235
- "@dxos/agent-runtime": "0.13.0",
236
- "@dxos/app-graph": "0.13.0",
237
- "@dxos/ai": "0.13.0",
238
- "@dxos/app-framework": "0.13.0",
239
- "@dxos/app-toolkit": "0.13.0",
240
- "@dxos/assistant": "0.13.0",
241
- "@dxos/assistant-toolkit": "0.13.0",
242
- "@dxos/client": "0.13.0",
243
- "@dxos/async": "0.13.0",
244
- "@dxos/compute-runtime": "0.13.0",
245
- "@dxos/compute": "0.13.0",
246
- "@dxos/conductor": "0.13.0",
247
- "@dxos/config": "0.13.0",
248
- "@dxos/echo": "0.13.0",
249
- "@dxos/devtools": "0.13.0",
250
- "@dxos/echo-client": "0.13.0",
251
- "@dxos/echo-react": "0.13.0",
252
- "@dxos/edge-client": "0.13.0",
253
- "@dxos/effect": "0.13.0",
254
- "@dxos/errors": "0.13.0",
255
- "@dxos/graph": "0.13.0",
256
- "@dxos/halo-react": "0.13.0",
257
- "@dxos/invariant": "0.13.0",
258
- "@dxos/link": "0.13.0",
259
- "@dxos/mcp-client": "0.13.0",
260
- "@dxos/keys": "0.13.0",
261
- "@dxos/plugin-attention": "0.13.0",
262
- "@dxos/log": "0.13.0",
263
- "@dxos/plugin-client": "0.13.0",
264
- "@dxos/plugin-connector": "0.13.0",
265
- "@dxos/plugin-deck": "0.13.0",
266
- "@dxos/plugin-markdown": "0.13.0",
267
- "@dxos/plugin-registry": "0.13.0",
268
- "@dxos/plugin-graph": "0.13.0",
269
- "@dxos/plugin-routine": "0.13.0",
270
- "@dxos/plugin-space": "0.13.0",
271
- "@dxos/plugin-status-bar": "0.13.0",
272
- "@dxos/plugin-tasks": "0.13.0",
273
- "@dxos/plugin-transcription": "0.13.0",
274
- "@dxos/react-hooks": "0.13.0",
275
- "@dxos/react-client": "0.13.0",
276
- "@dxos/react-list": "0.13.0",
277
- "@dxos/react-ui-assistant": "0.13.0",
278
- "@dxos/react-ui-attention": "0.13.0",
279
- "@dxos/react-ui-chat": "0.13.0",
280
- "@dxos/react-ui-components": "0.13.0",
281
- "@dxos/react-ui-form": "0.13.0",
282
- "@dxos/react-ui-feed": "0.13.0",
283
- "@dxos/react-ui-search": "0.13.0",
284
- "@dxos/react-ui-menu": "0.13.0",
285
- "@dxos/react-ui-task": "0.13.0",
286
- "@dxos/react-ui-trace": "0.13.0",
287
- "@dxos/schema": "0.13.0",
288
- "@dxos/react-ui-transcription": "0.13.0",
289
- "@dxos/types": "0.13.0",
290
- "@dxos/ui": "0.13.0",
291
- "@dxos/ui-types": "0.13.0",
292
- "@dxos/util": "0.13.0",
293
- "@dxos/ui-editor": "0.13.0",
294
- "@dxos/react-ui-experimental": "0.13.0",
295
- "@dxos/react-ui-list": "0.13.0"
240
+ "@dxos/agent-runtime": "0.14.0",
241
+ "@dxos/ai": "0.14.0",
242
+ "@dxos/app-framework": "0.14.0",
243
+ "@dxos/app-graph": "0.14.0",
244
+ "@dxos/app-toolkit": "0.14.0",
245
+ "@dxos/assistant": "0.14.0",
246
+ "@dxos/assistant-toolkit": "0.14.0",
247
+ "@dxos/async": "0.14.0",
248
+ "@dxos/client": "0.14.0",
249
+ "@dxos/compute": "0.14.0",
250
+ "@dxos/compute-runtime": "0.14.0",
251
+ "@dxos/conductor": "0.14.0",
252
+ "@dxos/devtools": "0.14.0",
253
+ "@dxos/config": "0.14.0",
254
+ "@dxos/echo": "0.14.0",
255
+ "@dxos/echo-react": "0.14.0",
256
+ "@dxos/edge-client": "0.14.0",
257
+ "@dxos/effect": "0.14.0",
258
+ "@dxos/errors": "0.14.0",
259
+ "@dxos/graph": "0.14.0",
260
+ "@dxos/invariant": "0.14.0",
261
+ "@dxos/keys": "0.14.0",
262
+ "@dxos/link": "0.14.0",
263
+ "@dxos/log": "0.14.0",
264
+ "@dxos/mcp-client": "0.14.0",
265
+ "@dxos/observability": "0.14.0",
266
+ "@dxos/echo-client": "0.14.0",
267
+ "@dxos/plugin-attention": "0.14.0",
268
+ "@dxos/plugin-client": "0.14.0",
269
+ "@dxos/plugin-connector": "0.14.0",
270
+ "@dxos/halo-react": "0.14.0",
271
+ "@dxos/plugin-graph": "0.14.0",
272
+ "@dxos/plugin-markdown": "0.14.0",
273
+ "@dxos/plugin-registry": "0.14.0",
274
+ "@dxos/plugin-routine": "0.14.0",
275
+ "@dxos/plugin-space": "0.14.0",
276
+ "@dxos/plugin-status-bar": "0.14.0",
277
+ "@dxos/plugin-tasks": "0.14.0",
278
+ "@dxos/plugin-transcription": "0.14.0",
279
+ "@dxos/plugin-deck": "0.14.0",
280
+ "@dxos/react-client": "0.14.0",
281
+ "@dxos/plugin-observability": "0.14.0",
282
+ "@dxos/react-hooks": "0.14.0",
283
+ "@dxos/react-list": "0.14.0",
284
+ "@dxos/react-ui-assistant": "0.14.0",
285
+ "@dxos/react-ui-attention": "0.14.0",
286
+ "@dxos/react-ui-feed": "0.14.0",
287
+ "@dxos/react-ui-menu": "0.14.0",
288
+ "@dxos/react-ui-form": "0.14.0",
289
+ "@dxos/react-ui-chat": "0.14.0",
290
+ "@dxos/react-ui-list": "0.14.0",
291
+ "@dxos/react-ui-task": "0.14.0",
292
+ "@dxos/react-ui-trace": "0.14.0",
293
+ "@dxos/react-ui-transcription": "0.14.0",
294
+ "@dxos/schema": "0.14.0",
295
+ "@dxos/react-ui-search": "0.14.0",
296
+ "@dxos/types": "0.14.0",
297
+ "@dxos/ui": "0.14.0",
298
+ "@dxos/ui-editor": "0.14.0",
299
+ "@dxos/util": "0.14.0",
300
+ "@dxos/ui-types": "0.14.0",
301
+ "@dxos/react-ui-experimental": "0.14.0",
302
+ "@dxos/react-ui-components": "0.14.0"
296
303
  },
297
304
  "devDependencies": {
298
305
  "@storybook/react-vite": "^10.6.0",
@@ -303,30 +310,32 @@
303
310
  "react": "~19.2.7",
304
311
  "react-dom": "~19.2.7",
305
312
  "vite": "^8.2.2",
306
- "@dxos/assistant-toolkit": "0.13.0",
307
- "@dxos/devtools": "0.13.0",
308
- "@dxos/plugin-client": "0.13.0",
309
- "@dxos/plugin-chess": "0.13.0",
310
- "@dxos/plugin-map": "0.13.0",
311
- "@dxos/plugin-preview": "0.13.0",
312
- "@dxos/plugin-table": "0.13.0",
313
- "@dxos/echo-client": "0.13.0",
314
- "@dxos/plugin-testing": "0.13.0",
315
- "@dxos/random": "0.13.0",
316
- "@dxos/react-ui-mosaic": "0.13.0",
317
- "@dxos/react-ui-editor": "0.13.0",
318
- "@dxos/react-ui": "0.13.0",
319
- "@dxos/react-ui-menu": "0.13.0",
320
- "@dxos/react-ui-syntax-highlighter": "0.13.0",
321
- "@dxos/ui-theme": "0.13.0"
313
+ "@dxos/assistant-toolkit": "0.14.0",
314
+ "@dxos/devtools": "0.14.0",
315
+ "@dxos/echo-client": "0.14.0",
316
+ "@dxos/edge-compute": "0.14.0",
317
+ "@dxos/plugin-chess": "0.14.0",
318
+ "@dxos/plugin-client": "0.14.0",
319
+ "@dxos/plugin-table": "0.14.0",
320
+ "@dxos/plugin-map": "0.14.0",
321
+ "@dxos/protocols": "0.14.0",
322
+ "@dxos/plugin-testing": "0.14.0",
323
+ "@dxos/react-ui": "0.14.0",
324
+ "@dxos/react-ui-editor": "0.14.0",
325
+ "@dxos/react-ui-menu": "0.14.0",
326
+ "@dxos/react-ui-mosaic": "0.14.0",
327
+ "@dxos/react-ui-syntax-highlighter": "0.14.0",
328
+ "@dxos/ui-theme": "0.14.0",
329
+ "@dxos/plugin-preview": "0.14.0",
330
+ "@dxos/random": "0.14.0"
322
331
  },
323
332
  "peerDependencies": {
324
333
  "@effect/atom-react": "4.0.0",
325
334
  "effect": "4.0.0",
326
335
  "react": "~19.2.7",
327
336
  "react-dom": "~19.2.7",
328
- "@dxos/react-ui": "^0.13.0",
329
- "@dxos/ui-theme": "^0.13.0"
337
+ "@dxos/react-ui": "^0.14.0",
338
+ "@dxos/ui-theme": "^0.14.0"
330
339
  },
331
340
  "publishConfig": {
332
341
  "access": "public"
@@ -13,7 +13,7 @@ import * as LayerSpec from '@dxos/compute/LayerSpec';
13
13
  import * as Process from '@dxos/compute/Process';
14
14
  import * as RoutineCapabilities from '@dxos/plugin-routine/RoutineCapabilities';
15
15
 
16
- import { AssistantOptions } from '#types';
16
+ import { AssistantCapabilities, AssistantOptions } from '#types';
17
17
 
18
18
  import { resolveTurnProducer } from './turn-producers.ts';
19
19
 
@@ -42,6 +42,7 @@ const makeAgentServiceSpec = (codeModeTurnProducer: AssistantOptions.AssistantPl
42
42
  return AgentServiceRuntime.layer({
43
43
  delegationStrategy: strategies[0],
44
44
  makeTurnProducer: (options) => resolveTurnProducer(manager, codeMode)(options),
45
+ processes: () => manager.getAll(AssistantCapabilities.AgentProcess),
45
46
  });
46
47
  }),
47
48
  ),
@@ -22,7 +22,7 @@ import * as RegistryPlugin from '@dxos/plugin-registry/RegistryPlugin';
22
22
  import * as RoutineCapabilities from '@dxos/plugin-routine/RoutineCapabilities';
23
23
  import * as DatabaseSkill from '@dxos/plugin-space/DatabaseSkill';
24
24
 
25
- import { AssistantSkill, PluginManagerSkill } from '#skills';
25
+ import { AssistantSkill, PluginManagerSkill, TurnReviewSkill } from '#skills';
26
26
 
27
27
  const skillDefinition = Effect.fnUntraced(function* () {
28
28
  const manager = yield* Plugin.Service;
@@ -48,6 +48,7 @@ const skillDefinition = Effect.fnUntraced(function* () {
48
48
  SkillManagerSkill,
49
49
  DelegationSkill,
50
50
  AlarmSkill,
51
+ TurnReviewSkill,
51
52
  ]),
52
53
 
53
54
  // Run the conversational agent as a supervisor: delegate in-progress plan tasks to sub-agents
@@ -0,0 +1,301 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Layer from 'effect/Layer';
7
+ import * as KeyValueStore from 'effect/persistence/KeyValueStore';
8
+ import * as AtomRegistry from 'effect/reactivity/AtomRegistry';
9
+ import * as Result from 'effect/Result';
10
+ import * as Stream from 'effect/Stream';
11
+ import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest';
12
+
13
+ import { AgentService as AgentServiceRuntime } from '@dxos/agent-runtime';
14
+ import { AiService, Model, OpaqueToolkit, Provider } from '@dxos/ai';
15
+ import { AiSession } from '@dxos/assistant';
16
+ import * as Chat from '@dxos/assistant/Chat';
17
+ import { Client } from '@dxos/client';
18
+ import { type Space } from '@dxos/client/echo';
19
+ import {
20
+ ProcessManager,
21
+ ProcessOperationInvoker,
22
+ RemoteTraceMonitor,
23
+ UnifiedProcessManager,
24
+ configuredCredentialsLayer,
25
+ } from '@dxos/compute-runtime';
26
+ import * as AgentService from '@dxos/compute/AgentService';
27
+ import * as OperationHandlerSet from '@dxos/compute/OperationHandlerSet';
28
+ import * as ServiceResolver from '@dxos/compute/ServiceResolver';
29
+ import * as Trace from '@dxos/compute/Trace';
30
+ import { configPreset } from '@dxos/config';
31
+ import { Database, Feed, Ref, Registry } from '@dxos/echo';
32
+ import { registryLayer } from '@dxos/echo-client';
33
+ import { EdgeHttpClient } from '@dxos/edge-client';
34
+ import { EdgeProcessManager } from '@dxos/edge-compute';
35
+ import * as EffectEx from '@dxos/effect/EffectEx';
36
+ import { DXN } from '@dxos/keys';
37
+ import { toPublicKey } from '@dxos/protocols/buf';
38
+ import { EdgeReplicationSetting } from '@dxos/protocols/buf/dxos/echo/metadata_pb';
39
+ import { Message } from '@dxos/types';
40
+
41
+ import { ChatModel } from './chat-model.ts';
42
+ import { providerForModel } from './presets.ts';
43
+ import { ChatModelRecorder } from './testing/chat-model-recorder.ts';
44
+ import { makeSpaceLayer, makeTestRuntime } from './testing/harness.ts';
45
+
46
+ /**
47
+ * The chat model against an EDGE-hosted agent and a real model: the same indicator log as the scripted
48
+ * suite, plus the latency of each stage of a turn, so a slow or silent hosted turn shows WHERE it is slow.
49
+ *
50
+ * The stack is the one Composer builds for a `remote` chat — the durable command queue in front of EDGE
51
+ * process control, and the swarm monitor for the agent's live trace — so what this measures is what the
52
+ * app sees.
53
+ *
54
+ * Run (one or more environments, comma-separated; `local` is `moon run edge:dev` in the edge repo):
55
+ * DX_RUN_MANUAL_TESTS=1 DX_EDGE_ENVS=local,dev moon run plugin-assistant:test -- src/chat-model/chat-model.edge.test.ts
56
+ * Optional: DX_CHAT_MODEL (a model DXN; defaults to Claude Haiku 4.5), DX_CHAT_TURNS (prompts per chat).
57
+ */
58
+
59
+ type EdgeEnv = 'local' | 'dev' | 'preview';
60
+
61
+ const ENVS = (process.env.DX_EDGE_ENVS ?? 'local').split(',').map((env) => env.trim()) as EdgeEnv[];
62
+ const MODEL = process.env.DX_CHAT_MODEL ? DXN.make(process.env.DX_CHAT_MODEL) : Model.claudeHaiku45.id;
63
+
64
+ const PROMPTS = [
65
+ 'Reply with exactly one word: pong',
66
+ 'Count from one to twenty in words, separated by commas.',
67
+ 'Now answer with one word: done',
68
+ ].slice(0, Number.parseInt(process.env.DX_CHAT_TURNS ?? '', 10) || 3);
69
+
70
+ /** The stages of a turn, each the first moment (ms after send) at which the reader could see it. */
71
+ type TurnTiming = {
72
+ prompt: string;
73
+ /** The chat reads as working (stop control, activity line). */
74
+ active?: number;
75
+ /** The agent process reports RUNNING (the host has taken the input). */
76
+ agentRunning?: number;
77
+ /** First phase the agent itself reported (anything past the client's own `starting`). */
78
+ firstAgentPhase?: number;
79
+ /** The provider was contacted. */
80
+ contactingProvider?: number;
81
+ /** First streamed content. */
82
+ firstToken?: number;
83
+ /** The prompt row ticks to read (the agent's queue took it). */
84
+ promptRead?: number;
85
+ /** The reply is in the thread from the feed (replicated back from EDGE). */
86
+ replyInThread?: number;
87
+ /** The chat reads idle again. */
88
+ settled?: number;
89
+ phases: string[];
90
+ /** EDGE process-control calls made during the turn, as `verb start→end` (ms after send). */
91
+ calls: string[];
92
+ };
93
+
94
+ /** EDGE process-control calls, timed, so a slow turn can be attributed to the round trip that made it slow. */
95
+ type EdgeCall = { verb: string; start: number; end?: number };
96
+
97
+ const PROCESS_VERBS = [
98
+ 'spawnProcess',
99
+ 'listProcesses',
100
+ 'getProcess',
101
+ 'submitProcessInput',
102
+ 'readProcessEvents',
103
+ 'terminateProcess',
104
+ ] as const;
105
+
106
+ const traceEdgeCalls = (calls: EdgeCall[]) => {
107
+ for (const verb of PROCESS_VERBS) {
108
+ const original = EdgeHttpClient.prototype[verb];
109
+ vi.spyOn(EdgeHttpClient.prototype, verb).mockImplementation(function (
110
+ this: EdgeHttpClient,
111
+ ...args: Parameters<typeof original>
112
+ ) {
113
+ const call: EdgeCall = { verb, start: performance.now() };
114
+ calls.push(call);
115
+ return (original as (...args: unknown[]) => Promise<unknown>).apply(this, args).finally(() => {
116
+ call.end = performance.now();
117
+ });
118
+ } as typeof original);
119
+ }
120
+ };
121
+
122
+ describe.each(ENVS)('ChatModel (edge-hosted agent, %s)', { tags: ['manual'], timeout: 600_000 }, (env) => {
123
+ let client: Client;
124
+ let space: Space;
125
+ const calls: EdgeCall[] = [];
126
+
127
+ beforeAll(async () => {
128
+ client = await new Client({
129
+ config: configPreset({ edge: env }),
130
+ types: [Chat.Chat, Feed.Feed, Message.Message],
131
+ }).initialize();
132
+ await client.halo.createIdentity();
133
+ space = await client.spaces.create();
134
+ await space.waitUntilReady();
135
+ await space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED);
136
+ traceEdgeCalls(calls);
137
+ }, 120_000);
138
+
139
+ afterAll(async () => {
140
+ vi.restoreAllMocks();
141
+ await client?.destroy();
142
+ });
143
+
144
+ test('every turn keeps its indicators up while the hosted agent works', async () => {
145
+ const timings = await EffectEx.runPromise(
146
+ runTurns(client, space, calls).pipe(Effect.provide(stack(client, space))),
147
+ );
148
+
149
+ console.log(`\nchat-model latency on ${env} (${DXN.getName(MODEL)}), ms after send:`);
150
+ console.table(
151
+ timings.map(({ phases: _phases, calls: _calls, prompt, ...stages }) => ({
152
+ prompt: prompt.slice(0, 24),
153
+ ...stages,
154
+ })),
155
+ );
156
+ for (const timing of timings) {
157
+ console.log(` phases: ${timing.phases.join(' → ')}`);
158
+ console.log(` calls: ${timing.calls.join(', ')}`);
159
+ }
160
+ });
161
+ });
162
+
163
+ const runTurns = (client: Client, space: Space, calls: EdgeCall[]) =>
164
+ Effect.gen(function* () {
165
+ const feed = yield* Database.add(Feed.make());
166
+ // The model is the chat's own from the start: one stamped later reads as a reconfiguration, which
167
+ // replaces the hosted process.
168
+ const chat = yield* Database.add(
169
+ Chat.make({ name: 'Latency', feed: Ref.make(feed), remote: true, session: { model: MODEL } }),
170
+ );
171
+ yield* Database.flush();
172
+ // The hosted process reads the chat from EDGE's copy of the space.
173
+ yield* Effect.promise(() => space.internal.syncToEdge());
174
+
175
+ const runtime = yield* Effect.context<Database.Service>();
176
+ const session = yield* EffectEx.acquireReleaseResource(() => new AiSession.Session({ feed, runtime }));
177
+ const registry = yield* AtomRegistry.AtomRegistry;
178
+ const chatModel = new ChatModel(session, yield* makeTestRuntime, feed, yield* makeSpaceLayer, {
179
+ chat: Ref.make(chat),
180
+ model: MODEL,
181
+ provider: Provider.edge.id,
182
+ observableRegistry: registry,
183
+ });
184
+
185
+ const timings: TurnTiming[] = [];
186
+ for (const prompt of PROMPTS) {
187
+ // Resolved per turn, with the chat model's own options: a hosted process that finished its turn is
188
+ // replaced, and the replacement's status is the one to watch.
189
+ const agent = yield* AgentService.getSession(chat, {
190
+ provider: providerForModel(MODEL, Provider.edge.id),
191
+ location: 'edge',
192
+ });
193
+ const recorder = new ChatModelRecorder(registry, chatModel, { agentRunning: agent.running });
194
+ const turnRows = rows(recorder.state.thread);
195
+ const sentAt = performance.now();
196
+ const firstCall = calls.length;
197
+ chatModel.send({ message: prompt });
198
+ yield* Effect.promise(() => recorder.untilSettled(300_000));
199
+ // The reply lands in the thread from the feed, which may trail the settle.
200
+ yield* Effect.promise(() =>
201
+ recorder.until((state) => rows(state.thread) >= turnRows + 2, 60_000).catch(() => undefined),
202
+ );
203
+ recorder.dispose();
204
+
205
+ expect(recorder.state.error).toBe('-');
206
+ // A hosted turn that streams nothing is the defect this suite exists for, not a slow turn.
207
+ expect(recorder.firstAt('streaming', 'true')).toBeDefined();
208
+ // Ignore the client's own idle reading before the first event of the turn.
209
+ expect(recorder.violations.filter((violation) => violation.at > 0)).toEqual([]);
210
+ timings.push({
211
+ ...timingOf(prompt, recorder),
212
+ calls: calls
213
+ .slice(firstCall)
214
+ .filter(({ verb }) => verb !== 'readProcessEvents' && verb !== 'getProcess')
215
+ .map(({ verb, start, end }) => {
216
+ const at = (time: number | undefined) => (time === undefined ? '…' : Math.round(time - sentAt));
217
+ return `${verb.replace('Process', '')} ${at(start)}→${at(end)}`;
218
+ }),
219
+ });
220
+ }
221
+ return timings;
222
+ }).pipe(Effect.scoped);
223
+
224
+ /** Rows in a recorded thread (the prompt and the reply each add one). */
225
+ const rows = (thread: string) => (thread === '-' ? 0 : thread.split(' | ').length);
226
+
227
+ const timingOf = (prompt: string, recorder: ChatModelRecorder): Omit<TurnTiming, 'calls'> => {
228
+ const agentPhase = (value: string) => value !== '-' && value !== 'starting' && value !== 'generating';
229
+ const threadRows = recorder.events.filter((event) => event.signal === 'thread');
230
+ return {
231
+ prompt,
232
+ active: recorder.firstAt('active', 'true'),
233
+ agentRunning: recorder.firstAt('agent', 'running'),
234
+ firstAgentPhase: recorder.firstAt('activity', agentPhase),
235
+ contactingProvider: recorder.firstAt('activity', (value) => value.startsWith('contacting-provider')),
236
+ firstToken: recorder.firstAt('streaming', 'true'),
237
+ promptRead: threadRows.find((event) => event.value.includes(`[read]:"${prompt.slice(0, 24)}`))?.at,
238
+ replyInThread: threadRows.at(-1)?.at,
239
+ settled: recorder.events.findLast((event) => event.signal === 'active' && event.value === 'false')?.at,
240
+ phases: recorder.values('activity').filter((value) => value !== ''),
241
+ };
242
+ };
243
+
244
+ /**
245
+ * What Composer provides a `remote` chat: the local process manager, EDGE's behind the durable command
246
+ * queue, the swarm-backed live trace, and `AgentService` over both.
247
+ */
248
+ const stack = (client: Client, space: Space) =>
249
+ Layer.empty.pipe(
250
+ Layer.provideMerge(ProcessOperationInvoker.layer),
251
+ Layer.provideMerge(AgentServiceRuntime.layer({ defaultModel: MODEL, provider: Provider.edge.id })),
252
+ Layer.provideMerge(UnifiedProcessManager.layer),
253
+ Layer.provideMerge(
254
+ Layer.unwrap(
255
+ Effect.gen(function* () {
256
+ const kvStore = yield* KeyValueStore.KeyValueStore;
257
+ return EdgeProcessManager.fromClient(client, { kvStore });
258
+ }),
259
+ ),
260
+ ),
261
+ Layer.provideMerge(Layer.succeed(RemoteTraceMonitor.Service, swarmMonitor(client))),
262
+ Layer.provideMerge(ProcessManager.layer()),
263
+ Layer.provideMerge(
264
+ ServiceResolver.layerRequirements(
265
+ Database.Service,
266
+ OpaqueToolkit.OpaqueToolkitProvider,
267
+ AiService.AiService,
268
+ Registry.Service,
269
+ ),
270
+ ),
271
+ Layer.provideMerge(OperationHandlerSet.provide(OperationHandlerSet.empty)),
272
+ Layer.provideMerge(Trace.layerNoop),
273
+ Layer.provideMerge(AiService.notAvailable),
274
+ Layer.provideMerge(OpaqueToolkit.providerEmpty),
275
+ Layer.provideMerge(configuredCredentialsLayer([])),
276
+ Layer.provideMerge(registryLayer()),
277
+ Layer.provideMerge(KeyValueStore.layerMemory),
278
+ Layer.provideMerge(AtomRegistry.layer),
279
+ Layer.provideMerge(Database.layer(space.db)),
280
+ );
281
+
282
+ /** The swarm-backed live trace, as plugin-client contributes it. */
283
+ const swarmMonitor = (client: Client): RemoteTraceMonitor.Monitor =>
284
+ RemoteTraceMonitor.createSwarmRemoteTraceMonitor({
285
+ subscribe: (tags) =>
286
+ tags.length === 0
287
+ ? Stream.empty
288
+ : Stream.unwrap(
289
+ Effect.sync(() => {
290
+ const peer = { peerKey: toPublicKey(client.halo.device?.deviceKey)?.toHex() ?? '' };
291
+ return client.services.rpc['NetworkService.subscribeMessages']({ peer, tags }).pipe(
292
+ Stream.filterMap((message) =>
293
+ message.payload?.value
294
+ ? Result.succeed({ payload: message.payload.value, tags: message.tags })
295
+ : Result.failVoid,
296
+ ),
297
+ Stream.catch(() => Stream.empty),
298
+ );
299
+ }),
300
+ ),
301
+ });
@@ -22,12 +22,12 @@ import { TestHelpers } from '@dxos/effect/testing';
22
22
  import { DXN } from '@dxos/keys';
23
23
  import { Message } from '@dxos/types';
24
24
 
25
- import { AiChatProcessor, AiUsageQuotaError, parseError } from './processor.ts';
25
+ import { AiUsageQuotaError, ChatModel, parseError } from './chat-model.ts';
26
26
  import { type ThreadProjection } from './thread.ts';
27
27
 
28
28
  const TestLayer = AssistantTestLayer({ tracing: 'noop', types: [Chat.Chat, Feed.Feed, Message.Message] });
29
29
 
30
- describe('Chat processor', () => {
30
+ describe('ChatModel', () => {
31
31
  it.effect(
32
32
  'basic',
33
33
  Effect.fn(
@@ -38,19 +38,19 @@ describe('Chat processor', () => {
38
38
  const session = yield* EffectEx.acquireReleaseResource(() => new AiSession.Session({ feed, runtime }));
39
39
  const managedRuntime = ManagedRuntime.make(Layer.empty) as unknown as Capabilities.ProcessManagerRuntime;
40
40
  const registry = AtomRegistry.make();
41
- const processor = new AiChatProcessor(session, managedRuntime, feed, Layer.empty as any, {
41
+ const chatModel = new ChatModel(session, managedRuntime, feed, Layer.empty as any, {
42
42
  observableRegistry: registry,
43
43
  });
44
- expect(processor).toBeDefined();
45
- expect(processor.active).toBeDefined();
44
+ expect(chatModel).toBeDefined();
45
+ expect(chatModel.active).toBeDefined();
46
46
 
47
- // The thread is the processor's one view: a feed append and a sent prompt both arrive through it.
47
+ // The thread is the chat model's one view: a feed append and a sent prompt both arrive through it.
48
48
  const rows = (predicate: (thread: ThreadProjection) => boolean) =>
49
49
  Effect.promise(
50
50
  () =>
51
51
  new Promise<ThreadProjection>((resolve) => {
52
52
  const unsubscribe = registry.subscribe(
53
- processor.thread,
53
+ chatModel.thread,
54
54
  (thread) => {
55
55
  if (predicate(thread)) {
56
56
  queueMicrotask(() => unsubscribe());
@@ -68,8 +68,8 @@ describe('Chat processor', () => {
68
68
  const appended = yield* rows((thread) => thread.messages.length === 1);
69
69
  expect(appended.tail).toBe(0);
70
70
 
71
- const id = processor.send({ message: 'hello' });
72
- const sent = registry.get(processor.thread);
71
+ const id = chatModel.send({ message: 'hello' });
72
+ const sent = registry.get(chatModel.thread);
73
73
  expect(sent.messages.map((message) => message.id)).toEqual([appended.messages[0].id, id]);
74
74
  expect(sent.delivery.get(id)?.outboxId).toBe(id);
75
75
  expect(sent.tail).toBe(1);
@@ -159,6 +159,16 @@ describe('parseError', () => {
159
159
  expect(parseError(err).message).toBe('Connection refused');
160
160
  });
161
161
 
162
+ // A provider failure inside the agent process reaches the chat as the process's stringified failure,
163
+ // so its typed reason is gone but the provider's own description is still in the text.
164
+ test('passes through the detail of an AiError stringified by the process boundary', ({ expect }) => {
165
+ const err = new Error('Process failed', {
166
+ cause:
167
+ 'effect/ai/AiError/AiError: AnthropicClient.streamText: Invalid request. prompt is too long\n at stack (file.ts:1:1)',
168
+ });
169
+ expect(parseError(err).message).toBe('Invalid request. prompt is too long');
170
+ });
171
+
162
172
  test('falls back to a generic message for unrecognized errors', ({ expect }) => {
163
173
  expect(parseError('something unexpected blew up').message).toBe('An unexpected error occurred.');
164
174
  expect(parseError(new Error('boom')).message).toBe('An unexpected error occurred.');