@antelopejs/dms-ai 0.1.3 → 0.1.5

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 (245) hide show
  1. package/README.md +48 -29
  2. package/dist/channels/bridge.d.ts +2 -8
  3. package/dist/channels/bridge.js +26 -42
  4. package/dist/channels/bridge.js.map +1 -1
  5. package/dist/channels/channel-service.d.ts +2 -2
  6. package/dist/channels/channel-service.js +12 -31
  7. package/dist/channels/channel-service.js.map +1 -1
  8. package/dist/channels/registry.d.ts +2 -2
  9. package/dist/channels/registry.js +7 -7
  10. package/dist/channels/registry.js.map +1 -1
  11. package/dist/config.d.ts +1 -0
  12. package/dist/config.js +8 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants/channels.d.ts +5 -10
  15. package/dist/constants/channels.js +6 -10
  16. package/dist/constants/channels.js.map +1 -1
  17. package/dist/constants/sidecar.d.ts +2 -1
  18. package/dist/constants/sidecar.js +3 -2
  19. package/dist/constants/sidecar.js.map +1 -1
  20. package/dist/index.js +8 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/lifecycle/build-id.d.ts +1 -0
  23. package/dist/lifecycle/build-id.js +32 -0
  24. package/dist/lifecycle/build-id.js.map +1 -0
  25. package/dist/lifecycle/sidecar-socket.d.ts +1 -1
  26. package/dist/lifecycle/sidecar-socket.js +2 -2
  27. package/dist/lifecycle/sidecar-socket.js.map +1 -1
  28. package/dist/lifecycle/spawn-sidecar.d.ts +0 -1
  29. package/dist/lifecycle/spawn-sidecar.js +5 -27
  30. package/dist/lifecycle/spawn-sidecar.js.map +1 -1
  31. package/dist/pages/settings/page.js.map +1 -1
  32. package/dist/routes/channels.d.ts +3 -3
  33. package/dist/routes/channels.js +11 -12
  34. package/dist/routes/channels.js.map +1 -1
  35. package/dist/routes/index.d.ts +0 -1
  36. package/dist/routes/index.js +0 -1
  37. package/dist/routes/index.js.map +1 -1
  38. package/frontend-vue/.prettierignore +3 -0
  39. package/frontend-vue/.prettierrc +10 -0
  40. package/frontend-vue/app/chat/components/ChatView.vue +582 -0
  41. package/frontend-vue/app/chat/components/ComposerInput.vue +742 -0
  42. package/frontend-vue/app/chat/components/ConnectionBanner.vue +105 -0
  43. package/frontend-vue/app/chat/components/ConversationDrawer.vue +266 -0
  44. package/frontend-vue/app/chat/components/MarkdownContent.vue +196 -0
  45. package/frontend-vue/app/chat/components/MessageList.vue +474 -0
  46. package/frontend-vue/app/chat/components/PermissionRequests.vue +323 -0
  47. package/frontend-vue/app/chat/components/QuestionPrompt.vue +275 -0
  48. package/frontend-vue/app/chat/components/QueuedMessages.vue +109 -0
  49. package/frontend-vue/app/chat/components/TodoList.vue +291 -0
  50. package/frontend-vue/app/chat/components/ToolCallEntry.vue +218 -0
  51. package/frontend-vue/app/chat/components/ToolCluster.vue +282 -0
  52. package/frontend-vue/app/chat/composables/useChatChannel.ts +116 -0
  53. package/frontend-vue/app/chat/composables/useConversation.ts +838 -0
  54. package/frontend-vue/app/chat/composables/useConversationId.ts +34 -0
  55. package/frontend-vue/app/chat/composables/useConversationList.ts +56 -0
  56. package/frontend-vue/app/chat/composables/usePermissionQueue.ts +59 -0
  57. package/frontend-vue/app/chat/composables/useQuestionQueue.ts +50 -0
  58. package/frontend-vue/app/chat/composables/useRunClock.ts +28 -0
  59. package/frontend-vue/app/chat/composables/useSettings.ts +67 -0
  60. package/frontend-vue/app/chat/constants/attachments.ts +16 -0
  61. package/frontend-vue/app/chat/constants/connection-banner.ts +7 -0
  62. package/frontend-vue/app/chat/constants/conversation-drawer.ts +13 -0
  63. package/frontend-vue/app/chat/constants/conversation.ts +75 -0
  64. package/frontend-vue/app/chat/constants/permissions.ts +50 -0
  65. package/frontend-vue/app/chat/constants/protocol.ts +48 -0
  66. package/frontend-vue/app/chat/constants/run-status.ts +50 -0
  67. package/frontend-vue/app/chat/constants/settings.ts +60 -0
  68. package/frontend-vue/app/chat/types/conversation.ts +87 -0
  69. package/frontend-vue/app/chat/types/permission.ts +7 -0
  70. package/frontend-vue/app/chat/types/question.ts +17 -0
  71. package/frontend-vue/app/chat/types/settings.ts +27 -0
  72. package/frontend-vue/app/chat/utils/attachments.ts +62 -0
  73. package/frontend-vue/app/chat/utils/format.ts +14 -0
  74. package/frontend-vue/app/chat/utils/ids.ts +34 -0
  75. package/frontend-vue/app/chat/utils/markdown.ts +188 -0
  76. package/frontend-vue/app/chat/utils/run-status.ts +90 -0
  77. package/frontend-vue/app/chat/utils/todos.ts +64 -0
  78. package/frontend-vue/app/chat/utils/tool-summary.ts +53 -0
  79. package/frontend-vue/app/components/ActivityView.vue +34 -34
  80. package/frontend-vue/app/components/ChatPanel.vue +276 -0
  81. package/frontend-vue/app/components/SettingsView.vue +127 -130
  82. package/frontend-vue/app/components/SkillsView.vue +55 -53
  83. package/frontend-vue/app/plugins/ai.client.ts +145 -129
  84. package/frontend-vue/app/runtime/assistant-session.ts +21 -0
  85. package/frontend-vue/app/runtime/backoff.ts +11 -14
  86. package/frontend-vue/app/runtime/channel-client.ts +130 -141
  87. package/frontend-vue/app/runtime/chat-transport.ts +25 -55
  88. package/frontend-vue/app/runtime/constants.ts +70 -60
  89. package/frontend-vue/app/runtime/current-page.ts +49 -15
  90. package/frontend-vue/app/runtime/event-stream.ts +35 -35
  91. package/frontend-vue/app/runtime/frame-router.ts +30 -0
  92. package/frontend-vue/app/runtime/header-action.ts +18 -18
  93. package/frontend-vue/app/runtime/host-commands.ts +57 -56
  94. package/frontend-vue/app/runtime/listeners.ts +31 -0
  95. package/frontend-vue/app/runtime/navigation-complete.ts +7 -7
  96. package/frontend-vue/app/runtime/overlay-prefs.ts +36 -36
  97. package/frontend-vue/app/runtime/panel-state.ts +88 -0
  98. package/frontend-vue/app/runtime/session-gate.ts +8 -8
  99. package/frontend-vue/app/runtime/shortcuts.ts +12 -12
  100. package/frontend-vue/app/runtime/sidecar-status.ts +61 -61
  101. package/frontend-vue/app/runtime/typed-message.ts +11 -0
  102. package/frontend-vue/dms.frontend.ts +12 -12
  103. package/frontend-vue/eslint.config.mjs +13 -13
  104. package/frontend-vue/package.json +11 -4
  105. package/frontend-vue/pnpm-lock.yaml +298 -96
  106. package/frontend-vue/tests/backoff.test.ts +26 -30
  107. package/frontend-vue/tests/channel-client.test.ts +187 -188
  108. package/frontend-vue/tests/chat/markdown.test.ts +271 -0
  109. package/frontend-vue/tests/chat/useChatChannel.test.ts +124 -0
  110. package/frontend-vue/tests/chat/useConversation.test.ts +283 -0
  111. package/frontend-vue/tests/chat-panel.test.ts +354 -0
  112. package/frontend-vue/tests/chat-transport.test.ts +84 -57
  113. package/frontend-vue/tests/host-commands.test.ts +126 -87
  114. package/frontend-vue/tests/navigation.test.ts +65 -34
  115. package/frontend-vue/tests/overlay-prefs.test.ts +49 -49
  116. package/frontend-vue/tests/panel-state.test.ts +58 -0
  117. package/frontend-vue/tests/session-gate.test.ts +33 -33
  118. package/frontend-vue/tests/sidecar-guest.test.ts +46 -46
  119. package/frontend-vue/tests/sidecar-status.test.ts +29 -29
  120. package/frontend-vue/tsconfig.chat.json +16 -0
  121. package/frontend-vue/vitest.config.ts +6 -4
  122. package/package.json +5 -5
  123. package/sidecar/dist/agent/effective-mode.d.ts +2 -2
  124. package/sidecar/dist/agent/effective-mode.js +1 -1
  125. package/sidecar/dist/agent/effective-mode.js.map +1 -1
  126. package/sidecar/dist/agent/host-context.js +7 -9
  127. package/sidecar/dist/agent/host-context.js.map +1 -1
  128. package/sidecar/dist/agent/permission-bus.d.ts +1 -1
  129. package/sidecar/dist/agent/permission-bus.js +2 -2
  130. package/sidecar/dist/agent/permission-bus.js.map +1 -1
  131. package/sidecar/dist/agent/provider.d.ts +2 -2
  132. package/sidecar/dist/agent/question-bus.d.ts +1 -1
  133. package/sidecar/dist/agent/question-bus.js +2 -2
  134. package/sidecar/dist/agent/question-bus.js.map +1 -1
  135. package/sidecar/dist/agent/runner.d.ts +2 -2
  136. package/sidecar/dist/agent/runner.js +24 -8
  137. package/sidecar/dist/agent/runner.js.map +1 -1
  138. package/sidecar/dist/agent/session.d.ts +2 -2
  139. package/sidecar/dist/agent/session.js +49 -9
  140. package/sidecar/dist/agent/session.js.map +1 -1
  141. package/sidecar/dist/agent/switching-runner.js +10 -5
  142. package/sidecar/dist/agent/switching-runner.js.map +1 -1
  143. package/sidecar/dist/agent/turn-end-reasons.d.ts +2 -0
  144. package/sidecar/dist/agent/turn-end-reasons.js +6 -1
  145. package/sidecar/dist/agent/turn-end-reasons.js.map +1 -1
  146. package/sidecar/dist/constants/agent.d.ts +4 -1
  147. package/sidecar/dist/constants/agent.js +4 -1
  148. package/sidecar/dist/constants/agent.js.map +1 -1
  149. package/sidecar/dist/constants/cli.d.ts +0 -1
  150. package/sidecar/dist/constants/cli.js +0 -1
  151. package/sidecar/dist/constants/cli.js.map +1 -1
  152. package/sidecar/dist/constants/codex.d.ts +5 -2
  153. package/sidecar/dist/constants/codex.js +5 -2
  154. package/sidecar/dist/constants/codex.js.map +1 -1
  155. package/sidecar/dist/constants/http.d.ts +0 -9
  156. package/sidecar/dist/constants/http.js +0 -19
  157. package/sidecar/dist/constants/http.js.map +1 -1
  158. package/sidecar/dist/constants/mcp.d.ts +1 -1
  159. package/sidecar/dist/constants/mcp.js +1 -1
  160. package/sidecar/dist/constants/mcp.js.map +1 -1
  161. package/sidecar/dist/constants/paths.d.ts +0 -1
  162. package/sidecar/dist/constants/paths.js +0 -1
  163. package/sidecar/dist/constants/paths.js.map +1 -1
  164. package/sidecar/dist/constants/ws.d.ts +3 -4
  165. package/sidecar/dist/constants/ws.js +3 -4
  166. package/sidecar/dist/constants/ws.js.map +1 -1
  167. package/sidecar/dist/index.d.ts +0 -1
  168. package/sidecar/dist/index.js +6 -10
  169. package/sidecar/dist/index.js.map +1 -1
  170. package/sidecar/dist/mcp/tools/find-pages-using.js +5 -10
  171. package/sidecar/dist/mcp/tools/find-pages-using.js.map +1 -1
  172. package/sidecar/dist/mcp/types.d.ts +1 -1
  173. package/sidecar/dist/pages/page-filepath.d.ts +4 -0
  174. package/sidecar/dist/pages/page-filepath.js +28 -0
  175. package/sidecar/dist/pages/page-filepath.js.map +1 -0
  176. package/sidecar/dist/prompts/agent.d.ts +1 -1
  177. package/sidecar/dist/prompts/agent.js +1 -1
  178. package/sidecar/dist/prompts/agent.js.map +1 -1
  179. package/sidecar/dist/protocol/events.js +2 -2
  180. package/sidecar/dist/protocol/events.js.map +1 -1
  181. package/sidecar/dist/protocol/messages.d.ts +4 -15
  182. package/sidecar/dist/protocol/messages.js +4 -11
  183. package/sidecar/dist/protocol/messages.js.map +1 -1
  184. package/sidecar/dist/providers/claude/config.d.ts +2 -2
  185. package/sidecar/dist/providers/claude/config.js +2 -2
  186. package/sidecar/dist/providers/claude/config.js.map +1 -1
  187. package/sidecar/dist/providers/claude/provider.js +1 -1
  188. package/sidecar/dist/providers/claude/provider.js.map +1 -1
  189. package/sidecar/dist/providers/codex/config.d.ts +3 -2
  190. package/sidecar/dist/providers/codex/config.js +3 -3
  191. package/sidecar/dist/providers/codex/config.js.map +1 -1
  192. package/sidecar/dist/providers/codex/process.js +107 -74
  193. package/sidecar/dist/providers/codex/process.js.map +1 -1
  194. package/sidecar/dist/providers/codex/provider.js +68 -31
  195. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  196. package/sidecar/dist/providers/codex/skills.d.ts +1 -0
  197. package/sidecar/dist/providers/codex/skills.js +4 -1
  198. package/sidecar/dist/providers/codex/skills.js.map +1 -1
  199. package/sidecar/dist/providers/codex/stop.d.ts +4 -0
  200. package/sidecar/dist/providers/codex/stop.js +71 -0
  201. package/sidecar/dist/providers/codex/stop.js.map +1 -0
  202. package/sidecar/dist/server/{iframe-socket-registry.d.ts → chat-socket-registry.d.ts} +3 -2
  203. package/sidecar/dist/server/{iframe-socket-registry.js → chat-socket-registry.js} +4 -3
  204. package/sidecar/dist/server/chat-socket-registry.js.map +1 -0
  205. package/sidecar/dist/server/client-auth.js +2 -14
  206. package/sidecar/dist/server/client-auth.js.map +1 -1
  207. package/sidecar/dist/server/host-command-router.d.ts +5 -0
  208. package/sidecar/dist/server/host-command-router.js +4 -0
  209. package/sidecar/dist/server/host-command-router.js.map +1 -0
  210. package/sidecar/dist/server/host-socket-registry.d.ts +1 -1
  211. package/sidecar/dist/server/host-socket-registry.js +16 -13
  212. package/sidecar/dist/server/host-socket-registry.js.map +1 -1
  213. package/sidecar/dist/server/http.d.ts +0 -1
  214. package/sidecar/dist/server/http.js +1 -44
  215. package/sidecar/dist/server/http.js.map +1 -1
  216. package/sidecar/dist/server/routing.d.ts +5 -6
  217. package/sidecar/dist/server/routing.js +18 -28
  218. package/sidecar/dist/server/routing.js.map +1 -1
  219. package/sidecar/dist/server/ws.d.ts +2 -2
  220. package/sidecar/dist/server/ws.js +41 -39
  221. package/sidecar/dist/server/ws.js.map +1 -1
  222. package/sidecar/dist/state/host-state.d.ts +10 -2
  223. package/sidecar/dist/state/host-state.js +13 -3
  224. package/sidecar/dist/state/host-state.js.map +1 -1
  225. package/sidecar/dist/state/settings-store.js +2 -2
  226. package/sidecar/dist/state/settings-store.js.map +1 -1
  227. package/sidecar/dist/state/settings-types.d.ts +3 -3
  228. package/sidecar/dist/state/settings-types.js +1 -1
  229. package/sidecar/dist/state/settings-types.js.map +1 -1
  230. package/sidecar/package.json +3 -5
  231. package/dist/chatbox/passthrough.d.ts +0 -3
  232. package/dist/chatbox/passthrough.js +0 -53
  233. package/dist/chatbox/passthrough.js.map +0 -1
  234. package/dist/constants/chatbox.d.ts +0 -10
  235. package/dist/constants/chatbox.js +0 -35
  236. package/dist/constants/chatbox.js.map +0 -1
  237. package/dist/routes/chatbox.d.ts +0 -7
  238. package/dist/routes/chatbox.js +0 -47
  239. package/dist/routes/chatbox.js.map +0 -1
  240. package/frontend-vue/app/runtime/overlay.ts +0 -400
  241. package/frontend-vue/app/runtime/theme-bridge.ts +0 -189
  242. package/sidecar/chatbox/dist/assets/index-C8u7mkHn.css +0 -1
  243. package/sidecar/chatbox/dist/assets/index-tdZvY7ee.js +0 -52
  244. package/sidecar/chatbox/dist/index.html +0 -13
  245. package/sidecar/dist/server/iframe-socket-registry.js.map +0 -1
