@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
@@ -3,43 +3,45 @@ import {
3
3
  createBackoff,
4
4
  nextDelay,
5
5
  resetBackoff,
6
- } from './backoff'
6
+ } from "./backoff";
7
7
  import {
8
+ CHANNEL_EVENT_FRAME,
8
9
  CHANNEL_EVENT_READY,
9
- CHANNEL_EVENTS_SEGMENT,
10
- CHANNEL_LIST_SEPARATOR,
10
+ CHANNEL_EVENTS_PATH,
11
11
  CHANNEL_MESSAGES_SEGMENT,
12
+ CHANNEL_PATH,
12
13
  CHANNEL_RECONNECT_DELAYS_MS,
13
14
  CHANNEL_STATUS_CONNECTED,
14
15
  CHANNEL_STATUS_CONNECTING,
15
16
  CHANNEL_STATUS_DISCONNECTED,
16
17
  CHANNEL_STATUS_RECONNECTING,
17
- CHANNELS_PATH,
18
18
  EVENT_STREAM_TYPE,
19
- } from './constants'
20
- import { readEventStream, type StreamEvent } from './event-stream'
19
+ } from "./constants";
20
+ import { readEventStream, type StreamEvent } from "./event-stream";
21
21
 
22
22
  export type ChannelStatus =
23
23
  | typeof CHANNEL_STATUS_CONNECTING
24
24
  | typeof CHANNEL_STATUS_CONNECTED
25
25
  | typeof CHANNEL_STATUS_DISCONNECTED
26
- | typeof CHANNEL_STATUS_RECONNECTING
26
+ | typeof CHANNEL_STATUS_RECONNECTING;
27
27
 
28
28
  /** Posts one message as JSON; `$authFetch` in the dashboard refreshes a stale session. */
29
- export type PostMessage = (path: string, body: unknown) => Promise<unknown>
29
+ export type PostMessage = (path: string, body: unknown) => Promise<unknown>;
30
30
 
31
- /** Opens the event stream of a set of channels. */
32
- export type OpenStream = (path: string, signal: AbortSignal) => Promise<Response>
31
+ /** Opens the tab's event stream. */
32
+ export type OpenStream = (
33
+ path: string,
34
+ signal: AbortSignal,
35
+ ) => Promise<Response>;
33
36
 
34
37
  export interface ChannelClientOptions {
35
- channels: readonly string[]
36
- post: PostMessage
37
- openStream?: OpenStream
38
- /** Every connection is a new sidecar socket per channel: identify on it here. */
39
- onReady?: () => void
40
- /** Raw sidecar frame, as the socket of that channel delivered it. */
41
- onFrame?: (channel: string, raw: string) => void
42
- onStatusChange?: (status: ChannelStatus) => void
38
+ post: PostMessage;
39
+ openStream?: OpenStream;
40
+ /** Every connection is a new sidecar socket: identify on it here. */
41
+ onReady?: () => void;
42
+ /** Raw sidecar frame, as the socket delivered it. */
43
+ onFrame?: (raw: string) => void;
44
+ onStatusChange?: (status: ChannelStatus) => void;
43
45
  }
44
46
 
