@antelopejs/dms-ai 0.1.4 → 0.1.6

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 (250) hide show
  1. package/README.md +55 -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 +5 -1
  18. package/dist/constants/sidecar.js +6 -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-owner.d.ts +13 -0
  26. package/dist/lifecycle/sidecar-owner.js +89 -0
  27. package/dist/lifecycle/sidecar-owner.js.map +1 -0
  28. package/dist/lifecycle/sidecar-socket.d.ts +1 -1
  29. package/dist/lifecycle/sidecar-socket.js +2 -2
  30. package/dist/lifecycle/sidecar-socket.js.map +1 -1
  31. package/dist/lifecycle/spawn-sidecar.d.ts +0 -1
  32. package/dist/lifecycle/spawn-sidecar.js +51 -159
  33. package/dist/lifecycle/spawn-sidecar.js.map +1 -1
  34. package/dist/lifecycle/terminate-process.d.ts +1 -0
  35. package/dist/lifecycle/terminate-process.js +34 -0
  36. package/dist/lifecycle/terminate-process.js.map +1 -0
  37. package/dist/routes/channels.d.ts +3 -3
  38. package/dist/routes/channels.js +11 -12
  39. package/dist/routes/channels.js.map +1 -1
  40. package/dist/routes/index.d.ts +0 -1
  41. package/dist/routes/index.js +0 -1
  42. package/dist/routes/index.js.map +1 -1
  43. package/frontend-vue/.prettierignore +3 -0
  44. package/frontend-vue/.prettierrc +10 -0
  45. package/frontend-vue/app/chat/components/ChatView.vue +582 -0
  46. package/frontend-vue/app/chat/components/ComposerInput.vue +742 -0
  47. package/frontend-vue/app/chat/components/ConnectionBanner.vue +105 -0
  48. package/frontend-vue/app/chat/components/ConversationDrawer.vue +266 -0
  49. package/frontend-vue/app/chat/components/MarkdownContent.vue +196 -0
  50. package/frontend-vue/app/chat/components/MessageList.vue +474 -0
  51. package/frontend-vue/app/chat/components/PermissionRequests.vue +323 -0
  52. package/frontend-vue/app/chat/components/QuestionPrompt.vue +275 -0
  53. package/frontend-vue/app/chat/components/QueuedMessages.vue +109 -0
  54. package/frontend-vue/app/chat/components/TodoList.vue +291 -0
  55. package/frontend-vue/app/chat/components/ToolCallEntry.vue +218 -0
  56. package/frontend-vue/app/chat/components/ToolCluster.vue +282 -0
  57. package/frontend-vue/app/chat/composables/useChatChannel.ts +116 -0
  58. package/frontend-vue/app/chat/composables/useConversation.ts +838 -0
  59. package/frontend-vue/app/chat/composables/useConversationId.ts +34 -0
  60. package/frontend-vue/app/chat/composables/useConversationList.ts +56 -0
  61. package/frontend-vue/app/chat/composables/usePermissionQueue.ts +59 -0
  62. package/frontend-vue/app/chat/composables/useQuestionQueue.ts +50 -0
  63. package/frontend-vue/app/chat/composables/useRunClock.ts +28 -0
  64. package/frontend-vue/app/chat/composables/useSettings.ts +67 -0
  65. package/frontend-vue/app/chat/constants/attachments.ts +16 -0
  66. package/frontend-vue/app/chat/constants/connection-banner.ts +7 -0
  67. package/frontend-vue/app/chat/constants/conversation-drawer.ts +13 -0
  68. package/frontend-vue/app/chat/constants/conversation.ts +75 -0
  69. package/frontend-vue/app/chat/constants/permissions.ts +50 -0
  70. package/frontend-vue/app/chat/constants/protocol.ts +48 -0
  71. package/frontend-vue/app/chat/constants/run-status.ts +50 -0
  72. package/frontend-vue/app/chat/constants/settings.ts +60 -0
  73. package/frontend-vue/app/chat/types/conversation.ts +87 -0
  74. package/frontend-vue/app/chat/types/permission.ts +7 -0
  75. package/frontend-vue/app/chat/types/question.ts +17 -0
  76. package/frontend-vue/app/chat/types/settings.ts +27 -0
  77. package/frontend-vue/app/chat/utils/attachments.ts +62 -0
  78. package/frontend-vue/app/chat/utils/format.ts +14 -0
  79. package/frontend-vue/app/chat/utils/ids.ts +34 -0
  80. package/frontend-vue/app/chat/utils/markdown.ts +188 -0
  81. package/frontend-vue/app/chat/utils/run-status.ts +90 -0
  82. package/frontend-vue/app/chat/utils/todos.ts +64 -0
  83. package/frontend-vue/app/chat/utils/tool-summary.ts +53 -0
  84. package/frontend-vue/app/components/ActivityView.vue +34 -34
  85. package/frontend-vue/app/components/ChatPanel.vue +276 -0
  86. package/frontend-vue/app/components/SettingsView.vue +120 -112
  87. package/frontend-vue/app/components/SkillsView.vue +55 -53
  88. package/frontend-vue/app/plugins/ai.client.ts +145 -129
  89. package/frontend-vue/app/runtime/assistant-session.ts +21 -0
  90. package/frontend-vue/app/runtime/backoff.ts +11 -14
  91. package/frontend-vue/app/runtime/channel-client.ts +130 -141
  92. package/frontend-vue/app/runtime/chat-transport.ts +25 -55
  93. package/frontend-vue/app/runtime/constants.ts +69 -59
  94. package/frontend-vue/app/runtime/current-page.ts +49 -15
  95. package/frontend-vue/app/runtime/event-stream.ts +35 -35
  96. package/frontend-vue/app/runtime/frame-router.ts +30 -0
  97. package/frontend-vue/app/runtime/header-action.ts +18 -18
  98. package/frontend-vue/app/runtime/host-commands.ts +57 -56
  99. package/frontend-vue/app/runtime/listeners.ts +31 -0
  100. package/frontend-vue/app/runtime/navigation-complete.ts +7 -7
  101. package/frontend-vue/app/runtime/overlay-prefs.ts +36 -36
  102. package/frontend-vue/app/runtime/panel-state.ts +88 -0
  103. package/frontend-vue/app/runtime/session-gate.ts +8 -8
  104. package/frontend-vue/app/runtime/shortcuts.ts +12 -12
  105. package/frontend-vue/app/runtime/sidecar-status.ts +61 -61
  106. package/frontend-vue/app/runtime/typed-message.ts +11 -0
  107. package/frontend-vue/dms.frontend.ts +12 -12
  108. package/frontend-vue/eslint.config.mjs +13 -13
  109. package/frontend-vue/package.json +11 -4
  110. package/frontend-vue/pnpm-lock.yaml +298 -96
  111. package/frontend-vue/tests/backoff.test.ts +26 -30
  112. package/frontend-vue/tests/channel-client.test.ts +187 -188
  113. package/frontend-vue/tests/chat/markdown.test.ts +271 -0
  114. package/frontend-vue/tests/chat/useChatChannel.test.ts +124 -0
  115. package/frontend-vue/tests/chat/useConversation.test.ts +283 -0
  116. package/frontend-vue/tests/chat-panel.test.ts +354 -0
  117. package/frontend-vue/tests/chat-transport.test.ts +84 -57
  118. package/frontend-vue/tests/host-commands.test.ts +126 -87
  119. package/frontend-vue/tests/navigation.test.ts +65 -34
  120. package/frontend-vue/tests/overlay-prefs.test.ts +49 -49
  121. package/frontend-vue/tests/panel-state.test.ts +58 -0
  122. package/frontend-vue/tests/session-gate.test.ts +33 -33
  123. package/frontend-vue/tests/sidecar-guest.test.ts +46 -46
  124. package/frontend-vue/tests/sidecar-status.test.ts +29 -29
  125. package/frontend-vue/tsconfig.chat.json +16 -0
  126. package/frontend-vue/vitest.config.ts +6 -4
  127. package/package.json +6 -5
  128. package/sidecar/dist/agent/effective-mode.d.ts +2 -2
  129. package/sidecar/dist/agent/effective-mode.js +1 -1
  130. package/sidecar/dist/agent/effective-mode.js.map +1 -1
  131. package/sidecar/dist/agent/host-context.js +7 -9
  132. package/sidecar/dist/agent/host-context.js.map +1 -1
  133. package/sidecar/dist/agent/permission-bus.d.ts +1 -1
  134. package/sidecar/dist/agent/permission-bus.js +2 -2
  135. package/sidecar/dist/agent/permission-bus.js.map +1 -1
  136. package/sidecar/dist/agent/provider.d.ts +2 -2
  137. package/sidecar/dist/agent/question-bus.d.ts +1 -1
  138. package/sidecar/dist/agent/question-bus.js +2 -2
  139. package/sidecar/dist/agent/question-bus.js.map +1 -1
  140. package/sidecar/dist/agent/runner.d.ts +2 -2
  141. package/sidecar/dist/agent/runner.js +24 -8
  142. package/sidecar/dist/agent/runner.js.map +1 -1
  143. package/sidecar/dist/agent/session.d.ts +2 -2
  144. package/sidecar/dist/agent/session.js +49 -9
  145. package/sidecar/dist/agent/session.js.map +1 -1
  146. package/sidecar/dist/agent/switching-runner.js +10 -5
  147. package/sidecar/dist/agent/switching-runner.js.map +1 -1
  148. package/sidecar/dist/agent/turn-end-reasons.d.ts +2 -0
  149. package/sidecar/dist/agent/turn-end-reasons.js +6 -1
  150. package/sidecar/dist/agent/turn-end-reasons.js.map +1 -1
  151. package/sidecar/dist/constants/agent.d.ts +4 -1
  152. package/sidecar/dist/constants/agent.js +4 -1
  153. package/sidecar/dist/constants/agent.js.map +1 -1
  154. package/sidecar/dist/constants/cli.d.ts +0 -1
  155. package/sidecar/dist/constants/cli.js +0 -1
  156. package/sidecar/dist/constants/cli.js.map +1 -1
  157. package/sidecar/dist/constants/codex.d.ts +5 -2
  158. package/sidecar/dist/constants/codex.js +5 -2
  159. package/sidecar/dist/constants/codex.js.map +1 -1
  160. package/sidecar/dist/constants/http.d.ts +0 -9
  161. package/sidecar/dist/constants/http.js +0 -19
  162. package/sidecar/dist/constants/http.js.map +1 -1
  163. package/sidecar/dist/constants/mcp.d.ts +1 -1
  164. package/sidecar/dist/constants/mcp.js +1 -1
  165. package/sidecar/dist/constants/mcp.js.map +1 -1
  166. package/sidecar/dist/constants/paths.d.ts +0 -1
  167. package/sidecar/dist/constants/paths.js +0 -1
  168. package/sidecar/dist/constants/paths.js.map +1 -1
  169. package/sidecar/dist/constants/ws.d.ts +3 -4
  170. package/sidecar/dist/constants/ws.js +3 -4
  171. package/sidecar/dist/constants/ws.js.map +1 -1
  172. package/sidecar/dist/index.d.ts +4 -1
  173. package/sidecar/dist/index.js +13 -13
  174. package/sidecar/dist/index.js.map +1 -1
  175. package/sidecar/dist/mcp/tools/find-pages-using.js +5 -10
  176. package/sidecar/dist/mcp/tools/find-pages-using.js.map +1 -1
  177. package/sidecar/dist/mcp/types.d.ts +1 -1
  178. package/sidecar/dist/pages/page-filepath.d.ts +4 -0
  179. package/sidecar/dist/pages/page-filepath.js +28 -0
  180. package/sidecar/dist/pages/page-filepath.js.map +1 -0
  181. package/sidecar/dist/prompts/agent.d.ts +1 -1
  182. package/sidecar/dist/prompts/agent.js +1 -1
  183. package/sidecar/dist/prompts/agent.js.map +1 -1
  184. package/sidecar/dist/protocol/events.js +2 -2
  185. package/sidecar/dist/protocol/events.js.map +1 -1
  186. package/sidecar/dist/protocol/messages.d.ts +4 -15
  187. package/sidecar/dist/protocol/messages.js +4 -11
  188. package/sidecar/dist/protocol/messages.js.map +1 -1
  189. package/sidecar/dist/providers/claude/config.d.ts +2 -2
  190. package/sidecar/dist/providers/claude/config.js +2 -2
  191. package/sidecar/dist/providers/claude/config.js.map +1 -1
  192. package/sidecar/dist/providers/claude/provider.js +1 -1
  193. package/sidecar/dist/providers/claude/provider.js.map +1 -1
  194. package/sidecar/dist/providers/codex/config.d.ts +3 -2
  195. package/sidecar/dist/providers/codex/config.js +3 -3
  196. package/sidecar/dist/providers/codex/config.js.map +1 -1
  197. package/sidecar/dist/providers/codex/process.js +107 -74
  198. package/sidecar/dist/providers/codex/process.js.map +1 -1
  199. package/sidecar/dist/providers/codex/provider.js +68 -31
  200. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  201. package/sidecar/dist/providers/codex/skills.d.ts +1 -0
  202. package/sidecar/dist/providers/codex/skills.js +4 -1
  203. package/sidecar/dist/providers/codex/skills.js.map +1 -1
  204. package/sidecar/dist/providers/codex/stop.d.ts +4 -0
  205. package/sidecar/dist/providers/codex/stop.js +71 -0
  206. package/sidecar/dist/providers/codex/stop.js.map +1 -0
  207. package/sidecar/dist/server/{iframe-socket-registry.d.ts → chat-socket-registry.d.ts} +3 -2
  208. package/sidecar/dist/server/{iframe-socket-registry.js → chat-socket-registry.js} +4 -3
  209. package/sidecar/dist/server/chat-socket-registry.js.map +1 -0
  210. package/sidecar/dist/server/client-auth.js +2 -14
  211. package/sidecar/dist/server/client-auth.js.map +1 -1
  212. package/sidecar/dist/server/host-command-router.d.ts +5 -0
  213. package/sidecar/dist/server/host-command-router.js +4 -0
  214. package/sidecar/dist/server/host-command-router.js.map +1 -0
  215. package/sidecar/dist/server/host-socket-registry.d.ts +1 -1
  216. package/sidecar/dist/server/host-socket-registry.js +16 -13
  217. package/sidecar/dist/server/host-socket-registry.js.map +1 -1
  218. package/sidecar/dist/server/http.d.ts +0 -1
  219. package/sidecar/dist/server/http.js +1 -44
  220. package/sidecar/dist/server/http.js.map +1 -1
  221. package/sidecar/dist/server/routing.d.ts +5 -6
  222. package/sidecar/dist/server/routing.js +18 -28
  223. package/sidecar/dist/server/routing.js.map +1 -1
  224. package/sidecar/dist/server/ws.d.ts +2 -2
  225. package/sidecar/dist/server/ws.js +41 -39
  226. package/sidecar/dist/server/ws.js.map +1 -1
  227. package/sidecar/dist/state/host-state.d.ts +10 -2
  228. package/sidecar/dist/state/host-state.js +13 -3
  229. package/sidecar/dist/state/host-state.js.map +1 -1
  230. package/sidecar/dist/state/settings-store.js +2 -2
  231. package/sidecar/dist/state/settings-store.js.map +1 -1
  232. package/sidecar/dist/state/settings-types.d.ts +3 -3
  233. package/sidecar/dist/state/settings-types.js +1 -1
  234. package/sidecar/dist/state/settings-types.js.map +1 -1
  235. package/sidecar/package.json +3 -5
  236. package/dist/chatbox/passthrough.d.ts +0 -3
  237. package/dist/chatbox/passthrough.js +0 -53
  238. package/dist/chatbox/passthrough.js.map +0 -1
  239. package/dist/constants/chatbox.d.ts +0 -10
  240. package/dist/constants/chatbox.js +0 -35
  241. package/dist/constants/chatbox.js.map +0 -1
  242. package/dist/routes/chatbox.d.ts +0 -7
  243. package/dist/routes/chatbox.js +0 -47
  244. package/dist/routes/chatbox.js.map +0 -1
  245. package/frontend-vue/app/runtime/overlay.ts +0 -400
  246. package/frontend-vue/app/runtime/theme-bridge.ts +0 -189
  247. package/sidecar/chatbox/dist/assets/index-C8u7mkHn.css +0 -1
  248. package/sidecar/chatbox/dist/assets/index-tdZvY7ee.js +0 -52
  249. package/sidecar/chatbox/dist/index.html +0 -13
  250. package/sidecar/dist/server/iframe-socket-registry.js.map +0 -1
