@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
@@ -14,7 +14,7 @@ import { ChatDialog as NaturalChatDialog } from '@dxos/react-ui-chat';
14
14
  import * as UiHooks from '@dxos/react-ui/Hooks';
15
15
 
16
16
  import { Chat, type ChatRootProps } from '#components';
17
- import { useChatProcessor, useChatServices, usePresets } from '#hooks';
17
+ import { useChatModel, useChatServices, usePresets } from '#hooks';
18
18
  import { meta } from '#meta';
19
19
  import { AssistantCapabilities } from '#types';
20
20
 
@@ -30,7 +30,7 @@ export const ChatDialog = ({ chat }: ChatDialogProps) => {
30
30
  const runtime = useChatServices({ id: db?.spaceId });
31
31
  const { preset, ...chatProps } = usePresets(settings, chat);
32
32
  const registry = useRegistry();
33
- const processor = useChatProcessor({ chat, preset, runtime, registry, settings });
33
+ const chatModel = useChatModel({ db, chat, preset, runtime, registry, settings });
34
34
  // Subscribe via `useObject` so the thread re-renders when ChatOptions changes the view type.
35
35
  const [chatViewType] = useObject(chat, 'viewType');
36
36
 
@@ -50,12 +50,12 @@ export const ChatDialog = ({ chat }: ChatDialogProps) => {
50
50
  }
51
51
  }, []);
52
52
 