45
47
  export interface ChannelClient {
@@ -47,219 +49,206 @@ export interface ChannelClient {
47
49
  * Queued and posted one at a time, in order. False when it was dropped
48
50
  * because the stream is not connected, so the caller knows nothing left.
49
51
  */
50
- send: (channel: string, msg: unknown) => boolean
51
- isConnected: () => boolean
52
- getStatus: () => ChannelStatus
52
+ send: (msg: unknown) => boolean;
53
+ isConnected: () => boolean;
54
+ getStatus: () => ChannelStatus;
53
55
  /** Opens the stream if it is not open yet. */
54
- start: () => void
56
+ start: () => void;
55
57
  /** Closes the stream until the next `start`. */
56
- stop: () => void
58
+ stop: () => void;
57
59
  /**
58
60
  * Drops any pending backoff and replaces the stream at once, if started.
59
61
  * The status says connecting until the new stream is ready, since nothing
60
62
  * can be sent in between.
61
63
  */
62
- reconnectNow: () => void
64
+ reconnectNow: () => void;
63
65
  }
64
66
 
65
67
  interface ReadyPayload {
66
- connections?: Record<string, unknown>
67
- }
68
-
69
- interface QueuedMessage {
70
- channel: string
71
- msg: unknown
68
+ connectionId?: unknown;
72
69
  }
73
70
 
74
71
  interface ClientState {
75
- options: ChannelClientOptions
76
- status: ChannelStatus
77
- connections: Record<string, string> | null
78
- stream: AbortController | null
79
- retryTimer: ReturnType<typeof setTimeout> | null
80
- queue: QueuedMessage[]
81
- isDraining: boolean
82
- isStarted: boolean
83
- backoff: BackoffState
84
- }
85
-
86
- export function channelEventsPath(channels: readonly string[]): string {
87
- return `${CHANNELS_PATH}/${channels.join(CHANNEL_LIST_SEPARATOR)}/${CHANNEL_EVENTS_SEGMENT}`
72
+ options: ChannelClientOptions;
73
+ status: ChannelStatus;
74
+ connectionId: string | null;
75
+ stream: AbortController | null;
76
+ retryTimer: ReturnType<typeof setTimeout> | null;
77
+ queue: unknown[];
78
+ isDraining: boolean;
79
+ isStarted: boolean;
80
+ backoff: BackoffState;
88
81
  }
89
82
 
90
83
  export function channelMessagesPath(connectionId: string): string {
91
- return `${CHANNELS_PATH}/${encodeURIComponent(connectionId)}/${CHANNEL_MESSAGES_SEGMENT}`
84
+ return `${CHANNEL_PATH}/${encodeURIComponent(connectionId)}/${CHANNEL_MESSAGES_SEGMENT}`;
92
85
  }
93
86
 
94
87
  function openEventStream(path: string, signal: AbortSignal): Promise<Response> {
95
88
  return fetch(path, {
96
89
  headers: { accept: EVENT_STREAM_TYPE },
97
- credentials: 'same-origin',
98
- cache: 'no-store',
90
+ credentials: "same-origin",
91
+ cache: "no-store",
99
92
  signal,
100
- })
93
+ });
101
94
  }
102
95
 
103
96
  function setStatus(state: ClientState, next: ChannelStatus): void {
104
- if (state.status === next) return
105
- state.status = next
106
- state.options.onStatusChange?.(next)
97
+ if (state.status === next) return;
98
+ state.status = next;
99
+ state.options.onStatusChange?.(next);
107
100
  }
108
101
 
109
- function readConnections(data: string): Record<string, string> | null {
102
+ function readConnectionId(data: string): string | null {
110
103
  try {
111
- const { connections } = JSON.parse(data) as ReadyPayload
112
- if (connections === undefined || connections === null) return null
113
- const entries = Object.entries(connections).filter(
114
- (entry): entry is [string, string] => typeof entry[1] === 'string',
115
- )
116
- return Object.fromEntries(entries)
104
+ const { connectionId } = JSON.parse(data) as ReadyPayload;
105
+ return typeof connectionId === "string" ? connectionId : null;
117
106
  } catch {
118
- return null
107
+ return null;
119
108
  }
120
109
  }
121
110
 
122
111
  function handleReady(state: ClientState, event: StreamEvent): void {
123
- state.connections = readConnections(event.data)
124
- resetBackoff(state.backoff)
125
- setStatus(state, CHANNEL_STATUS_CONNECTED)
126
- state.options.onReady?.()
112
+ state.connectionId = readConnectionId(event.data);
113
+ resetBackoff(state.backoff);
114
+ setStatus(state, CHANNEL_STATUS_CONNECTED);
115
+ state.options.onReady?.();
127
116
  }
128
117
 
129
118
  function handleEvent(state: ClientState, event: StreamEvent): void {
130
- if (event.name === CHANNEL_EVENT_READY) return handleReady(state, event)
131
- if (!state.options.channels.includes(event.name)) return
132
- state.options.onFrame?.(event.name, event.data)
119
+ if (event.name === CHANNEL_EVENT_READY) return handleReady(state, event);
120
+ if (event.name !== CHANNEL_EVENT_FRAME) return;
121
+ state.options.onFrame?.(event.data);
133
122
  }
134
123
 
135
124
  function scheduleRetry(state: ClientState): void {
136
- if (state.retryTimer !== null) return
137
- const delay = nextDelay(state.backoff, CHANNEL_RECONNECT_DELAYS_MS)
125
+ if (state.retryTimer !== null) return;
126
+ const delay = nextDelay(state.backoff, CHANNEL_RECONNECT_DELAYS_MS);
138
127
  state.retryTimer = setTimeout(() => {
139
- state.retryTimer = null
140
- connect(state)
141
- }, delay)
128
+ state.retryTimer = null;
129
+ connect(state);
130
+ }, delay);
142
131
  }