@@ -2,199 +2,215 @@ import {
2
2
  type DmsAppContext,
3
3
  defineDmsPlugin,
4
4
  useDmsRouter,
5
- } from '#dms/frontend-module'
5
+ useDmsState,
6
+ } from "#dms/frontend-module";
7
+ import { type Ref, readonly, ref, watch } from "vue";
8
+ import {
9
+ ASSISTANT_SESSION_KEY,
10
+ type AssistantSession,
11
+ } from "../runtime/assistant-session";
6
12
  import {
7
13
  type ChannelClient,
8
14
  createChannelClient,
9
- } from '../runtime/channel-client'
15
+ } from "../runtime/channel-client";
10
16
  import {
17
+ type ChatTransportHub,
11
18
  createChatTransport,
12
- exposeChatTransport,
13
- } from '../runtime/chat-transport'
19
+ } from "../runtime/chat-transport";
14
20
  import {
21
+ APP_OVERLAYS_STATE_KEY,
15
22
  CHANNEL_IDLE_STOP_MS,
16
23
  CHANNEL_STATUS_RECONNECTING,
17
- CHAT_CHANNEL,
18
- CHATBOX_PATH,
24
+ CHAT_PANEL_COMPONENT_NAME,
19
25
  HELLO_MESSAGE_TYPE,
20
- HOST_CHANNEL,
21
26
  HOST_ROLE,
22
27
  JSON_CONTENT_TYPE,
23
28
  SIDECAR_INFO_PATH,
24
29
  SIDECAR_STATUS_CONNECTED,
25
- SIDECAR_STATUS_REVIVING,
26
- SIDECAR_STATUS_UNAVAILABLE,
27
30
  VISIBILITY_CHANGE_EVENT,
28
- } from '../runtime/constants'
31
+ } from "../runtime/constants";
29
32
  import {
30
33
  buildCurrentPageUpdate,
31
34
  installCurrentPageTracker,
32
- } from '../runtime/current-page'
33
- import { registerLauncherAction } from '../runtime/header-action'
35
+ } from "../runtime/current-page";
36
+ import { createFrameRouter } from "../runtime/frame-router";
37
+ import { registerLauncherAction } from "../runtime/header-action";
34
38
  import {
35
39
  createHostCommandDispatcher,
36
40
  type HostCommandDispatcher,
37
- } from '../runtime/host-commands'
38
- import { installNavigationCompleteEmitter } from '../runtime/navigation-complete'
39
- import { injectOverlay, type OverlayHandle } from '../runtime/overlay'
40
- import { runWhenLoggedIn } from '../runtime/session-gate'
41
- import { installToggleShortcut } from '../runtime/shortcuts'
41
+ } from "../runtime/host-commands";
42
+ import { installNavigationCompleteEmitter } from "../runtime/navigation-complete";
43
+ import {
44
+ type ChatPanelState,
45
+ createChatPanelState,
46
+ } from "../runtime/panel-state";
47
+ import { runWhenLoggedIn } from "../runtime/session-gate";
48
+ import { installToggleShortcut } from "../runtime/shortcuts";
42
49
  import {
43
50
  createSidecarStatusController,
44
51
  type SidecarStatus,
45
52
  type SidecarStatusController,
46
- } from '../runtime/sidecar-status'
53
+ } from "../runtime/sidecar-status";
47
54
 