@@ -0,0 +1,88 @@
1
+ import { type Ref, readonly, ref } from "vue";
2
+ import {
3
+ OVERLAY_DEFAULT_WIDTH_PX,
4
+ OVERLAY_MIN_WIDTH_PX,
5
+ OVERLAY_OUTSIDE_TOGGLE_SUPPRESS_MS,
6
+ } from "./constants";
7
+ import {
8
+ type OverlayPrefs,
9
+ readPrefs,
10
+ writePrefsDebounced,
11
+ } from "./overlay-prefs";
12
+
13
+ const MAX_WIDTH_VW_RATIO = 0.94;
14
+
15
+ /** Whether the chat panel is open and how wide it is, remembered per browser. */
16
+ export interface ChatPanelState {
17
+ isOpen: Readonly<Ref<boolean>>;
18
+ width: Readonly<Ref<number>>;
19
+ /** The keyboard shortcut's toggle: never suppressed. */
20
+ toggle: () => void;
21
+ /**
22
+ * The header launcher's toggle. It ignores an open request right after an
23
+ * outside click closed the panel: the launcher's `click` follows the same
24
+ * `pointerdown` that closed it, and would open it again at once.
25
+ */
26
+ toggleFromLauncher: () => void;
27
+ close: () => void;
28
+ /** An outside click closed the panel; `timeStamp` is the event's. */
29
+ closeFromOutside: (timeStamp: number) => void;
30
+ /** Resizes live, between the minimum width and most of the viewport. */
31
+ resize: (width: number) => void;
32
+ /** Remembers the width once a resize is over. */
33
+ commitWidth: () => void;
34
+ }
35
+
36
+ function maxWidth(): number {
37
+ const viewportWidth = globalThis.innerWidth ?? OVERLAY_DEFAULT_WIDTH_PX * 3;
38
+ return Math.max(
39
+ OVERLAY_MIN_WIDTH_PX,
40
+ Math.round(viewportWidth * MAX_WIDTH_VW_RATIO),
41
+ );
42
+ }
43
+
44
+ export function clampWidth(width: number): number {
45
+ return Math.min(Math.max(width, OVERLAY_MIN_WIDTH_PX), maxWidth());
46
+ }
47
+
48
+ function resolveInitialPrefs(): OverlayPrefs {
49
+ const stored = readPrefs();
50
+ if (stored === null)
51
+ return { width: OVERLAY_DEFAULT_WIDTH_PX, isOpen: false };
52
+ return { width: clampWidth(stored.width), isOpen: stored.isOpen };
53
+ }
54
+
55
+ function isRightAfter(timeStamp: number): boolean {
56
+ return performance.now() - timeStamp < OVERLAY_OUTSIDE_TOGGLE_SUPPRESS_MS;
57
+ }
58
+
59
+ export function createChatPanelState(): ChatPanelState {
60
+ const initial = resolveInitialPrefs();
61
+ const isOpen = ref(initial.isOpen);
62
+ const width = ref(initial.width);
63
+ let outsideCloseAt = Number.NEGATIVE_INFINITY;
64
+ const persist = (): void =>
65
+ writePrefsDebounced({ width: width.value, isOpen: isOpen.value });
66
+ const setOpen = (next: boolean): void => {
67
+ isOpen.value = next;
68
+ persist();
69
+ };
70
+ return {
71
+ isOpen: readonly(isOpen),
72
+ width: readonly(width),
73
+ toggle: () => setOpen(!isOpen.value),
74
+ toggleFromLauncher: () => {
75
+ if (!isOpen.value && isRightAfter(outsideCloseAt)) return;
76
+ setOpen(!isOpen.value);
77
+ },
78
+ close: () => setOpen(false),
79
+ closeFromOutside: (timeStamp) => {
80
+ outsideCloseAt = timeStamp;
81
+ setOpen(false);
82
+ },
83
+ resize: (next) => {
84
+ width.value = clampWidth(next);
85
+ },
86
+ commitWidth: persist,
87
+ };
88
+ }
@@ -1,4 +1,4 @@
1
- import { watch } from 'vue'
1
+ import { watch } from "vue";
2
2
 