143
132
 
144
133
  function lose(state: ClientState): void {
145
- state.connections = null
146
- state.queue = []
147
- if (!state.isStarted) return
148
- setStatus(state, CHANNEL_STATUS_RECONNECTING)
149
- scheduleRetry(state)
150
- }
151
-
152
- async function run(state: ClientState, controller: AbortController): Promise<void> {
153
- const open = state.options.openStream ?? openEventStream
134
+ state.connectionId = null;
135
+ state.queue = [];
136
+ if (!state.isStarted) return;
137
+ setStatus(state, CHANNEL_STATUS_RECONNECTING);
138
+ scheduleRetry(state);
139
+ }
140
+
141
+ async function run(
142
+ state: ClientState,
143
+ controller: AbortController,
144
+ ): Promise<void> {
145
+ const open = state.options.openStream ?? openEventStream;
154
146
  try {
155
- const response = await open(
156
- channelEventsPath(state.options.channels),
157
- controller.signal,
158
- )
159
- if (!response.ok || response.body === null) return
160
- await readEventStream(response.body, (event) => handleEvent(state, event))
147
+ const response = await open(CHANNEL_EVENTS_PATH, controller.signal);
148
+ if (!response.ok || response.body === null) return;
149
+ await readEventStream(response.body, (event) => handleEvent(state, event));
161
150
  } catch {
162
- return
151
+ return;
163
152
  } finally {
164
- if (!controller.signal.aborted) lose(state)
153
+ if (!controller.signal.aborted) lose(state);
165
154
  }
166
155
  }
167
156
 
168
157
  function connect(state: ClientState): void {
169
- if (!state.isStarted) return
170
- state.stream?.abort()
171
- state.connections = null
172
- const controller = new AbortController()
173
- state.stream = controller
174
- void run(state, controller)
158
+ if (!state.isStarted) return;
159
+ state.stream?.abort();
160
+ state.connectionId = null;
161
+ const controller = new AbortController();
162
+ state.stream = controller;
163
+ void run(state, controller);
175
164
  }
176
165
 
177
166
  function restart(state: ClientState): void {
178
- state.stream?.abort()
179
- lose(state)
167
+ state.stream?.abort();
168
+ lose(state);
180
169
  }
181
170
 