48
- type AuthFetch = ReturnType<typeof useAuthFetch>['$authFetch']
55
+ type AuthFetch = ReturnType<typeof useAuthFetch>["$authFetch"];
49
56
 
50
57
  interface ChannelDeps {
51
- $authFetch: AuthFetch
52
- controller: SidecarStatusController
53
- dispatchHostCommand: HostCommandDispatcher
58
+ $authFetch: AuthFetch;
59
+ controller: SidecarStatusController;
60
+ dispatchHostCommand: HostCommandDispatcher;
54
61
  }
55
62
 
56
- function buildIframeUrl(): string {
57
- // Seed the chatbox with the host's current light/dark mode so its very first
58
- // paint matches; the theme bridge keeps it in sync from there on.
59
- const mode = document.documentElement.classList.contains('dark')
60
- ? 'dark'
61
- : 'light'
62
- return `${CHATBOX_PATH}?theme=${mode}`
63
+ interface AssistantChannel {
64
+ client: ChannelClient;
65
+ chat: ChatTransportHub;
63
66
  }
64
67
 
65
- function createAssistantChannel(deps: ChannelDeps): ChannelClient {
68
+ function createAssistantChannel(deps: ChannelDeps): AssistantChannel {
66
69
  const chat = createChatTransport({
67
- send: (msg) => channel.send(CHAT_CHANNEL, msg),
68
- reconnect: () => channel.reconnectNow(),
69
- getStatus: () => channel.getStatus(),
70
- })
71
- const frameRoutes: Record<string, (raw: string) => void> = {
72
- [HOST_CHANNEL]: deps.dispatchHostCommand,
73
- [CHAT_CHANNEL]: chat.deliverFrame,
74
- }
75
- const channel = createChannelClient({
76
- channels: [HOST_CHANNEL, CHAT_CHANNEL],
70
+ send: (msg) => client.send(msg),
71
+ reconnect: () => client.reconnectNow(),
72
+ getStatus: () => client.getStatus(),
73
+ });
74
+ const client = createChannelClient({
77
75
  post: (path, body) =>
78
76
  deps.$authFetch(path, {
79
- method: 'POST',
77
+ method: "POST",
80
78
  body: JSON.stringify(body),
81
- headers: { 'content-type': JSON_CONTENT_TYPE },
79
+ headers: { "content-type": JSON_CONTENT_TYPE },
82
80
  }),
83
81
  onReady: () => {
84
- channel.send(HOST_CHANNEL, { type: HELLO_MESSAGE_TYPE, role: HOST_ROLE })
85
- channel.send(HOST_CHANNEL, buildCurrentPageUpdate(window.location.href))
86
- chat.announceReady()
82
+ client.send({ type: HELLO_MESSAGE_TYPE, role: HOST_ROLE });
83
+ client.send(buildCurrentPageUpdate(window.location.href));
84
+ chat.announceReady();
87
85
  },
88
- onFrame: (name, raw) => frameRoutes[name]?.(raw),
86
+ onFrame: createFrameRouter({
87
+ host: deps.dispatchHostCommand,
88
+ chat: chat.deliver,
89
+ }),
89
90
  onStatusChange: (status) => {
90
- chat.announceStatus(status)
91
- // The sidecar went away, or dms-ai reloaded: re-probe, which revives
92
- // an idle-exited sidecar, rather than only waiting on the retries.
93
- if (status === CHANNEL_STATUS_RECONNECTING) deps.controller.reportChannelDown()
91
+ chat.announceStatus(status);
92
+ if (status === CHANNEL_STATUS_RECONNECTING)
93
+ deps.controller.reportChannelDown();
94
94
  },
95
- })
96
- exposeChatTransport(chat.transport)
97
- return channel
95
+ });
96
+ return { client, chat };
98
97
  }