3
3
  // Plugins run once, when the Vue app boots, on whatever page the browser landed
4
4
  // on. Landing on the sign-in screen means there is no session yet, and signing
@@ -10,13 +10,13 @@ export function runWhenLoggedIn(
10
10
  start: () => void,
11
11
  ): () => void {
12
12
  if (isLoggedIn()) {
13
- start()
14
- return () => undefined
13
+ start();
14
+ return () => undefined;
15
15
  }
16
16
  const stop = watch(isLoggedIn, (loggedIn) => {
17
- if (!loggedIn) return
18
- stop()
19
- start()
20
- })
21
- return stop
17
+ if (!loggedIn) return;
18
+ stop();
19
+ start();
20
+ });
21
+ return stop;
22
22
  }
@@ -1,28 +1,28 @@
1
- import { MODAL_OPEN_SELECTOR, TOGGLE_SHORTCUT_KEY } from './constants'
1
+ import { MODAL_OPEN_SELECTOR, TOGGLE_SHORTCUT_KEY } from "./constants";
2
2
 
3
3
  export interface InstallToggleShortcutOptions {
4
- onToggle: () => void
4
+ onToggle: () => void;
5
5
  }
6
6
 
7
7
  function isModalOpen(): boolean {
8
- return document.querySelector(MODAL_OPEN_SELECTOR) !== null
8
+ return document.querySelector(MODAL_OPEN_SELECTOR) !== null;
9
9
  }