182
171
  async function drain(state: ClientState): Promise<void> {
183
- if (state.isDraining) return
184
- state.isDraining = true
185
- while (state.queue.length > 0 && state.connections !== null) {
186
- const next = state.queue.shift() as QueuedMessage
187
- const connectionId = state.connections[next.channel]
188
- if (connectionId === undefined) continue
172
+ if (state.isDraining) return;
173
+ state.isDraining = true;
174
+ while (state.queue.length > 0 && state.connectionId !== null) {
175
+ const next = state.queue.shift();
189
176
  try {
190
- await state.options.post(channelMessagesPath(connectionId), next.msg)
177
+ await state.options.post(channelMessagesPath(state.connectionId), next);
191
178
  } catch {
192
- restart(state)
179
+ restart(state);
193
180
  }
194
181
  }
195
- state.isDraining = false
182
+ state.isDraining = false;
196
183
  }
197
184
 
198
- function send(state: ClientState, channel: string, msg: unknown): boolean {
199
- if (state.connections?.[channel] === undefined) return false
200
- state.queue.push({ channel, msg })
201
- void drain(state)
202
- return true
185
+ function send(state: ClientState, msg: unknown): boolean {
186
+ if (state.connectionId === null) return false;
187
+ state.queue.push(msg);
188
+ void drain(state);
189
+ return true;
203
190
  }
204
191
 
205
192
  function clearRetry(state: ClientState): void {
206
- if (state.retryTimer !== null) clearTimeout(state.retryTimer)
207
- state.retryTimer = null
193
+ if (state.retryTimer !== null) clearTimeout(state.retryTimer);
194
+ state.retryTimer = null;
208
195
  }
209
196
 
210
197
  function start(state: ClientState): void {
211
- if (state.isStarted) return
212
- state.isStarted = true
213
- resetBackoff(state.backoff)
214
- setStatus(state, CHANNEL_STATUS_CONNECTING)
215
- connect(state)
198
+ if (state.isStarted) return;
199
+ state.isStarted = true;
200
+ resetBackoff(state.backoff);
201
+ setStatus(state, CHANNEL_STATUS_CONNECTING);
202
+ connect(state);
216
203
  }
217
204
 
218
205
  function stop(state: ClientState): void {
219
- if (!state.isStarted) return
220
- state.isStarted = false
221
- clearRetry(state)
222
- state.stream?.abort()
223
- state.connections = null
224
- state.queue = []
225
- setStatus(state, CHANNEL_STATUS_DISCONNECTED)
206
+ if (!state.isStarted) return;
207
+ state.isStarted = false;
208
+ clearRetry(state);
209
+ state.stream?.abort();
210
+ state.connectionId = null;
211
+ state.queue = [];
212
+ setStatus(state, CHANNEL_STATUS_DISCONNECTED);
226
213
  }
227
214
 
228
215
  function reconnectNow(state: ClientState): void {
229
- if (!state.isStarted) return
230
- clearRetry(state)
231
- resetBackoff(state.backoff)
232
- setStatus(state, CHANNEL_STATUS_CONNECTING)
233
- connect(state)
216
+ if (!state.isStarted) return;
217
+ clearRetry(state);
218
+ resetBackoff(state.backoff);
219
+ setStatus(state, CHANNEL_STATUS_CONNECTING);
220
+ connect(state);
234
221
  }
235
222
 
236
223
  /**
237
- * One stream to the sidecar through the DMS carrying several channels, and
238
- * sequential POSTs to send on each. Browsers keep at most six HTTP/1.1
239
- * connections per origin, so a tab holds one such stream and only while it
240
- * needs it: the caller starts and stops it. While started, any end of the
241
- * stream — a sidecar that went down, a hot reload of dms-ai, a network drop —
242
- * reconnects with backoff, and each new connection is announced through
243
- * `onReady` so the caller identifies itself again.
224
+ * The tab's one stream to the sidecar through the DMS, carrying the dashboard's
225
+ * and the chat's frames alike, and sequential POSTs to send. Browsers keep at
226
+ * most six HTTP/1.1 connections per origin, so a tab holds one such stream and
227
+ * only while it needs it: the caller starts and stops it. While started, any
228
+ * end of the stream — a sidecar that went down, a hot reload of dms-ai, a
229
+ * network drop — reconnects with backoff, and each new connection is announced
230
+ * through `onReady` so the caller identifies itself again.
244
231
  */
245
- export function createChannelClient(options: ChannelClientOptions): ChannelClient {
232
+ export function createChannelClient(
233
+ options: ChannelClientOptions,
234
+ ): ChannelClient {
246
235
  const state: ClientState = {
247
236
  options,
248
237
  status: CHANNEL_STATUS_DISCONNECTED,
249
- connections: null,
238
+ connectionId: null,
250
239
  stream: null,
251
240
  retryTimer: null,
252
241
  queue: [],
253
242
  isDraining: false,
254
243
  isStarted: false,
255
244
  backoff: createBackoff(),
256
- }
245
+ };
257
246
  return {
258
- send: (channel, msg) => send(state, channel, msg),
259
- isConnected: () => state.connections !== null,
247
+ send: (msg) => send(state, msg),
248
+ isConnected: () => state.connectionId !== null,
260
249
  getStatus: () => state.status,
261
250
  start: () => start(state),
262
251
  stop: () => stop(state),
263
252
  reconnectNow: () => reconnectNow(state),
264
- }
253
+ };
265
254
  }
@@ -1,82 +1,52 @@
1
- import type { ChannelStatus } from './channel-client'
2
- import { CHAT_TRANSPORT_KEY, LOG_PREFIX } from './constants'
1
+ import type { ChannelStatus } from "./channel-client";
2
+ import { createListeners } from "./listeners";
3
3
 
4
4
  /**
5
- * What the chat document, same-origin with the dashboard, reads on its parent
6
- * window to reach the sidecar: the dashboard owns the tab's only stream and
7
- * lends the chat its channel of it.
5
+ * The chat's side of the tab's stream: the dashboard owns the stream and hands
6
+ * the chat the sidecar messages that are not host commands.
8
7
  */
9
8
  export interface ChatTransport {
10
- /** Raw sidecar frames of the chat channel, in order. */
11
- onFrame: (listener: (raw: string) => void) => () => void
9
+ /** Sidecar messages for the chat, parsed once by the dashboard, in order. */
10
+ onMessage: (listener: (msg: object) => void) => () => void;
12
11
  /** Every (re)connection is a new sidecar socket: the chat says hello again. */
13
- onReady: (listener: () => void) => () => void
14
- onStatus: (listener: (status: ChannelStatus) => void) => () => void
15
- getStatus: () => ChannelStatus
16
- /** False when the message was dropped because the channel is not connected. */
17
- send: (msg: unknown) => boolean
18
- reconnect: () => void
12
+ onReady: (listener: () => void) => () => void;
13
+ onStatus: (listener: (status: ChannelStatus) => void) => () => void;
14
+ getStatus: () => ChannelStatus;
15
+ /** False when the message was dropped because the stream is not connected. */
16
+ send: (msg: unknown) => boolean;
17
+ reconnect: () => void;
19
18
  }
20
19
 
21
20
  export interface ChatTransportActions {
22
- send: (msg: unknown) => boolean
23
- reconnect: () => void
24
- getStatus: () => ChannelStatus
21
+ send: (msg: unknown) => boolean;
22
+ reconnect: () => void;
23
+ getStatus: () => ChannelStatus;
25
24
  }
26
25
 
27
26
  export interface ChatTransportHub {
28
- transport: ChatTransport
29
- deliverFrame: (raw: string) => void
30
- announceReady: () => void
31
- announceStatus: (status: ChannelStatus) => void
32
- }
33
-
34
- interface Listeners<T> {
35
- add: (listener: (value: T) => void) => () => void
36
- emit: (value: T) => void
37
- }
38
-
39
- function createListeners<T>(): Listeners<T> {
40
- const listeners = new Set<(value: T) => void>()
41
- return {
42
- add: (listener) => {
43
- listeners.add(listener)
44
- return () => listeners.delete(listener)
45
- },
46
- emit: (value) => {
47
- for (const listener of listeners) {
48
- try {
49
- listener(value)
50
- } catch (error: unknown) {
51
- console.error(`${LOG_PREFIX} chat listener failed`, error)
52
- }
53
- }
54
- },
55
- }
27
+ transport: ChatTransport;
28
+ deliver: (msg: object) => void;
29
+ announceReady: () => void;
30
+ announceStatus: (status: ChannelStatus) => void;
56
31
  }
57
32
 
58
33
  export function createChatTransport(
59
34
  actions: ChatTransportActions,
60
35
  ): ChatTransportHub {
61
- const frames = createListeners<string>()
62
- const ready = createListeners<undefined>()
63
- const statuses = createListeners<ChannelStatus>()
36
+ const messages = createListeners<object>();
37
+ const ready = createListeners<undefined>();
38
+ const statuses = createListeners<ChannelStatus>();
64
39
  return {
65
40
  transport: {
66
- onFrame: frames.add,
41
+ onMessage: messages.add,
67
42
  onReady: (listener) => ready.add(() => listener()),
68
43
  onStatus: statuses.add,
69
44
  getStatus: actions.getStatus,
70
45
  send: actions.send,
71
46
  reconnect: actions.reconnect,
72
47
  },
73
- deliverFrame: frames.emit,
48
+ deliver: messages.emit,
74
49
  announceReady: () => ready.emit(undefined),
75
50
  announceStatus: statuses.emit,
76
- }
77
- }
78
-
79
- /** Publishes the transport where the chat document looks for it. */
80
- export function exposeChatTransport(transport: ChatTransport): void {
81
- Reflect.set(globalThis, CHAT_TRANSPORT_KEY, transport)
51
+ };
82
52
  }
