@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
@@ -0,0 +1,81 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Context from 'effect/Context';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Layer from 'effect/Layer';
8
+ import * as ManagedRuntime from 'effect/ManagedRuntime';
9
+ import type * as Scope from 'effect/Scope';
10
+
11
+ import { AiService, OpaqueToolkit } from '@dxos/ai';
12
+ import type * as Capabilities from '@dxos/app-framework/Capabilities';
13
+ import * as Capability from '@dxos/app-framework/Capability';
14
+ import * as Plugin from '@dxos/app-framework/Plugin';
15
+ import * as PluginManager from '@dxos/app-framework/PluginManager';
16
+ import { ProcessManager } from '@dxos/compute-runtime';
17
+ import * as AgentService from '@dxos/compute/AgentService';
18
+ import * as Credential from '@dxos/compute/Credential';
19
+ import * as Operation from '@dxos/compute/Operation';
20
+ import * as ServiceResolver from '@dxos/compute/ServiceResolver';
21
+ import { Database, Registry } from '@dxos/echo';
22
+
23
+ import { type SpaceServices } from '../chat-model.ts';
24
+
25
+ type BaseSpaceServices = Exclude<SpaceServices, AgentService.AgentService>;
26
+
27
+ /** The ambient services a chat model needs besides the agent service. */
28
+ const pickBaseServices = Effect.map(Effect.context<BaseSpaceServices>(), (services) =>
29
+ Layer.succeedContext(
30
+ Context.pick(
31
+ Database.Service,
32
+ Credential.CredentialsService,
33
+ AiService.AiService,
34
+ Registry.Service,
35
+ OpaqueToolkit.OpaqueToolkitProvider,
36
+ )(services),
37
+ ),
38
+ );
39
+
40
+ /** The chat model's space layer over the ambient services, including the real agent service. */
41
+ export const makeSpaceLayer: Effect.Effect<Layer.Layer<SpaceServices>, never, SpaceServices> = Effect.gen(function* () {
42
+ const base = yield* pickBaseServices;
43
+ const agentService = yield* AgentService.AgentService;
44
+ return Layer.mergeAll(base, Layer.succeed(AgentService.AgentService, agentService));
45
+ });
46
+
47
+ /** The chat model's space layer with a stub agent service, to script the trace stream directly. */
48
+ export const makeStubSpaceLayer = (
49
+ agentService: AgentService.Service,
50
+ ): Effect.Effect<Layer.Layer<SpaceServices>, never, BaseSpaceServices> =>
51
+ Effect.map(pickBaseServices, (base) => Layer.mergeAll(base, Layer.succeed(AgentService.AgentService, agentService)));
52
+
53
+ /**
54
+ * A sound {@link Capabilities.ProcessManagerRuntime} for tests: a `ManagedRuntime` over the
55
+ * assistant test layer's process services plus a bare `PluginManager` for the capability/plugin
56
+ * tags. Scoped so the runtime is disposed with the test.
57
+ */
58
+ export const makeTestRuntime: Effect.Effect<
59
+ Capabilities.ProcessManagerRuntime,
60
+ never,
61
+ ProcessManager.Service | Operation.Service | ServiceResolver.ServiceResolver | Scope.Scope
62
+ > = Effect.gen(function* () {
63
+ const services = yield* Effect.context<
64
+ ProcessManager.Service | Operation.Service | ServiceResolver.ServiceResolver
65
+ >();
66
+ const manager = PluginManager.make({
67
+ pluginLoader: (id: string) => Effect.die(new Error(`No plugins in test runtime: ${id}`)),
68
+ plugins: [],
69
+ });
70
+ const runtime: Capabilities.ProcessManagerRuntime = ManagedRuntime.make(
71
+ Layer.mergeAll(
72
+ Layer.succeedContext(services),
73
+ Layer.succeed(Capability.Service, manager.capabilities),
74
+ Layer.succeed(Plugin.Service, manager),
75
+ ),
76
+ );
77
+ yield* Effect.addFinalizer(() =>
78
+ Effect.promise(() => (runtime as ManagedRuntime.ManagedRuntime<never, never>).dispose()),
79
+ );
80
+ return runtime;
81
+ });
@@ -578,7 +578,7 @@ const sortText = (messages: Message.Message[]) => text(Array.sort(messages, byAp
578
578
 
579
579
  const statusOf = (message: Message.Message) => getDelivery(message);
580
580
 
581
- /** A prompt as `AiChatProcessor.send` holds it, submitted while the feed held `known`. */
581
+ /** A prompt as `ChatModel.send` holds it, submitted while the feed held `known`. */
582
582
  const outboxEntry = (text: string, known: readonly Message.Message[]): OutboxEntry => ({
583
583
  id: Obj.ID.random(),
584
584
  created: new Date(clock++).toISOString(),
@@ -35,6 +35,7 @@ import {
35
35
  import { ActionToolbar, type ActionToolbarProps, createMenuAction } from '@dxos/react-ui-menu';
36
36
  import { TaskList, TaskQuestion } from '@dxos/react-ui-task';
37
37
  import * as UiHooks from '@dxos/react-ui/Hooks';
38
+ import * as Layout from '@dxos/react-ui/Layout';
38
39
  import * as Toast from '@dxos/react-ui/Toast';
39
40
  import * as Util from '@dxos/react-ui/Util';
40
41
  import { Message, Task } from '@dxos/types';
@@ -44,15 +45,15 @@ import { type ChatSwitcher, useChatToolbarActions, useDebug } from '#hooks';
44
45
  import { meta } from '#meta';
45
46
  import { AssistantOperation } from '#types';
46
47
 
47
- import { TaskSlashCommands } from '../../commands/index.ts';
48
48
  import {
49
49
  AiUsageQuotaError,
50
- type ProcessorRequestContext,
51
- getProcessorState,
50
+ type ChatRequestContext,
51
+ getChatModelState,
52
52
  projectAlarms,
53
53
  projectSelfWakes,
54
54
  resolveRewind,
55
- } from '../../processor/index.ts';
55
+ } from '../../chat-model/index.ts';
56
+ import { TaskSlashCommands } from '../../commands/index.ts';
56
57
  import {
57
58
  ChatStatus,
58
59
  DEFAULT_MAX_QUEUE,
@@ -78,7 +79,7 @@ import { SurfaceWidget } from './SurfaceWidget.tsx';
78
79
  //
79
80
 
80
81
  type ChatRootProps = PropsWithChildren<
81
- Pick<ChatContextValue, 'chat' | 'processor' | 'debug'> & {
82
+ Pick<ChatContextValue, 'chat' | 'chatModel' | 'debug'> & {
82
83
  /** Fallback database when the chat is transient (not yet persisted). */
83
84
  db?: Database.Database;
84
85
  onEvent?: (event: ChatEvent) => void;
@@ -88,14 +89,14 @@ type ChatRootProps = PropsWithChildren<
88
89
  */
89
90
  onSubmit?: (text: string) => Promise<void> | void;
90
91
  /** Called at submit time to capture ephemeral request context (e.g. companion selection). */
91
- getContext?: () => ProcessorRequestContext | undefined;
92
+ getContext?: () => ChatRequestContext | undefined;
92
93
  }
93
94
  >;
94
95
 
95
96
  const ChatRoot = ({
96
97
  children,
97
98
  chat,
98
- processor,
99
+ chatModel,
99
100
  db: dbFallback,
100
101
  debug: debugProp,
101
102
  onEvent,
@@ -106,12 +107,12 @@ const ChatRoot = ({
106
107
  const [debug, setDebug] = useState(debugProp ?? false);
107
108
  // Slash commands run their operations through the same invoker the rest of the UI uses.
108
109
  const { invokePromise } = Hooks.useOperationInvoker();
109
- const processorState = getProcessorState(processor);
110
- const streaming = useAtomValue(processorState.streaming);
111
- const active = useAtomValue(processorState.active);
110
+ const chatModelState = getChatModelState(chatModel);
111
+ const streaming = useAtomValue(chatModelState.streaming);
112
+ const active = useAtomValue(chatModelState.active);
112
113
  const requestTiming = useRequestTiming({ active });
113
114
  const lastPrompt = useRef<string | undefined>(undefined);
114
- // A slash command runs outside the processor, so `streaming` does not cover it.
115
+ // A slash command runs outside the chat model, so `streaming` does not cover it.
115
116
  const commandPending = useRef(false);
116
117
  // Transient chats have no database of their own; fall back to the supplied space db so
117
118
  // the message query and context controls operate before the chat is persisted.
@@ -133,15 +134,15 @@ const ChatRoot = ({
133
134
  db,
134
135
  feed ? Query.select(Filter.type(Alarm.Alarm)).from(feed) : Query.select(Filter.nothing()),
135
136
  );
136
- // The processor queries the feed and reconciles it with the streamed turn and the outbox.
137
- const { messages, delivery, queued, tail } = useAtomValue(processorState.thread);
137
+ // The chat model queries the feed and reconciles it with the streamed turn and the outbox.
138
+ const { messages, delivery, queued, tail } = useAtomValue(chatModelState.thread);
138
139
  const alarms = useMemo(() => projectAlarms({ feedAlarms }), [feedAlarms]);
139
140
  const selfWakes = useMemo(() => projectSelfWakes({ feedAlarms, messages }), [feedAlarms, messages]);
140
141
 
141
- const dump = useDebug({ processor });
142
+ const dump = useDebug({ chatModel });
142
143
 
143
- // Surface processor failures (e.g., AI service unavailable) to subscribers via the event bus.
144
- const error = useAtomValue(processorState.error);
144
+ // Surface chat model failures (e.g., AI service unavailable) to subscribers via the event bus.
145
+ const error = useAtomValue(chatModelState.error);
145
146
  useEffect(() => {
146
147
  if (Option.isSome(error)) {
147
148
  event.emit({ type: 'error', error: error.value });
@@ -188,7 +189,7 @@ const ChatRoot = ({
188
189
  void db.removeFeedItemsByIds(feed, [row.entryId]).catch((err) => log.catch(err));
189
190
  }
190
191
  if (row.outboxId) {
191
- processor?.removePrompt(row.outboxId);
192
+ chatModel?.removePrompt(row.outboxId);
192
193
  }
193
194
  }
194
195
  break;
@@ -199,7 +200,7 @@ const ChatRoot = ({
199
200
  });
200
201
  // `feed`, `messages` and `delivery` are dependencies because the rewind and prompt branches read
201
202
  // them: without them the handler would keep resolving against whatever was mounted first.
202
- }, [event, dump, onEvent, db, feed, messages, delivery, processor]);
203
+ }, [event, dump, onEvent, db, feed, messages, delivery, chatModel]);
203
204
 
204
205
  useEffect(() => {
205
206
  return event.on((ev) => {
@@ -222,7 +223,7 @@ const ChatRoot = ({
222
223
  }, [event, chat, invokePromise]);
223
224
 
224
225
  useEffect(() => {
225
- if (!processor) {
226
+ if (!chatModel) {
226
227
  return;
227
228
  }
228
229
 
@@ -275,7 +276,7 @@ const ChatRoot = ({
275
276
  if (result.followUp) {
276
277
  // Some effects run on the supervisor loop (delegation spawns post-turn), so the
277
278
  // command wakes the conversation with a short synthetic prompt.
278
- void processor.request({ message: result.followUp });
279
+ void chatModel.request({ message: result.followUp });
279
280
  }
280
281
  })()
281
282
  .catch((error) => {
@@ -293,9 +294,9 @@ const ChatRoot = ({
293
294
  markWork('chat.submit');
294
295
  lastPrompt.current = ev.text;
295
296
  // Optimistic: the prompt is in the thread from this call on, before `onSubmit` persists a
296
- // transient chat and before the agent acknowledges it. The processor requests it when the
297
+ // transient chat and before the agent acknowledges it. The chat model requests it when the
297
298
  // agent is idle and queues it behind a running turn otherwise.
298
- processor.send({ message: text, context: getContext?.() }, { prepare: () => onSubmit?.(text) });
299
+ chatModel.send({ message: text, context: getContext?.() }, { prepare: () => onSubmit?.(text) });
299
300
  }
300
301
  break;
301
302
  }
@@ -307,8 +308,8 @@ const ChatRoot = ({
307
308
  // content is synthetic — nobody typed it.
308
309
  void Promise.resolve(onSubmit?.(text)).then(() =>
309
310
  active
310
- ? processor.enqueue({ message: text, disposition: 'synthetic' })
311
- : processor.request({ message: text, disposition: 'synthetic' }),
311
+ ? chatModel.enqueue({ message: text, disposition: 'synthetic' })
312
+ : chatModel.request({ message: text, disposition: 'synthetic' }),
312
313
  );
313
314
  }
314
315
  break;
@@ -316,13 +317,13 @@ const ChatRoot = ({
316
317
 
317
318
  case 'retry': {
318
319
  if (!streaming) {
319
- void processor.retry();
320
+ void chatModel.retry();
320
321
  }
321
322
  break;
322
323
  }
323
324
 
324
325
  case 'cancel': {
325
- void processor.cancel();
326
+ void chatModel.cancel();
326
327
  if (streaming) {
327
328
  if (lastPrompt.current) {
328
329
  event.emit({ type: 'update-prompt', text: lastPrompt.current });
@@ -332,7 +333,7 @@ const ChatRoot = ({
332
333
  }
333
334
  }
334
335
  });
335
- }, [event, processor, streaming, active, queued, onSubmit, getContext, invokePromise, chat, db, feed]);
336
+ }, [event, chatModel, streaming, active, queued, onSubmit, getContext, invokePromise, chat, db, feed]);
336
337
 
337
338
  // An inline surface (connector prompt, plugin prompt) reports its completed flow as a synthetic
338
339
  // turn, so the agent resumes without the report reading as something the user typed.
@@ -344,8 +345,9 @@ const ChatRoot = ({
344
345
  event={event}
345
346
  db={db}
346
347
  chat={chat}
347
- processor={processor}
348
+ chatModel={chatModel}
348
349
  queueSize={queued}
350
+ started={messages.length > 0 || queued > 0}
349
351
  setController={setController}
350
352
  setVisibleRange={setVisibleRange}
351
353
  {...props}
@@ -537,7 +539,7 @@ type ChatThreadProps = Util.ThemedClassName<{
537
539
 
538
540
  const ChatThread = ({ classNames, viewType, tailLines, userHue: userHueProp, onViewUsage }: ChatThreadProps) => {
539
541
  const { t } = UiHooks.useTranslation(meta.profile.key);
540
- const { chat, db, debug, event, processor, setController, setVisibleRange } = useChatContext(CHAT_THREAD_NAME);
542
+ const { chat, db, debug, event, chatModel, setController, setVisibleRange } = useChatContext(CHAT_THREAD_NAME);
541
543
  const { messages, tail } = useChatThreadContext(CHAT_THREAD_NAME);
542
544
  const identity = useIdentity();
543
545
  // Embedded objects resolve against the chat's database (the fallback one while it is transient).
@@ -565,7 +567,7 @@ const ChatThread = ({ classNames, viewType, tailLines, userHue: userHueProp, onV
565
567
  // arrive as updates on one identity, and the window is told — the old `MessageSyncer`'s cursor
566
568
  // and range table have no equivalent left.
567
569
  const model = useFeedModel(messages, { stops: 'prompt' });
568
- const streaming = useAtomValue(getProcessorState(processor).streaming);
570
+ const streaming = useAtomValue(getChatModelState(chatModel).streaming);
569
571
  useEffect(() => {
570
572
  // The answer streams above any prompts still waiting for the agent, which end the thread.
571
573
  const last = messages[messages.length - 1 - tail];
@@ -717,7 +719,10 @@ ChatOutline.displayName = CHAT_OUTLINE_NAME;
717
719
 
718
720
  const CHAT_PROMPT_NAME = 'Chat.Prompt';
719
721
 
720
- type ChatPromptProps = Omit<NaturalChatPromptProps, 'chat' | 'db' | 'processor' | 'event' | 'tasksVisible'> & {
722
+ type ChatPromptProps = Omit<
723
+ NaturalChatPromptProps,
724
+ 'chat' | 'db' | 'chatModel' | 'event' | 'tasksVisible' | 'started'
725
+ > & {
721
726
  /** Whether the checklist is disclosed on mount. */
722
727
  defaultTasksVisible?: boolean;
723
728
  };
@@ -734,7 +739,7 @@ type ChatPromptProps = Omit<NaturalChatPromptProps, 'chat' | 'db' | 'processor'
734
739
  * event rather than being wrapped in a `Collapsible.Trigger` it cannot reach.
735
740
  */
736
741
  const ChatPrompt = ({ classNames, defaultTasksVisible = false, ...props }: ChatPromptProps) => {
737
- const { chat, db, processor, event, queueSize } = useChatContext(CHAT_PROMPT_NAME);
742
+ const { chat, db, chatModel, event, queueSize, started } = useChatContext(CHAT_PROMPT_NAME);
738
743
 
739
744
  // A chat with no checklist at all has nothing to disclose, so the toggle is withheld rather than
740
745
  // shown pointing at nothing — `ChatActions` renders it only when `tasksVisible` is defined.
@@ -774,9 +779,10 @@ const ChatPrompt = ({ classNames, defaultTasksVisible = false, ...props }: ChatP
774
779
  classNames={[tasksVisible && 'rounded-t-none', classNames]}
775
780
  db={db}
776
781
  chat={chat}
777
- processor={processor}
782
+ chatModel={chatModel}
778
783
  event={event}
779
784
  queueSize={queueSize}
785
+ started={started}
780
786
  tasksVisible={hasTasks ? tasksVisible : undefined}
781
787
  />
782
788
  </Collapsible.Root>
@@ -939,7 +945,7 @@ const ChatTaskQuestions = ({ tasks }: { tasks: readonly Task.Task[] }) => {
939
945
  }
940
946
 
941
947
  return (
942
- <div className='flex flex-col gap-2 p-2' data-testid='chat.taskQuestions'>
948
+ <Layout.Flex column gap='sm' classNames='p-2' data-testid='chat.taskQuestions'>
943
949
  {threads.map(({ task, thread }) => (
944
950
  <TaskQuestion
945
951
  key={thread.question.id}
@@ -947,7 +953,7 @@ const ChatTaskQuestions = ({ tasks }: { tasks: readonly Task.Task[] }) => {
947
953
  onAnswer={(answer) => handleAnswer(task, thread.question.id, answer)}
948
954
  />
949
955
  ))}
950
- </div>
956
+ </Layout.Flex>
951
957
  );
952
958
  };
953
959
 
@@ -960,14 +966,14 @@ ChatTaskQuestions.displayName = 'Chat.TaskQuestions';
960
966
  const CHAT_ACTIVITY_NAME = 'Chat.Activity';
961
967
 
962
968
  /**
963
- * The activity line bound to the chat's processor and the chat's pending alarms: what the agent is
969
+ * The activity line bound to the chat's chat model and the chat's pending alarms: what the agent is
964
970
  * doing, for as long as it is doing anything. The line itself takes resolved values, so it lives
965
971
  * beside the prompt with the rest of the presentational parts and only the binding is here.
966
972
  */
967
973
  const ChatActivity = ({ classNames }: Util.ThemedClassName) => {
968
- const { processor } = useChatContext(CHAT_ACTIVITY_NAME);
974
+ const { chatModel } = useChatContext(CHAT_ACTIVITY_NAME);
969
975
  const { alarms } = useChatThreadContext(CHAT_ACTIVITY_NAME);
970
- const activity = useAtomValue(getProcessorState(processor).activity);
976
+ const activity = useAtomValue(getChatModelState(chatModel).activity);
971
977
 
972
978
  // Earliest pending alarm: the agent wakes at the first one, so a later one says nothing about the
973
979
  // wait in front of the reader.
@@ -12,7 +12,7 @@ import * as Toast from '@dxos/react-ui/Toast';
12
12
  import { meta as pluginMeta } from '#meta';
13
13
  import { translations } from '#translations';
14
14
 
15
- import { AiUsageQuotaError, parseError } from '../../processor/index.ts';
15
+ import { AiUsageQuotaError, parseError } from '../../chat-model/index.ts';
16
16
 
17
17
  type FailureToastProps = {
18
18
  /**
@@ -11,7 +11,7 @@ import { type ChatThreadController } from '@dxos/react-ui-assistant';
11
11
  import { type MessageRange } from '@dxos/react-ui-feed';
12
12
  import { type Message } from '@dxos/types';
13
13
 
14
- import { type AiChatProcessor } from '../../processor/index.ts';
14
+ import { type ChatModel } from '../../chat-model/index.ts';
15
15
  import { type ChatEvent } from './events.ts';
16
16
 
17
17
  /**
@@ -36,8 +36,10 @@ export type ChatContextValue = {
36
36
  event: Event<ChatEvent>;
37
37
  db?: Database.Database;
38
38
  chat?: Chat.Chat;
39
- /** Undefined while the processor is still opening; the chat renders from the feed meanwhile. */
40
- processor?: AiChatProcessor;
39
+ /** Undefined while the chat model is still opening; the chat renders from the feed meanwhile. */
40
+ chatModel?: ChatModel;
41
+ /** Whether the conversation has begun; a boolean, so it changes once rather than per message. */
42
+ started: boolean;
41
43
  /** How many prompts wait behind the running turn; a count, so it changes per enqueue rather than per block. */
42
44
  queueSize: number;
43
45
  setController: (controller: ChatThreadController | null) => void;
@@ -37,7 +37,7 @@ export type ChatActionsProps = Util.ThemedClassName<
37
37
  customActions?: Atom.Atom<ActionGraphProps>;
38
38
  processing?: boolean;
39
39
  debug?: boolean;
40
- /** Whether the prompt holds text and the processor would accept it; drives the send control's enablement. */
40
+ /** Whether the prompt holds text and the chat model would accept it; drives the send control's enablement. */
41
41
  canSend?: boolean;
42
42
  /** Whether the checklist beside the prompt is shown; the toggle renders only when provided. */
43
43
  tasksVisible?: boolean;
@@ -12,23 +12,23 @@ import type * as Util from '@dxos/react-ui/Util';
12
12
 
13
13
  import { meta } from '#meta';
14
14
 
15
- import { type AiChatProcessor } from '../../processor/index.ts';
15
+ import { type ChatModel } from '../../chat-model/index.ts';
16
16
 
17
17
  export type ChatMcpErrorsProps = Util.ThemedClassName<{
18
- processor: AiChatProcessor;
18
+ chatModel: ChatModel;
19
19
  }>;
20
20
 
21
21
  /**
22
22
  * Inline banner that lists MCP servers that failed to connect during the most recent request.
23
23
  * The chat itself keeps working without these servers — this just lets the user see what was dropped.
24
24
  */
25
- export const ChatMcpErrors = ({ classNames, processor }: ChatMcpErrorsProps) => {
25
+ export const ChatMcpErrors = ({ classNames, chatModel }: ChatMcpErrorsProps) => {
26
26
  const { t } = Hooks.useTranslation(meta.profile.key);
27
- const errors = useAtomValue(processor.mcpErrors);
27
+ const errors = useAtomValue(chatModel.mcpErrors);
28
28
 
29
29
  const handleDismiss = useCallback(() => {
30
- processor.dismissMcpErrors();
31
- }, [processor]);
30
+ chatModel.dismissMcpErrors();
31
+ }, [chatModel]);
32
32
 
33
33
  if (errors.length === 0) {
34
34
  return null;
@@ -7,6 +7,7 @@ import * as Effect from 'effect/Effect';
7
7
  import * as Atom from 'effect/reactivity/Atom';
8
8
  import React, { useState } from 'react';
9
9
 
10
+ import { SessionConfig } from '@dxos/ai';
10
11
  import * as Capability from '@dxos/app-framework/Capability';
11
12
  import { withPluginManager } from '@dxos/app-framework/testing';
12
13
  import { capabilities } from '@dxos/assistant-toolkit/testing';
@@ -29,6 +30,19 @@ import { type Assistant, AssistantCapabilities } from '#types';
29
30
 
30
31
  import { ChatOptions, type ChatOptionsProps, ObjectsPanel } from './ChatOptions.tsx';
31
32
 
33
+ /** Stands in for an agent another plugin registers: listed in the picker, never run by the story. */
34
+ const stubAgent = (
35
+ id: string,
36
+ label: string,
37
+ availability: AssistantCapabilities.AgentAvailability,
38
+ ): AssistantCapabilities.Agent => ({
39
+ id,
40
+ label,
41
+ icon: id === SessionConfig.COMPOSER_HARNESS ? 'ph--sparkle--regular' : 'px--anthropic--regular',
42
+ availability: Atom.make(availability).pipe(Atom.keepAlive),
43
+ makeTurnProducer: () => Effect.succeed({ runTurn: () => Effect.succeed([]), getSkills: () => [] }),
44
+ });
45
+
32
46
  const presets = [
33
47
  {
34
48
  id: 'edge-claude-sonnet',
@@ -44,9 +58,9 @@ const presets = [
44
58
  },
45
59
  ];
46
60
 
47
- type StoryArgs = Pick<ChatOptionsProps, 'presets'>;
61
+ type StoryArgs = Pick<ChatOptionsProps, 'presets' | 'started'>;
48
62
 
49
- const DefaultStory = ({ presets }: StoryArgs) => {
63
+ const DefaultStory = ({ presets, started }: StoryArgs) => {
50
64
  const [space] = useSpaces();
51
65
  const [feed] = useQuery(space?.db, Filter.type(Feed.Feed));
52
66
  const [chat] = useQuery(space?.db, Filter.type(Chat.Chat));
@@ -63,6 +77,7 @@ const DefaultStory = ({ presets }: StoryArgs) => {
63
77
  db={space.db}
64
78
  context={binder}
65
79
  registry={registry}
80
+ started={started}
66
81
  presets={presets}
67
82
  preset={preset}
68
83
  onPresetChange={setPreset}
@@ -72,7 +87,6 @@ const DefaultStory = ({ presets }: StoryArgs) => {
72
87
 
73
88
  const meta = {
74
89
  title: 'plugins/plugin-assistant/components/ChatOptions',
75
- component: ChatOptions as any,
76
90
  render: DefaultStory,
77
91
  decorators: [
78
92
  withTheme(),
@@ -107,6 +121,18 @@ const meta = {
107
121
  ...capabilities,
108
122
  // The Models tab's online switch reads the assistant settings; without them it would suspend forever.
109
123
  Capability.contribute(AssistantCapabilities.Settings, Atom.make<Assistant.Settings>({}).pipe(Atom.keepAlive)),
124
+ Capability.contribute(
125
+ AssistantCapabilities.Agent,
126
+ stubAgent(SessionConfig.COMPOSER_HARNESS, 'Composer', { available: true }),
127
+ ),
128
+ Capability.contribute(
129
+ AssistantCapabilities.Agent,
130
+ stubAgent('claude-code', 'Claude Code', { available: false, reason: 'needs the Composer desktop app' }),
131
+ ),
132
+ Capability.contribute(
133
+ AssistantCapabilities.Agent,
134
+ stubAgent('claude-code-edge', 'Claude Code (cloud)', { available: true }),
135
+ ),
110
136
  ],
111
137
  }),
112
138
  ],
@@ -126,6 +152,14 @@ export const Default: Story = {
126
152
  },
127
153
  };
128
154
 
155
+ /** A chat that has begun shows its agent but no longer offers the others. */
156
+ export const Started: Story = {
157
+ args: {
158
+ presets,
159
+ started: true,
160
+ },
161
+ };
162
+
129
163
  export const _ObjectsPanel: Story = {
130
164
  render: () => {
131
165
  const [space] = useSpaces();