10
10
 
11
11
  function isToggleCombo(event: KeyboardEvent): boolean {
12
- const isModifierPressed = event.metaKey || event.ctrlKey
13
- const isTargetKey = event.key.toLowerCase() === TOGGLE_SHORTCUT_KEY
12
+ const isModifierPressed = event.metaKey || event.ctrlKey;
13
+ const isTargetKey = event.key.toLowerCase() === TOGGLE_SHORTCUT_KEY;
14
14
  // Require shift too — plain ctrl/cmd + k is the DMS search palette.
15
- return isModifierPressed && event.shiftKey && isTargetKey
15
+ return isModifierPressed && event.shiftKey && isTargetKey;
16
16
  }
17
17
 
18
18
  export function installToggleShortcut(
19
19
  options: InstallToggleShortcutOptions,
20
20
  ): void {
21
21
  const handler = (event: KeyboardEvent): void => {
22
- if (!isToggleCombo(event)) return
23
- if (isModalOpen()) return
24
- event.preventDefault()
25
- options.onToggle()
26
- }
27
- document.addEventListener('keydown', handler)
22
+ if (!isToggleCombo(event)) return;
23
+ if (isModalOpen()) return;
24
+ event.preventDefault();
25
+ options.onToggle();
26
+ };
27
+ document.addEventListener("keydown", handler);
28
28
  }