@@ -1,81 +1,91 @@
1
- export const OVERLAY_DEFAULT_WIDTH_PX = 460
2
- export const OVERLAY_Z_INDEX = 999_999
3
- export const OVERLAY_DOM_ID = 'dms-ai-overlay-root'
4
- export const OVERLAY_IFRAME_ID = 'dms-ai-overlay-iframe'
5
- export const SIDECAR_INFO_PATH = '/ai/sidecar-info'
6
- /** The chat document, served through the DMS from the dashboard's own origin. */
7
- export const CHATBOX_PATH = '/api/ai/chatbox/'
1
+ export const OVERLAY_DEFAULT_WIDTH_PX = 460;
2
+ /**
3
+ * Above the dashboard's header and the builder's overlay (z-50), below what the
4
+ * dashboard portals out of the app: the DMS paints its popovers, modals and
5
+ * toasts in an isolated layer after the app, whatever this value is.
6
+ */
7
+ export const OVERLAY_Z_INDEX = 55;
8
+ export const OVERLAY_DOM_ID = "dms-ai-overlay-root";
9
+ export const DMS_OVERLAYS_DOM_ID = "dms-overlays";
10
+ export const SIDECAR_INFO_PATH = "/ai/sidecar-info";
8
11
  /** Event stream to receive, POST to send: HTTP the DMS frontend server relays. */