99
98
 
100
- function followPanel(overlay: OverlayHandle, channel: ChannelClient): () => void {
101
- let stopTimer: ReturnType<typeof setTimeout> | null = null
99
+ function followPanel(panel: ChatPanelState, client: ChannelClient): () => void {
100
+ let stopTimer: ReturnType<typeof setTimeout> | null = null;
102
101
  const clearStopTimer = (): void => {
103
- if (stopTimer !== null) clearTimeout(stopTimer)
104
- stopTimer = null
105
- }
102
+ if (stopTimer !== null) clearTimeout(stopTimer);
103
+ stopTimer = null;
104
+ };
106
105
  const follow = (): void => {
107
- clearStopTimer()
108
- const isNeeded = overlay.isOpen() && document.visibilityState === 'visible'
109
- if (isNeeded) channel.start()
110
- else stopTimer = setTimeout(() => channel.stop(), CHANNEL_IDLE_STOP_MS)
111
- }
112
- const stopOpenChange = overlay.onOpenChange(follow)
113
- document.addEventListener(VISIBILITY_CHANGE_EVENT, follow)
114
- follow()
106
+ clearStopTimer();
107
+ const isNeeded =
108
+ panel.isOpen.value && document.visibilityState === "visible";
109
+ if (isNeeded) client.start();
110
+ else stopTimer = setTimeout(() => client.stop(), CHANNEL_IDLE_STOP_MS);
111
+ };
112
+ const stopWatchingPanel = watch(panel.isOpen, follow);
113
+ document.addEventListener(VISIBILITY_CHANGE_EVENT, follow);
114
+ follow();
115
115
  return () => {
116
- clearStopTimer()
117
- stopOpenChange()
118
- document.removeEventListener(VISIBILITY_CHANGE_EVENT, follow)
119
- }
116
+ clearStopTimer();
117
+ stopWatchingPanel();
118
+ document.removeEventListener(VISIBILITY_CHANGE_EVENT, follow);
119
+ };
120
+ }
121
+
122
+ interface FollowedSidecarStatus {
123
+ status: Readonly<Ref<SidecarStatus>>;
124
+ stop: () => void;
120
125
  }