@@ -1,148 +1,148 @@
1
- import { createBackoff, nextDelay, resetBackoff } from './backoff'
1
+ import { createBackoff, nextDelay, resetBackoff } from "./backoff";
2
2
  import {
3
3
  SIDECAR_POLL_DELAYS_MS,
4
4
  SIDECAR_STATUS_CONNECTED,
5
5
  SIDECAR_STATUS_CONNECTING,
6
6
  SIDECAR_STATUS_REVIVING,
7
7
  SIDECAR_STATUS_UNAVAILABLE,
8
- } from './constants'
8
+ } from "./constants";
9
9
 
10
10
  export type SidecarStatus =
11
11
  | typeof SIDECAR_STATUS_CONNECTING
12
12
  | typeof SIDECAR_STATUS_CONNECTED
13
13
  | typeof SIDECAR_STATUS_REVIVING
14
- | typeof SIDECAR_STATUS_UNAVAILABLE
14
+ | typeof SIDECAR_STATUS_UNAVAILABLE;
15
15
 
16
16
  interface SidecarInfo {
17
- isRunning: boolean
18
- hasGivenUp: boolean
19
- disabled: boolean
17
+ isRunning: boolean;
18
+ hasGivenUp: boolean;
19
+ disabled: boolean;
20
20
  }