9
- export const CHANNELS_PATH = '/ai/channels'
10
- export const CHANNEL_EVENTS_SEGMENT = 'events'
11
- export const CHANNEL_MESSAGES_SEGMENT = 'messages'
12
- export const CHANNEL_LIST_SEPARATOR = ','
13
- export const HOST_CHANNEL = 'host'
14
- export const CHAT_CHANNEL = 'chat'
15
- export const CHANNEL_EVENT_READY = 'ready'
12
+ export const CHANNEL_PATH = "/ai/channel";
13
+ export const CHANNEL_EVENTS_PATH = `${CHANNEL_PATH}/events`;
14
+ export const CHANNEL_MESSAGES_SEGMENT = "messages";
15
+ export const CHANNEL_EVENT_READY = "ready";
16
+ /** A sidecar frame: the stream's default event name. */
17
+ export const CHANNEL_EVENT_FRAME = "message";
16
18
  /**
17
19
  * How long the stream outlives a closed panel or a hidden tab, so a quick
18
20
  * close and reopen does not reconnect, while a tab left in the background soon
19
21
  * gives back its one of the browser's six HTTP/1.1 connections to the dashboard.
20
22
  */
21
- export const CHANNEL_IDLE_STOP_MS = 2_000
22
- export const VISIBILITY_CHANGE_EVENT = 'visibilitychange'
23
- /** Where the chat document, same-origin, finds the dashboard's chat transport. */
24
- export const CHAT_TRANSPORT_KEY = 'dmsAiChatTransport'
25
- export const OVERLAY_OPEN_CHANGE_EVENT = 'dms-ai:open-change'
26
- export const EVENT_STREAM_TYPE = 'text/event-stream'
27
- export const JSON_CONTENT_TYPE = 'application/json'
28
- export const OVERLAY_BOX_SHADOW = '0 8px 24px rgba(0, 0, 0, 0.18)'
23
+ export const CHANNEL_IDLE_STOP_MS = 2_000;
24
+ export const VISIBILITY_CHANGE_EVENT = "visibilitychange";
25
+ export const EVENT_STREAM_TYPE = "text/event-stream";
26
+ export const JSON_CONTENT_TYPE = "application/json";
29
27
  export const CHANNEL_RECONNECT_DELAYS_MS = [
30
28
  1000, 2000, 4000, 8000, 15000, 30000,
31
- ] as const
29
+ ] as const;
32
30
  // Sidecar reachability, as seen by the host. `reviving` is the self-healing