121
126
 
122
- function statusApplier(
123
- overlay: OverlayHandle,
124
- channel: ChannelClient,
125
- ): (status: SidecarStatus) => void {
126
- let firstApply = true
127
- return (status) => {
128
- if (status === SIDECAR_STATUS_CONNECTED) {
129
- overlay.repoint(buildIframeUrl())
130
- // Only a connect after a drop needs to skip the channel's backoff.
131
- if (!firstApply) channel.reconnectNow()
132
- } else if (status === SIDECAR_STATUS_REVIVING) {
133
- overlay.showPlaceholder('reviving')
134
- } else if (status === SIDECAR_STATUS_UNAVAILABLE) {
135
- overlay.showPlaceholder('unavailable')
136
- } else {
137
- overlay.showPlaceholder('connecting')
138
- }
139
- firstApply = false
140
- }
127
+ function followSidecarStatus(
128
+ controller: SidecarStatusController,
129
+ client: ChannelClient,
130
+ ): FollowedSidecarStatus {
131
+ const status = ref<SidecarStatus>(controller.getStatus());
132
+ let isFirstReport = true;
133
+ const stop = controller.subscribe((next) => {
134
+ if (next === SIDECAR_STATUS_CONNECTED && !isFirstReport)
135
+ client.reconnectNow();
136
+ status.value = next;
137
+ isFirstReport = false;
138
+ });
139
+ return { status: readonly(status), stop };
141
140
  }
142
141
 
143
- function installHostState(channel: ChannelClient): () => void {
142
+ function installHostState(client: ChannelClient): () => void {
144
143
  const sendWhenConnected = (msg: unknown): void => {
145
- if (!channel.isConnected()) return
146
- channel.send(HOST_CHANNEL, msg)
147
- }
148
- const stopTracking = installCurrentPageTracker({ send: sendWhenConnected })
144
+ if (!client.isConnected()) return;
145
+ client.send(msg);
146
+ };
147
+ const stopTracking = installCurrentPageTracker({ send: sendWhenConnected });
149
148
  const stopNavigation = installNavigationCompleteEmitter({
150
149
  send: sendWhenConnected,
151
- })
150
+ });
152
151
  return () => {
153
- stopTracking()
154
- stopNavigation()
155
- }
152
+ stopTracking();
153
+ stopNavigation();
154
+ };
155
+ }
156
+
157
+ function registerChatPanel(): void {
158
+ const overlays = useDmsState<string[]>(APP_OVERLAYS_STATE_KEY, () => []);
159
+ if (overlays.value.includes(CHAT_PANEL_COMPONENT_NAME)) return;
160
+ overlays.value = [...overlays.value, CHAT_PANEL_COMPONENT_NAME];
156
161
  }