21
21
 
22
22
  export interface SidecarStatusController {
23
23
  // Runs the first probe. Returns false when the assistant should not be shown
24
24
  // at all — the dms-ai backend route is absent (probe unreachable) or spawning
25
25
  // is disabled for this process — so the caller skips injection entirely.
26
- init: () => Promise<boolean>
27
- getStatus: () => SidecarStatus
26
+ init: () => Promise<boolean>;
27
+ getStatus: () => SidecarStatus;
28
28
  // Subscribe to status changes; fires immediately with the current status so a
29
29
  // late subscriber (overlay/icon injected after init) reflects it right away.
30
- subscribe: (cb: (status: SidecarStatus) => void) => () => void
30
+ subscribe: (cb: (status: SidecarStatus) => void) => () => void;
31
31
  /**
32
32
  * The host channel dropped: re-probe now, which also revives an idle-exited
33
33
  * sidecar, and report `connected` again once it answers.
34
34
  */
35
- reportChannelDown: () => void
36
- dispose: () => void
35
+ reportChannelDown: () => void;
36
+ dispose: () => void;
37
37
  }
38
38
 
39
- export type FetchSidecarInfo = () => Promise<Partial<SidecarInfo>>
39
+ export type FetchSidecarInfo = () => Promise<Partial<SidecarInfo>>;
40
40
 
41
41
  async function fetchInfo(
42
42
  fetchDetails: FetchSidecarInfo,
43
43
  ): Promise<SidecarInfo | null> {
44
44
  try {
45
- const body = await fetchDetails()
45
+ const body = await fetchDetails();
46
46
  return {
47
47
  isRunning: body.isRunning === true,
48
48
  hasGivenUp: body.hasGivenUp === true,
49
49
  disabled: body.disabled === true,
50
- }
50
+ };
51
51
  } catch {
52
- return null
52
+ return null;
53
53
  }
54
54
  }
55
55
 
56
56
  export function createSidecarStatusController(
57
57
  fetchDetails: FetchSidecarInfo,
58
58
  ): SidecarStatusController {
59
- const backoff = createBackoff()
60
- const subscribers = new Set<(status: SidecarStatus) => void>()
61
- let status: SidecarStatus = SIDECAR_STATUS_CONNECTING
62
- let pollTimer: ReturnType<typeof setTimeout> | null = null
63
- let disposed = false
59
+ const backoff = createBackoff();
60
+ const subscribers = new Set<(status: SidecarStatus) => void>();
61
+ let status: SidecarStatus = SIDECAR_STATUS_CONNECTING;
62
+ let pollTimer: ReturnType<typeof setTimeout> | null = null;
63
+ let disposed = false;
64
64
 
65
65
  function notify(): void {
66
- for (const cb of subscribers) cb(status)
66
+ for (const cb of subscribers) cb(status);
67
67
  }
68
68
 
69
69
  function setStatus(next: SidecarStatus): void {
70
- if (status === next) return
71
- status = next
72
- notify()
70
+ if (status === next) return;
71
+ status = next;
72
+ notify();
73
73
  }
74
74
 
75
75
  function schedulePoll(): void {
76
- if (disposed || pollTimer !== null) return
77
- const delay = nextDelay(backoff, SIDECAR_POLL_DELAYS_MS)
76
+ if (disposed || pollTimer !== null) return;
77
+ const delay = nextDelay(backoff, SIDECAR_POLL_DELAYS_MS);
78
78
  pollTimer = setTimeout(() => {
79
- pollTimer = null
80
- void poll()
81
- }, delay)
79
+ pollTimer = null;
80
+ void poll();
81
+ }, delay);
82
82
  }
83
83
 