33
31
  // state (idle-exited / crashed-respawning): the probe loop revives the sidecar
34
32
  // and the channels reconnect once it answers. `unavailable` is terminal (the
35
33
  // crash budget is spent) and needs a reload.
36
- export const SIDECAR_STATUS_CONNECTING = 'connecting'
37
- export const SIDECAR_STATUS_CONNECTED = 'connected'
38
- export const SIDECAR_STATUS_REVIVING = 'reviving'
39
- export const SIDECAR_STATUS_UNAVAILABLE = 'unavailable'
34
+ export const SIDECAR_STATUS_CONNECTING = "connecting";
35
+ export const SIDECAR_STATUS_CONNECTED = "connected";
36
+ export const SIDECAR_STATUS_REVIVING = "reviving";
37
+ export const SIDECAR_STATUS_UNAVAILABLE = "unavailable";
40
38
  // Re-probe cadence while reviving. Each probe also revives the daemon backend
41
39
  // side, so this doubles as the respawn heartbeat; capped so a genuinely stuck
42
40
  // sidecar is polled sparingly rather than hammered.
43
- export const SIDECAR_POLL_DELAYS_MS = [
44
- 500, 1000, 2000, 4000, 8000,
45
- ] as const
46
- export const OVERLAY_PLACEHOLDER_ID = 'dms-ai-overlay-placeholder'
47
- export const PLACEHOLDER_CONNECTING_TEXT = 'Connecting the assistant…'
48
- export const PLACEHOLDER_REVIVING_TEXT = 'Reconnecting the assistant…'
49
- export const PLACEHOLDER_UNAVAILABLE_TITLE = 'Assistant unavailable'
50
- export const PLACEHOLDER_UNAVAILABLE_TEXT = 'Reload the page to try again.'
41
+ export const SIDECAR_POLL_DELAYS_MS = [500, 1000, 2000, 4000, 8000] as const;
42
+ export const PLACEHOLDER_CONNECTING_TEXT = "Connecting the assistant…";
43
+ export const PLACEHOLDER_REVIVING_TEXT = "Reconnecting the assistant…";
44
+ export const PLACEHOLDER_UNAVAILABLE_TITLE = "Assistant unavailable";
45
+ export const PLACEHOLDER_UNAVAILABLE_TEXT = "Reload the page to try again.";
51
46
 