53
- if (!chat || !processor) {
53
+ if (!chat || !chatModel) {
54
54
  return null;
55
55
  }
56
56
 
57
57
  return (
58
- <Chat.Root chat={chat} processor={processor} onEvent={handleEvent}>
58
+ <Chat.Root chat={chat} chatModel={chatModel} onEvent={handleEvent}>
59
59
  <NaturalChatDialog.Root open={open} expanded={expanded} onOpenChange={setOpen}>
60
60
  <NaturalChatDialog.Header title={t('assistant-dialog.title')} />
61
61
  <NaturalChatDialog.Content>
@@ -2,90 +2,59 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
- import React, { useEffect, useMemo, useState } from 'react';
5
+ import * as Effect from 'effect/Effect';
6
+ import type * as AtomRegistry from 'effect/reactivity/AtomRegistry';
7
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
8
 
7
9
  import * as Capabilities from '@dxos/app-framework/Capabilities';
8
10
  import * as Hooks from '@dxos/app-framework/Hooks';
11
+ import * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';
9
12
  import * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';
10
- import type * as Chat from '@dxos/assistant/Chat';
13
+ import { AiContext } from '@dxos/assistant';
14
+ import * as Chat from '@dxos/assistant/Chat';
11
15
  import { Event } from '@dxos/async';
16
+ import { Database, Feed, Ref } from '@dxos/echo';
17
+ import * as EffectEx from '@dxos/effect/EffectEx';
18
+ import { log } from '@dxos/log';
12
19
  import { type Space, useRegistry } from '@dxos/react-client/echo';
13
20
  import * as UiHooks from '@dxos/react-ui/Hooks';
14
21
 
15
22
  import { type ChatEvent, ChatPrompt } from '#components';
16
- import { useChatProcessor, useChatServices, usePresets } from '#hooks';
23
+ import { usePresets } from '#hooks';
17
24
  import { meta } from '#meta';
18
- import { AssistantCapabilities, AssistantOperation } from '#types';
25
+ import { AssistantCapabilities } from '#types';
19
26
 
20
27
  import { getChatPath } from '../../paths.ts';
28
+ import { bindChatDefaults, contributesPluginManager } from '../../util/default-skills.ts';
21
29
 
22
30
  type SpaceScopedProps = {
23
31
  space?: Space;
24
32
  };
25
33
 
26
- /**
27
- * Home article pinned-bottom contributor: the assistant prompt. Backed by an ephemeral in-memory
28
- * chat whose sole responsibility is to collect the user's text, context bindings, and preset
29
- * choice, then on submit: persist the chat to the space, queue the text as a pending prompt, and
30
- * navigate to it. AI generation runs in the opened chat view — the processor here exists only to
31
- * back the context-binder UI.
32
- */
33
34
  export const SpaceHomePrompt = ({ space }: SpaceScopedProps) => {
34
35
  const { t } = UiHooks.useTranslation(meta.profile.key);
35
- const { invokePromise } = Hooks.useOperationInvoker();
36
36
 
37
37
  const registry = useRegistry();
38
38
  const atomRegistry = Hooks.useCapability(Capabilities.AtomRegistry);
39
- const stateAtom = Hooks.useCapability(AssistantCapabilities.State);
40
- const runtime = useChatServices({ id: space?.id });
41
39
  const settings = Hooks.useAtomCapability(AssistantCapabilities.Settings);
40
+ const pluginManager = contributesPluginManager(Hooks.useCapabilities(AppCapabilities.SkillDefinition));
42
41
 
43
- // In-memory backing chat (not yet added to the space). `nonce` forces a fresh chat after submit.
44
- const [chat, setChat] = useState<Chat.Chat>();
45
- const [nonce, setNonce] = useState(0);
46
- const { preset, ...presetProps } = usePresets(settings, chat);
47
- useEffect(() => {
42
+ const [draftGeneration, setDraftGeneration] = useState(0);
43
+ const draft = useMemo(() => {
48
44
  if (!space) {
49
- setChat(undefined);
50
- return;
45
+ return undefined;
51
46
  }
52
- let cancelled = false;
53
- void invokePromise(AssistantOperation.CreateChat, {}, { spaceId: space.db.spaceId }).then((result) => {
54
- if (!cancelled) {
55
- setChat(result.data?.object);
56
- }
57
- });
58
- return () => {
59
- cancelled = true;
60
- };
61
- }, [space, nonce, invokePromise]);
62
-
63
- const processor = useChatProcessor({ db: space?.db, chat, preset, runtime, registry });
64
-
65
- const event = useMemo(() => new Event<ChatEvent>(), []);
66
- useEffect(() => {
67
- return event.on((ev) => {
68
- if (ev.type !== 'submit') {
69
- return;
70
- }
71
- const text = ev.text.trim();
72
- if (!space || !chat || text.length === 0) {
73
- return;
74
- }
47
+ const feed = Feed.make();
48
+ return { feed, chat: Chat.make({ feed: Ref.make(feed) }) };
49
+ }, [space, draftGeneration]);
50
+ const chat = draft?.chat;
51
+ const context = useDraftContext({ db: space?.db, draft, registry: atomRegistry, pluginManager });
52
+ const { preset, ...presetProps } = usePresets(settings, chat);
75
53
 
76
- // Persist the in-memory chat, queue the prompt, and open the chat (which submits it).
77
- space.db.add(chat);
78
- const chatPath = getChatPath(space.db.spaceId, chat.id);
79
- atomRegistry.update(stateAtom, (current) => ({
80
- ...current,
81
- pendingPrompts: { ...current.pendingPrompts, [chatPath]: text },
82
- }));
83
- void invokePromise(LayoutOperation.Open, { subject: [chatPath] });
84
- setNonce((current) => current + 1);
85
- });
86
- }, [event, space, chat, atomRegistry, stateAtom, invokePromise]);
54
+ const startNewDraft = useCallback(() => setDraftGeneration((current) => current + 1), []);
55
+ const event = useDraftSend({ space, draft, context, onSent: startNewDraft });
87
56
 
88
- if (!processor || !chat || !space) {
57
+ if (!space) {
89
58
  return null;
90
59
  }
91
60
 
@@ -95,7 +64,8 @@ export const SpaceHomePrompt = ({ space }: SpaceScopedProps) => {
95
64
  outline
96
65
  chat={chat}
97
66
  db={space.db}
98
- processor={processor}
67
+ context={context}
68
+ registry={registry}
99
69
  event={event}
100
70
  preset={preset?.id}
101
71
  placeholder={t('space-home.prompt.placeholder')}
@@ -104,3 +74,86 @@ export const SpaceHomePrompt = ({ space }: SpaceScopedProps) => {
104
74
  };
105
75
 
106
76
  SpaceHomePrompt.displayName = 'SpaceHomePrompt';
77
+
78
+ type UseDraftSendProps = {
79
+ space?: Space;
80
+ draft?: { feed: Feed.Feed; chat: Chat.Chat };
81
+ context?: AiContext.Binder;
82
+ onSent: () => void;
83
+ };
84
+
85
+ /** Stores the draft's chat and bindings, then opens it; a failed send removes what it stored and restores the text. */
86
+ const useDraftSend = ({ space, draft, context, onSent }: UseDraftSendProps) => {
87
+ const { invokePromise } = Hooks.useOperationInvoker();
88
+ const atomRegistry = Hooks.useCapability(Capabilities.AtomRegistry);
89
+ const stateAtom = Hooks.useCapability(AssistantCapabilities.State);
90
+ const event = useMemo(() => new Event<ChatEvent>(), []);
91
+ useEffect(() => {
92
+ return event.on((ev) => {
93
+ if (ev.type !== 'submit') {
94
+ return;
95
+ }
96
+ const text = ev.text.trim();
97
+ if (!space || !draft || !context || text.length === 0) {
98
+ return;
99
+ }
100
+
101
+ const { feed, chat } = draft;
102
+ space.db.add(feed);
103
+ space.db.add(chat);
104
+ const chatPath = getChatPath(space.db.spaceId, chat.id);
105
+ onSent();
106
+ void context
107
+ .flush()
108
+ .then(() => {
109
+ atomRegistry.update(stateAtom, (current) => ({
110
+ ...current,
111
+ pendingPrompts: { ...current.pendingPrompts, [chatPath]: text },
112
+ }));
113
+ void invokePromise(LayoutOperation.Open, { subject: [chatPath] });
114
+ })
115
+ .catch((err) => {
116
+ log.catch(err);
117
+ space.db.remove(chat);
118
+ space.db.remove(feed);
119
+ event.emit({ type: 'update-prompt', text });
120
+ });
121
+ });
122
+ }, [event, space, draft, context, atomRegistry, stateAtom, invokePromise, onSent]);
123
+ return event;
124
+ };
125
+
126
+ type UseDraftContextProps = {
127
+ db?: Database.Database;
128
+ draft?: { feed: Feed.Feed; chat: Chat.Chat };
129
+ registry: AtomRegistry.AtomRegistry;
130
+ pluginManager: boolean;
131
+ };
132
+
133
+ const useDraftContext = ({ db, draft, registry, pluginManager }: UseDraftContextProps) => {
134
+ const [context, setContext] = useState<AiContext.Binder>();
135
+ UiHooks.useAsyncEffect(
136
+ async (controller) => {
137
+ if (!db || !draft) {
138
+ return;
139
+ }
140
+ const runtime = await EffectEx.runAndForwardErrors(
141
+ Effect.context<Database.Service>().pipe(Effect.provide(Database.layer(db))),
142
+ );
143
+ const binder = new AiContext.Binder({ feed: draft.feed, runtime, registry, hold: true });
144
+ await binder.open();
145
+ await bindChatDefaults(binder, { chat: draft.chat, pluginManager });
146
+ if (controller.signal.aborted) {
147
+ void binder.close();
148
+ return;
149
+ }
150
+ setContext(binder);
151
+ return () => {
152
+ setContext(undefined);
153
+ void binder.close();
154
+ };
155
+ },
156
+ [db, draft, registry, pluginManager],
157
+ );
158
+ return context;
159
+ };
@@ -5,7 +5,7 @@
5
5
 
6
6
  export * from './useSkillRegistry.ts';
7
7
  export * from './useChatKeymap.ts';
8
- export * from './useChatProcessor.ts';
8
+ export * from './useChatModel.ts';
9
9
  export * from './useChatServices.ts';
10
10
  export * from './useChatToolbarActions.ts';
11
11
  export * from './useContextBinder.ts';
@@ -21,4 +21,4 @@ export * from './useHomeSuggestions.ts';
21
21
  export * from './useProcessEphemeralStatus.ts';
22
22
  export * from './useSessionTimeline.ts';
23
23
 
24
- export { type AiChatProcessor } from '../processor/index.ts';
24
+ export { type ChatModel } from '../chat-model/index.ts';
@@ -23,23 +23,23 @@ import * as UiHooks from '@dxos/react-ui/Hooks';
23
23
 
24
24
  import { Assistant } from '#types';
25
25
 
26
- import { AiChatProcessor, type AiServicePreset } from '../processor/index.ts';
26
+ import { type AiServicePreset, ChatModel } from '../chat-model/index.ts';
27
27
 
28
- export type UseChatProcessorProps = {
28
+ export type UseChatModelProps = {
29
29
  db?: Database.Database;
30
30
  chat?: Chat.Chat;
31
31
  preset?: AiServicePreset;
32
32
  runtime?: Capabilities.ProcessManagerRuntime;
33
33
  registry?: Registry.Registry;
34
34
  settings?: Assistant.Settings;
35
- /** Attributes the prompts submitted through this processor to a person (see `AiChatProcessorOptions.sender`). */
35
+ /** Attributes the prompts submitted through this chat model to a person (see `ChatModelOptions.sender`). */
36
36
  sender?: AgentService.PromptSender;
37
37
  };
38
38
 
39
39
  /**
40
- * Configure and create AiChatProcessor.
40
+ * Configure and create ChatModel.
41
41
  */
42
- export const useChatProcessor = ({
42
+ export const useChatModel = ({
43
43
  db,
44
44
  chat,
45
45
  preset,
@@ -47,7 +47,7 @@ export const useChatProcessor = ({
47
47
  registry,
48
48
  settings,
49
49
  sender,
50
- }: UseChatProcessorProps): AiChatProcessor | undefined => {
50
+ }: UseChatModelProps): ChatModel | undefined => {
51
51
  const observableRegistry = useContext(RegistryContext);
52
52
 
53
53
  // Reactive subscription — re-renders when the feed ref resolves. Direct `.target` reads are not reactive.
@@ -79,11 +79,12 @@ export const useChatProcessor = ({
79
79
  }, [db, chat, feed]);
80
80
 
81
81
  const serviceResolver = Hooks.useCapability(Capabilities.ServiceResolver);
82
- // Primitives rather than the object, so an inline `sender` literal does not rebuild the processor each render.
82
+ // Primitives rather than the object, so an inline `sender` literal does not rebuild the chat model each render.
83
83
  const senderName = sender?.name;
84
84
  const senderDid = sender?.identityDid;
85
+ const reportStruggles = settings?.reportStruggles ?? false;
85
86
 
86
- const processor = useMemo(() => {
87
+ const chatModel = useMemo(() => {
87
88
  if (!runtime || !session || !chat || !feed || !db) {
88
89
  return undefined;
89
90
  }
@@ -98,8 +99,8 @@ export const useChatProcessor = ({
98
99
  OpaqueToolkit.OpaqueToolkitProvider,
99
100
  ).pipe(Layer.provide(Layer.succeed(ServiceResolver.ServiceResolver, serviceResolver)));
100
101
 
101
- log('creating processor', { preset, model: preset?.model, settings });
102
- return new AiChatProcessor(session, runtime, feed, spaceLayer, {
102
+ log('creating chat model', { preset, model: preset?.model, settings });
103
+ return new ChatModel(session, runtime, feed, spaceLayer, {
103
104
  chat: chat ? Ref.make(chat) : undefined,
104
105
  observableRegistry,
105
106
  registry,
@@ -110,13 +111,14 @@ export const useChatProcessor = ({
110
111
  senderName || senderDid
111
112
  ? { ...(senderName ? { name: senderName } : {}), ...(senderDid ? { identityDid: senderDid } : {}) }
112
113
  : undefined,
114
+ reportStruggles,
113
115
  });
114
- }, [runtime, session, registry, preset, chat, feed, db?.spaceId, senderName, senderDid]);
116
+ }, [runtime, session, registry, preset, chat, feed, db?.spaceId, senderName, senderDid, reportStruggles]);
115
117
 
116
- // A remount (e.g. the user navigated to another page mid-turn) gets a fresh processor whose
118
+ // A remount (e.g. the user navigated to another page mid-turn) gets a fresh chat model whose
117
119
  // active/streaming state starts empty, while the agent process for the feed keeps running;
118
120
  // adopting it restores the running indicator and the streamed blocks.
119
- useEffect(() => processor?.adopt(), [processor]);
121
+ useEffect(() => chatModel?.adopt(), [chatModel]);
120
122
 
121
- return processor;
123
+ return chatModel;
122
124
  };
@@ -6,23 +6,23 @@ import { useCallback } from 'react';
6
6
 
7
7
  import { trim } from '@dxos/util';
8
8
 
9
- import { type AiChatProcessor } from '../processor/index.ts';
9
+ import { type ChatModel } from '../chat-model/index.ts';
10
10
 
11
11
  /**
12
12
  * Returns an async callback that logs the chat's current context, system prompt,
13
13
  * and resolved tools to the browser console under a collapsible group.
14
14
  * Used by the chat's debug-toggle event handler.
15
15
  */
16
- export const useDebug = ({ processor }: { processor?: AiChatProcessor }) => {
16
+ export const useDebug = ({ chatModel }: { chatModel?: ChatModel }) => {
17
17
  return useCallback(async () => {
18
- if (!processor) {
18
+ if (!chatModel) {
19
19
  return;
20
20
  }
21
21
 
22
- const objects = processor.context.getObjects();
23
- const skills = processor.context.getSkills();
24
- const system = await processor.getSystemPrompt();
25
- const tools = (await processor.getTools()) ?? {};
22
+ const objects = chatModel.context.getObjects();
23
+ const skills = chatModel.context.getSkills();
24
+ const system = await chatModel.getSystemPrompt();
25
+ const tools = (await chatModel.getTools()) ?? {};
26
26
  console.group('Chat', { objects, skills });
27
27
  try {
28
28
  console.log(trim`
@@ -38,5 +38,5 @@ export const useDebug = ({ processor }: { processor?: AiChatProcessor }) => {
38
38
  } finally {
39
39
  console.groupEnd();
40
40
  }
41
- }, [processor]);
41
+ }, [chatModel]);
42
42
  };
@@ -9,7 +9,7 @@ import { type Space } from '@dxos/react-client/echo';
9
9
  import * as UiHooks from '@dxos/react-ui/Hooks';
10
10
 
11
11
  import { meta } from '#meta';
12
- import { AssistantOperation } from '#types';
12
+ import { AssistantCapabilities, AssistantOperation } from '#types';
13
13
 
14
14
  const FALLBACK_SUGGESTION_KEYS = [
15
15
  'space-home.suggestion-magazine.label',
@@ -17,31 +17,36 @@ const FALLBACK_SUGGESTION_KEYS = [
17
17
  ] as const;
18
18
 
19
19
  /**
20
- * Returns starter prompts for the space Home page. Returns undefined while the request is in flight
21
- * so the section stays hidden until settled. On success yields generated prompts; on empty result or
22
- * error falls back to the hardcoded defaults.
20
+ * Returns starter prompts for the space Home page: the space's cached prompts at once, then the
21
+ * operation's answer once it settles (generated prompts, or the hardcoded defaults on an empty result
22
+ * or error). Undefined while nothing is cached and the request is in flight.
23
23
  */
24
24
  export const useHomeSuggestions = (space?: Space): readonly string[] | undefined => {
25
25
  const { t } = UiHooks.useTranslation(meta.profile.key);
26
26
  const { invokePromise } = Hooks.useOperationInvoker();
27
+ const cache = Hooks.useOptionalAtomCapability(AssistantCapabilities.HomeSuggestionsCache);
27
28
  const fallbacks = useMemo(() => FALLBACK_SUGGESTION_KEYS.map((key) => t(key)), [t]);
28
- const [suggestions, setSuggestions] = useState<readonly string[] | undefined>(undefined);
29
+ const [settled, setSettled] = useState<{ spaceId: string; prompts: readonly string[] }>();
29
30
 
30
31
  UiHooks.useAsyncEffect(
31
32
  async (controller) => {
32
- setSuggestions(undefined);
33
33
  if (!space) {
34
34
  return;
35
35
  }
36
- const result = await invokePromise(AssistantOperation.GenerateHomeSuggestions, {}, { spaceId: space.db.spaceId });
36
+ const spaceId = space.db.spaceId;
37
+ const result = await invokePromise(AssistantOperation.GenerateHomeSuggestions, {}, { spaceId });
37
38
  if (controller.signal.aborted) {
38
39
  return;
39
40
  }
40
41
  const prompts = result.data?.prompts;
41
- setSuggestions(prompts && prompts.length > 0 ? prompts : fallbacks);
42
+ setSettled({ spaceId, prompts: prompts && prompts.length > 0 ? prompts : fallbacks });
42
43
  },
43
44
  [space, invokePromise],
44
45
  );
45
46
 
46
- return suggestions;
47
+ const spaceId = space?.db.spaceId;
48
+ if (!spaceId) {
49
+ return undefined;
50
+ }
51
+ return settled?.spaceId === spaceId ? settled.prompts : cache?.[spaceId]?.prompts;
47
52
  };
@@ -24,7 +24,7 @@ import {
24
24
  presetsForProvider,
25
25
  providerForModel,
26
26
  resolveProvider,
27
- } from '../processor/index.ts';
27
+ } from '../chat-model/index.ts';
28
28
 
29
29
  export type UsePresets = {
30
30
  preset: AiServicePreset | undefined;
@@ -38,7 +38,7 @@ export type UsePresets = {
38
38
  export const usePresets = (settings: Assistant.Settings, chat?: Chat.Chat): UsePresets => {
39
39
  const { t } = UiHooks.useTranslation(meta.profile.key);
40
40
  // Subscribed rather than read: the picker has to follow a selection made on another mount of the
41
- // same chat, and the stamp the processor writes before the first request.
41
+ // same chat, and the stamp the chat model writes before the first request.
42
42
  const [session] = useObject(chat, 'session');
43
43
  const chatModel = session?.model;
44
44
 
@@ -60,7 +60,7 @@ const resolveSubscribePids = (agentPid: Process.ID, processes: readonly Process.
60
60
  const attachActiveHandle = (
61
61
  processManager: ProcessManager.Manager,
62
62
  pid: Process.ID,
63
- ): Effect.Effect<Process.Handle<any, any, never> | undefined> =>
63
+ ): Effect.Effect<Process.Process<any, any, never> | undefined> =>
64
64
  Effect.gen(function* () {
65
65
  const maxAttempts = 15;
66
66
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -10,7 +10,7 @@ import { Obj } from '@dxos/echo';
10
10
  import * as AttentionCapabilities from '@dxos/plugin-attention/AttentionCapabilities';
11
11
  import { Selection } from '@dxos/react-ui-attention/types';
12
12
 
13
- import { type ProcessorRequestContext } from '../processor/index.ts';
13
+ import { type ChatRequestContext } from '../chat-model/index.ts';
14
14
 
15
15
  /** Resolve `object`'s selection to text via the AnchorResolver contributed for its typename. */
16
16
  export const getSelectionContext = ({
@@ -21,7 +21,7 @@ export const getSelectionContext = ({
21
21
  object: Obj.Unknown;
22
22
  selection: Selection.Selection | undefined;
23
23
  resolvers: readonly AppCapabilities.AnchorResolver[];
24
- }): ProcessorRequestContext | undefined => {
24
+ }): ChatRequestContext | undefined => {
25
25
  const typename = Obj.getTypename(object);
26
26
  const resolver = typename ? resolvers.find((candidate) => candidate.key === typename) : undefined;
27
27
  if (!resolver) {
@@ -55,9 +55,7 @@ export const getSelectionContext = ({
55
55
  };
56
56
 
57
57
  /** Submit-time provider of the companion object's current selection as request context. */
58
- export const useSelectionContext = (
59
- companionTo: Obj.Unknown | undefined,
60
- ): (() => ProcessorRequestContext | undefined) => {
58
+ export const useSelectionContext = (companionTo: Obj.Unknown | undefined): (() => ChatRequestContext | undefined) => {
61
59
  // getAll-style lookup: absent capabilities yield empty arrays so non-companion chats stay inert.
62
60
  const [viewState] = Hooks.useCapabilities(AttentionCapabilities.ViewState);
63
61
  const resolvers = Hooks.useCapabilities(AppCapabilities.AnchorResolver);