84
84
  async function poll(): Promise<void> {
85
- if (disposed) return
86
- handleInfo(await fetchInfo(fetchDetails))
85
+ if (disposed) return;
86
+ handleInfo(await fetchInfo(fetchDetails));
87
87
  }
88
88
 
89
89
  function handleInfo(info: SidecarInfo | null): void {
90
- if (disposed) return
90
+ if (disposed) return;
91
91
  // Probe itself failed (backend momentarily unreachable): keep waiting.
92
92
  if (info === null) {
93
- setStatus(SIDECAR_STATUS_REVIVING)
94
- schedulePoll()
95
- return
93
+ setStatus(SIDECAR_STATUS_REVIVING);
94
+ schedulePoll();
95
+ return;
96
96
  }
97
97
  if (info.hasGivenUp) {
98
- setStatus(SIDECAR_STATUS_UNAVAILABLE)
99
- return
98
+ setStatus(SIDECAR_STATUS_UNAVAILABLE);
99
+ return;
100
100
  }
101
101
  if (info.isRunning) {
102
- resetBackoff(backoff)
103
- setStatus(SIDECAR_STATUS_CONNECTED)
104
- return
102
+ resetBackoff(backoff);
103
+ setStatus(SIDECAR_STATUS_CONNECTED);
104
+ return;
105
105
  }
106
106
  // Down but not terminal — the probe already asked the backend to respawn;
107
107
  // keep polling until it reports the sidecar running.
108
- setStatus(SIDECAR_STATUS_REVIVING)
109
- schedulePoll()
108
+ setStatus(SIDECAR_STATUS_REVIVING);
109
+ schedulePoll();
110
110
  }
111
111
 
112
112
  return {
113
113
  init: async (): Promise<boolean> => {
114
- const info = await fetchInfo(fetchDetails)
115
- if (info === null) return false
116
- if (info.disabled) return false
117
- handleInfo(info)
118
- return true
114
+ const info = await fetchInfo(fetchDetails);
115
+ if (info === null) return false;
116
+ if (info.disabled) return false;
117
+ handleInfo(info);
118
+ return true;
119
119
  },
120
120
  getStatus: () => status,
121
121
  subscribe: (cb) => {
122
- subscribers.add(cb)
123
- cb(status)
122
+ subscribers.add(cb);
123
+ cb(status);
124
124
  return () => {
125
- subscribers.delete(cb)
126
- }
125
+ subscribers.delete(cb);
126
+ };
127
127
  },
128
128
  reportChannelDown: () => {
129
- if (disposed) return
130
- if (status !== SIDECAR_STATUS_CONNECTED) return
129
+ if (disposed) return;
130
+ if (status !== SIDECAR_STATUS_CONNECTED) return;
131
131
  // Surface the drop right away (amber dot + spinner) instead of waiting on
132
132
  // the re-probe: that probe awaits a respawn and can take a second or two,
133
133
  // during which the icon would otherwise look healthy. poll() flips back
134
134
  // to connected once the sidecar answers again.
135
- resetBackoff(backoff)
136
- setStatus(SIDECAR_STATUS_REVIVING)
137
- void poll()
135
+ resetBackoff(backoff);
136
+ setStatus(SIDECAR_STATUS_REVIVING);
137
+ void poll();
138
138
  },
139
139
  dispose: () => {
140
- disposed = true
140
+ disposed = true;
141
141
  if (pollTimer !== null) {
142
- clearTimeout(pollTimer)
143
- pollTimer = null
142
+ clearTimeout(pollTimer);
143
+ pollTimer = null;
144
144
  }
145
- subscribers.clear()
145
+ subscribers.clear();
146
146
  },
147
- }
147
+ };
148
148
  }
@@ -0,0 +1,11 @@
1
+ export interface TypedMessage {
2
+ type: string;
3
+ }
4
+
5
+ /** Whether `value` is an object carrying a string `type`, as every protocol message does. */
6
+ export function isTypedMessage<T extends TypedMessage = TypedMessage>(
7
+ value: unknown,
8
+ ): value is T {
9
+ if (value === null || typeof value !== "object") return false;
10
+ return typeof Reflect.get(value, "type") === "string";
11
+ }
@@ -1,12 +1,12 @@
1
- import { defineAsyncComponent, type Component } from 'vue'
2
- import type { DmsFrontendModule } from '#dms/frontend-module'
3
- import aiPlugin from './app/plugins/ai.client'
1
+ import { defineAsyncComponent, type Component } from "vue";
2
+ import type { DmsFrontendModule } from "#dms/frontend-module";
3
+ import aiPlugin from "./app/plugins/ai.client";
4
4
 
5
5
  interface VueModule {
6
- default: Component
6
+ default: Component;
7
7
  }
8
8
 
9
- const components = import.meta.glob<VueModule>('./app/components/**/*.vue')
9
+ const components = import.meta.glob<VueModule>("./app/components/**/*.vue");
10
10
 