52
- export const CHANNEL_STATUS_CONNECTING = 'connecting'
53
- export const CHANNEL_STATUS_CONNECTED = 'connected'
54
- export const CHANNEL_STATUS_DISCONNECTED = 'disconnected'
55
- export const CHANNEL_STATUS_RECONNECTING = 'reconnecting'
56
- export const HOST_ROLE = 'host'
57
- export const HELLO_MESSAGE_TYPE = 'hello'
58
- export const LOG_PREFIX = '[dms-ai]'
59
- export const HOST_STATE_UPDATE_TYPE = 'host_state_update'
60
- export const HOST_NAVIGATION_COMPLETE_TYPE = 'host_navigation_complete'
61
- export const HOST_COMMAND_NAVIGATE_TYPE = 'host_command_navigate'
62
- export const OVERLAY_RESIZE_HANDLE_SIZE_PX = 6
63
- export const OVERLAY_MIN_WIDTH_PX = 320
64
- export const OVERLAY_PREFS_STORAGE_KEY = 'dms-ai:overlay-prefs'
65
- export const OVERLAY_OUTSIDE_TOGGLE_SUPPRESS_MS = 300
66
- export const OVERLAY_PREFS_DEBOUNCE_MS = 300
67
- export const TOGGLE_SHORTCUT_KEY = 'k'
68
- export const MODAL_OPEN_SELECTOR = '[role="dialog"][aria-modal="true"]'
47
+ export const CHANNEL_STATUS_CONNECTING = "connecting";
48
+ export const CHANNEL_STATUS_CONNECTED = "connected";
49
+ export const CHANNEL_STATUS_DISCONNECTED = "disconnected";
50
+ export const CHANNEL_STATUS_RECONNECTING = "reconnecting";
51
+ export const HOST_ROLE = "host";
52
+ export const HELLO_MESSAGE_TYPE = "hello";
53
+ export const LOG_PREFIX = "[dms-ai]";
54
+ export const HOST_STATE_UPDATE_TYPE = "host_state_update";
55
+ export const HOST_NAVIGATION_COMPLETE_TYPE = "host_navigation_complete";
56
+ export const HOST_COMMAND_NAVIGATE_TYPE = "host_command_navigate";
57
+ /** Frames of the tab's stream meant for the dashboard; every other one is the chat's. */
58
+ export const HOST_COMMAND_TYPES: ReadonlySet<string> = new Set([
59
+ HOST_COMMAND_NAVIGATE_TYPE,
60
+ ]);
61
+ export const OVERLAY_MIN_WIDTH_PX = 320;
62
+ export const OVERLAY_PREFS_STORAGE_KEY = "dms-ai:overlay-prefs";
63
+ export const OVERLAY_OUTSIDE_TOGGLE_SUPPRESS_MS = 300;
64
+ export const OVERLAY_PREFS_DEBOUNCE_MS = 300;
65
+ export const TOGGLE_SHORTCUT_KEY = "k";
66
+ export const MODAL_OPEN_SELECTOR = '[role="dialog"][aria-modal="true"]';
69
67
 
70
68
  // Generic header-action registry exposed by the DMS core (dms-back). Any module
71
- // can push a button by writing to this shared Nuxt state key; the core renders
69
+ // can push a button by writing to this shared `useDmsState` key; the core renders
72
70
  // it with no knowledge of who registered it.
73
- export const HEADER_ACTIONS_STATE_KEY = 'dms:header-actions'
74
- export const LAUNCHER_ACTION_ID = 'dms-ai-launcher'
75
- export const LAUNCHER_ICON = 'i-ph-robot'
76
- export const LAUNCHER_LABEL = 'AI assistant'
77
- export const LAUNCHER_ORDER = 50
71
+ export const HEADER_ACTIONS_STATE_KEY = "dms:header-actions";
72
+ /**
73
+ * The DMS's persistent overlays: components rendered on every page, inside the
74
+ * app but outside the routed page, so they live through Inertia navigations.
75
+ * The state key and the component name are the whole contract.
76
+ */
77
+ export const APP_OVERLAYS_STATE_KEY = "dms-app-overlays";
78
+ export const CHAT_PANEL_COMPONENT_NAME = "DmsAiChatPanel";
79
+ export const LAUNCHER_ACTION_ID = "dms-ai-launcher";
80
+ /**
81
+ * The chat panel's header shows the same icon, which is what gets it into the
82
+ * renderer's icon bundle: it only scans `.vue` files for icon names.
83
+ */
84
+ export const LAUNCHER_ICON = "i-ph-robot";
85
+ export const LAUNCHER_LABEL = "AI assistant";
86
+ export const PANEL_LABEL = LAUNCHER_LABEL;
87
+ export const LAUNCHER_ORDER = 50;
78
88
  // Toggle shortcut, shown in the launcher tooltip. The combo is meta/ctrl + shift
79
89
  // + k, so render it the way each platform expects.
80
- export const TOGGLE_SHORTCUT_LABEL_MAC = '⌘⇧K'
81
- export const TOGGLE_SHORTCUT_LABEL_OTHER = 'Ctrl+Shift+K'
90
+ export const TOGGLE_SHORTCUT_LABEL_MAC = "⌘⇧K";
91
+ export const TOGGLE_SHORTCUT_LABEL_OTHER = "Ctrl+Shift+K";