157
162
 
158
163
  async function startAssistant({ vueApp }: DmsAppContext): Promise<void> {
159
- const { $authFetch } = useAuthFetch()
164
+ const { $authFetch } = useAuthFetch();
160
165
  const controller = createSidecarStatusController(() =>
161
166
  $authFetch(SIDECAR_INFO_PATH),
162
- )
163
- // A null/disabled first probe means the assistant shouldn't appear at all —
164
- // preserve the previous "no icon, no overlay" behavior in that case.
165
- if (!(await controller.init())) return
166
- const overlay = injectOverlay()
167
- if (overlay === null) return
168
- installToggleShortcut({ onToggle: () => overlay.toggleOpen() })
169
- // Registered unconditionally (even while reviving) so the launcher is present
170
- // through an outage; opening it surfaces the panel's own status placeholder.
171
- registerLauncherAction(() => overlay.toggleFromLauncher())
172
- // Host-side dev hot reload: a page the agent just wrote is re-registered
173
- // asynchronously, so each navigation waits for the layout to serve it, one at
174
- // a time so a burst of commands cannot land out of order.
167
+ );
168
+ if (!(await controller.init())) return;
169
+ const router = useDmsRouter();
175
170
  const dispatchHostCommand = createHostCommandDispatcher({
176
- router: useDmsRouter(),
171
+ router,
177
172
  devReload: useDmsDevReload(),
178
- })
179
- const channel = createAssistantChannel({ $authFetch, controller, dispatchHostCommand })
180
- controller.subscribe(statusApplier(overlay, channel))
181
- const stopFollowing = followPanel(overlay, channel)
182
- const stopHostState = installHostState(channel)
173
+ });
174
+ const { client, chat } = createAssistantChannel({
175
+ $authFetch,
176
+ controller,
177
+ dispatchHostCommand,
178
+ });
179
+ const panel = createChatPanelState();
180
+ const sidecarStatus = followSidecarStatus(controller, client);
181
+ const session: AssistantSession = {
182
+ status: sidecarStatus.status,
183
+ chat: chat.transport,
184
+ panel,
185
+ navigate: (path) => void router.push(path),
186
+ };
187
+ vueApp.provide(ASSISTANT_SESSION_KEY, session);
188
+ registerChatPanel();
189
+ installToggleShortcut({ onToggle: panel.toggle });
190
+ registerLauncherAction(panel.toggleFromLauncher);
191
+ const stopFollowing = followPanel(panel, client);
192
+ const stopHostState = installHostState(client);
183
193
  const teardown = (): void => {
184
- stopFollowing()
185
- stopHostState()
186
- channel.stop()
187
- }
188
- vueApp.onUnmount(teardown)
189
- import.meta.hot?.dispose(teardown)
194
+ stopFollowing();
195
+ stopHostState();
196
+ sidecarStatus.stop();
197
+ controller.dispose();
198
+ client.stop();
199
+ };
200
+ vueApp.onUnmount(teardown);
201
+ import.meta.hot?.dispose(teardown);
190
202
  }