11
11
  const frontendModule: DmsFrontendModule = {
12
12
  setup(sdk) {
@@ -14,13 +14,13 @@ const frontendModule: DmsFrontendModule = {
14
14
  .sort(([left], [right]) => left.localeCompare(right))
15
15
  .forEach(([path, loader]) => {
16
16
  const name = `DmsAi${path
17
- .split('/')
17
+ .split("/")
18
18
  .at(-1)!
19
- .replace(/\.vue$/, '')}`
20
- sdk.registerComponent(name, defineAsyncComponent(loader))
21
- })
22
- sdk.registerPlugin(aiPlugin, { clientOnly: true })
19
+ .replace(/\.vue$/, "")}`;
20
+ sdk.registerComponent(name, defineAsyncComponent(loader));
21
+ });
22
+ sdk.registerPlugin(aiPlugin, { clientOnly: true });
23
23
  },
24
- }
24
+ };
25
25
 
26
- export default frontendModule
26
+ export default frontendModule;
@@ -1,24 +1,24 @@
1
- import eslint from '@eslint/js'
2
- import ts from 'typescript-eslint'
3
- import vue from 'eslint-plugin-vue'
4
- import globals from 'globals'
1
+ import eslint from "@eslint/js";
2
+ import ts from "typescript-eslint";
3
+ import vue from "eslint-plugin-vue";
4
+ import globals from "globals";
5
5
 
6
6
  export default ts.config(
7
- { ignores: ['dist/**', 'node_modules/**'] },
7
+ { ignores: ["dist/**", "node_modules/**"] },
8
8
  eslint.configs.recommended,
9
- ...vue.configs['flat/essential'],
9
+ ...vue.configs["flat/essential"],
10
10
  {
11
- files: ['**/*.{ts,vue}'],
11
+ files: ["**/*.{ts,vue}"],
12
12
  languageOptions: {
13
13
  parserOptions: { parser: ts.parser },
14
14
  globals: { ...globals.browser, ...globals.node },
15
15
  },
16
16
  rules: {
17
- 'no-undef': 'off',
18
- 'no-unused-vars': 'off',
19
- 'no-redeclare': 'off',
20
- 'vue/multi-word-component-names': 'off',
17
+ "no-undef": "off",
18
+ "no-unused-vars": "off",
19
+ "no-redeclare": "off",
20
+ "vue/multi-word-component-names": "off",
21
21
  },
22
22
  },
23
- { files: ['**/*.ts'], languageOptions: { parser: ts.parser } },
24
- )
23
+ { files: ["**/*.ts"], languageOptions: { parser: ts.parser } },
24
+ );
@@ -11,25 +11,32 @@
11
11
  "scripts": {
12
12
  "build": "pnpm --dir \"${DMS_FRONTEND_WORKSPACE:?Set DMS_FRONTEND_WORKSPACE to the generated Inertia workspace}\" build",
13
13
  "typecheck": "pnpm --dir \"${DMS_FRONTEND_WORKSPACE:?Set DMS_FRONTEND_WORKSPACE to the generated Inertia workspace}\" typecheck",
14
+ "typecheck:chat": "vue-tsc --noEmit -p tsconfig.chat.json",
14
15
  "format": "prettier --write .",
15
- "lint": "eslint .",
16
- "lint:fix": "eslint . --fix",
16
+ "lint": "eslint . && prettier --check .",
17
+ "lint:fix": "eslint . --fix && prettier --write .",
17
18
  "test": "vitest run --passWithNoTests"
18
19
  },
19
20
  "dependencies": {
20
21
  "@inertiajs/vue3": "^3.7.0",
22
+ "@types/markdown-it": "^14.1.2",
23
+ "dompurify": "^3.2.4",
24
+ "markdown-it": "^14.1.0",
21
25
  "vue": "^3.5.18"
22
26
  },
23
27
  "devDependencies": {
24
28
  "@eslint/js": "^9.0.0",
25
- "typescript-eslint": "^8.0.0",
29
+ "@vitejs/plugin-vue": "^6.0.0",
30
+ "@vitest/runner": "^4.1.5",
31
+ "@vue/test-utils": "^2.4.6",
26
32
  "eslint": "^9.0.0",
27
33
  "eslint-plugin-vue": "^10.0.0",
28
34
  "globals": "^16.0.0",
29
- "@vitest/runner": "^4.1.5",
30
35
  "jsdom": "^29.1.1",
31
36
  "prettier": "^3.5.3",
37
+ "prettier-plugin-tailwindcss": "^0.6.11",
32
38
  "typescript": "^5.8.2",
39
+ "typescript-eslint": "^8.0.0",
33
40
  "vitest": "^4.1.5",
34
41
  "vue-tsc": "^3.1.1"
35
42
  }