191
203
 
204
+ /**
205
+ * The assistant appears for a signed-in owner in development only: a first
206
+ * probe that finds no dms-ai backend, or a disabled sidecar, leaves no trace.
207
+ */
192
208
  export default defineDmsPlugin((context) => {
193
- if (!import.meta.env.DEV) return
194
- const { loggedIn } = useUserSession()
209
+ if (!import.meta.env.DEV) return;
210
+ const { loggedIn } = useUserSession();
195
211
  const stopGate = runWhenLoggedIn(
196
212
  () => loggedIn.value,
197
213
  () => void context.runWithContext(() => startAssistant(context)),
198
- )
199
- context.vueApp.onUnmount(stopGate)
200
- })
214
+ );
215
+ context.vueApp.onUnmount(stopGate);
216
+ });
@@ -0,0 +1,21 @@
1
+ import type { InjectionKey, Ref } from "vue";
2
+ import type { ChatTransport } from "./chat-transport";
3
+ import type { ChatPanelState } from "./panel-state";
4
+ import type { SidecarStatus } from "./sidecar-status";
5
+
6
+ /**
7
+ * What the chat panel needs from the plugin that owns the tab's assistant: the
8
+ * sidecar's reachability, the chat's side of the tab's stream, the panel's own
9
+ * state, and the dashboard's router.
10
+ */
11
+ export interface AssistantSession {
12
+ status: Readonly<Ref<SidecarStatus>>;
13
+ chat: ChatTransport;
14
+ panel: ChatPanelState;
15
+ /** Opens a page of the dashboard, as its own links do. */
16
+ navigate: (path: string) => void;
17
+ }
18
+
19
+ export const ASSISTANT_SESSION_KEY: InjectionKey<AssistantSession> = Symbol(
20
+ "dms-ai:assistant-session",
21
+ );
@@ -1,30 +1,27 @@
1
1
  export interface BackoffState {
2
- index: number
2
+ index: number;
3
3
  }
4
4
 
5
5
  export function createBackoff(): BackoffState {
6
- return { index: 0 }
6
+ return { index: 0 };
7
7
  }
8
8
 
9
- function readScheduleAt(
10
- schedule: readonly number[],
11
- index: number,
12
- ): number {
13
- const value = schedule[index]
14
- if (value === undefined) return 0
15
- return value
9
+ function readScheduleAt(schedule: readonly number[], index: number): number {
10
+ const value = schedule[index];
11
+ if (value === undefined) return 0;
12
+ return value;
16
13
  }
17
14
 
18
15
  export function nextDelay(
19
16
  state: BackoffState,
20
17
  schedule: readonly number[],
21
18
  ): number {
22
- const cappedIndex = Math.min(state.index, schedule.length - 1)
23
- const delay = readScheduleAt(schedule, cappedIndex)
24
- state.index += 1
25
- return delay
19
+ const cappedIndex = Math.min(state.index, schedule.length - 1);
20
+ const delay = readScheduleAt(schedule, cappedIndex);
21
+ state.index += 1;
22
+ return delay;
26
23
  }
27
24
 
28
25
  export function resetBackoff(state: BackoffState): void {
29
- state.index = 0
26
+ state.index = 0;
30
27
  }