@antelopejs/dms-ai 0.1.4 → 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 (244) 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/routes/channels.d.ts +3 -3
  32. package/dist/routes/channels.js +11 -12
  33. package/dist/routes/channels.js.map +1 -1
  34. package/dist/routes/index.d.ts +0 -1
  35. package/dist/routes/index.js +0 -1
  36. package/dist/routes/index.js.map +1 -1
  37. package/frontend-vue/.prettierignore +3 -0
  38. package/frontend-vue/.prettierrc +10 -0
  39. package/frontend-vue/app/chat/components/ChatView.vue +582 -0
  40. package/frontend-vue/app/chat/components/ComposerInput.vue +742 -0
  41. package/frontend-vue/app/chat/components/ConnectionBanner.vue +105 -0
  42. package/frontend-vue/app/chat/components/ConversationDrawer.vue +266 -0
  43. package/frontend-vue/app/chat/components/MarkdownContent.vue +196 -0
  44. package/frontend-vue/app/chat/components/MessageList.vue +474 -0
  45. package/frontend-vue/app/chat/components/PermissionRequests.vue +323 -0
  46. package/frontend-vue/app/chat/components/QuestionPrompt.vue +275 -0
  47. package/frontend-vue/app/chat/components/QueuedMessages.vue +109 -0
  48. package/frontend-vue/app/chat/components/TodoList.vue +291 -0
  49. package/frontend-vue/app/chat/components/ToolCallEntry.vue +218 -0
  50. package/frontend-vue/app/chat/components/ToolCluster.vue +282 -0
  51. package/frontend-vue/app/chat/composables/useChatChannel.ts +116 -0
  52. package/frontend-vue/app/chat/composables/useConversation.ts +838 -0
  53. package/frontend-vue/app/chat/composables/useConversationId.ts +34 -0
  54. package/frontend-vue/app/chat/composables/useConversationList.ts +56 -0
  55. package/frontend-vue/app/chat/composables/usePermissionQueue.ts +59 -0
  56. package/frontend-vue/app/chat/composables/useQuestionQueue.ts +50 -0
  57. package/frontend-vue/app/chat/composables/useRunClock.ts +28 -0
  58. package/frontend-vue/app/chat/composables/useSettings.ts +67 -0
  59. package/frontend-vue/app/chat/constants/attachments.ts +16 -0
  60. package/frontend-vue/app/chat/constants/connection-banner.ts +7 -0
  61. package/frontend-vue/app/chat/constants/conversation-drawer.ts +13 -0
  62. package/frontend-vue/app/chat/constants/conversation.ts +75 -0
  63. package/frontend-vue/app/chat/constants/permissions.ts +50 -0
  64. package/frontend-vue/app/chat/constants/protocol.ts +48 -0
  65. package/frontend-vue/app/chat/constants/run-status.ts +50 -0
  66. package/frontend-vue/app/chat/constants/settings.ts +60 -0
  67. package/frontend-vue/app/chat/types/conversation.ts +87 -0
  68. package/frontend-vue/app/chat/types/permission.ts +7 -0
  69. package/frontend-vue/app/chat/types/question.ts +17 -0
  70. package/frontend-vue/app/chat/types/settings.ts +27 -0
  71. package/frontend-vue/app/chat/utils/attachments.ts +62 -0
  72. package/frontend-vue/app/chat/utils/format.ts +14 -0
  73. package/frontend-vue/app/chat/utils/ids.ts +34 -0
  74. package/frontend-vue/app/chat/utils/markdown.ts +188 -0
  75. package/frontend-vue/app/chat/utils/run-status.ts +90 -0
  76. package/frontend-vue/app/chat/utils/todos.ts +64 -0
  77. package/frontend-vue/app/chat/utils/tool-summary.ts +53 -0
  78. package/frontend-vue/app/components/ActivityView.vue +34 -34
  79. package/frontend-vue/app/components/ChatPanel.vue +276 -0
  80. package/frontend-vue/app/components/SettingsView.vue +120 -112
  81. package/frontend-vue/app/components/SkillsView.vue +55 -53
  82. package/frontend-vue/app/plugins/ai.client.ts +145 -129
  83. package/frontend-vue/app/runtime/assistant-session.ts +21 -0
  84. package/frontend-vue/app/runtime/backoff.ts +11 -14
  85. package/frontend-vue/app/runtime/channel-client.ts +130 -141
  86. package/frontend-vue/app/runtime/chat-transport.ts +25 -55
  87. package/frontend-vue/app/runtime/constants.ts +69 -59
  88. package/frontend-vue/app/runtime/current-page.ts +49 -15
  89. package/frontend-vue/app/runtime/event-stream.ts +35 -35
  90. package/frontend-vue/app/runtime/frame-router.ts +30 -0
  91. package/frontend-vue/app/runtime/header-action.ts +18 -18
  92. package/frontend-vue/app/runtime/host-commands.ts +57 -56
  93. package/frontend-vue/app/runtime/listeners.ts +31 -0
  94. package/frontend-vue/app/runtime/navigation-complete.ts +7 -7
  95. package/frontend-vue/app/runtime/overlay-prefs.ts +36 -36
  96. package/frontend-vue/app/runtime/panel-state.ts +88 -0
  97. package/frontend-vue/app/runtime/session-gate.ts +8 -8
  98. package/frontend-vue/app/runtime/shortcuts.ts +12 -12
  99. package/frontend-vue/app/runtime/sidecar-status.ts +61 -61
  100. package/frontend-vue/app/runtime/typed-message.ts +11 -0
  101. package/frontend-vue/dms.frontend.ts +12 -12
  102. package/frontend-vue/eslint.config.mjs +13 -13
  103. package/frontend-vue/package.json +11 -4
  104. package/frontend-vue/pnpm-lock.yaml +298 -96
  105. package/frontend-vue/tests/backoff.test.ts +26 -30
  106. package/frontend-vue/tests/channel-client.test.ts +187 -188
  107. package/frontend-vue/tests/chat/markdown.test.ts +271 -0
  108. package/frontend-vue/tests/chat/useChatChannel.test.ts +124 -0
  109. package/frontend-vue/tests/chat/useConversation.test.ts +283 -0
  110. package/frontend-vue/tests/chat-panel.test.ts +354 -0
  111. package/frontend-vue/tests/chat-transport.test.ts +84 -57
  112. package/frontend-vue/tests/host-commands.test.ts +126 -87
  113. package/frontend-vue/tests/navigation.test.ts +65 -34
  114. package/frontend-vue/tests/overlay-prefs.test.ts +49 -49
  115. package/frontend-vue/tests/panel-state.test.ts +58 -0
  116. package/frontend-vue/tests/session-gate.test.ts +33 -33
  117. package/frontend-vue/tests/sidecar-guest.test.ts +46 -46
  118. package/frontend-vue/tests/sidecar-status.test.ts +29 -29
  119. package/frontend-vue/tsconfig.chat.json +16 -0
  120. package/frontend-vue/vitest.config.ts +6 -4
  121. package/package.json +5 -5
  122. package/sidecar/dist/agent/effective-mode.d.ts +2 -2
  123. package/sidecar/dist/agent/effective-mode.js +1 -1
  124. package/sidecar/dist/agent/effective-mode.js.map +1 -1
  125. package/sidecar/dist/agent/host-context.js +7 -9
  126. package/sidecar/dist/agent/host-context.js.map +1 -1
  127. package/sidecar/dist/agent/permission-bus.d.ts +1 -1
  128. package/sidecar/dist/agent/permission-bus.js +2 -2
  129. package/sidecar/dist/agent/permission-bus.js.map +1 -1
  130. package/sidecar/dist/agent/provider.d.ts +2 -2
  131. package/sidecar/dist/agent/question-bus.d.ts +1 -1
  132. package/sidecar/dist/agent/question-bus.js +2 -2
  133. package/sidecar/dist/agent/question-bus.js.map +1 -1
  134. package/sidecar/dist/agent/runner.d.ts +2 -2
  135. package/sidecar/dist/agent/runner.js +24 -8
  136. package/sidecar/dist/agent/runner.js.map +1 -1
  137. package/sidecar/dist/agent/session.d.ts +2 -2
  138. package/sidecar/dist/agent/session.js +49 -9
  139. package/sidecar/dist/agent/session.js.map +1 -1
  140. package/sidecar/dist/agent/switching-runner.js +10 -5
  141. package/sidecar/dist/agent/switching-runner.js.map +1 -1
  142. package/sidecar/dist/agent/turn-end-reasons.d.ts +2 -0
  143. package/sidecar/dist/agent/turn-end-reasons.js +6 -1
  144. package/sidecar/dist/agent/turn-end-reasons.js.map +1 -1
  145. package/sidecar/dist/constants/agent.d.ts +4 -1
  146. package/sidecar/dist/constants/agent.js +4 -1
  147. package/sidecar/dist/constants/agent.js.map +1 -1
  148. package/sidecar/dist/constants/cli.d.ts +0 -1
  149. package/sidecar/dist/constants/cli.js +0 -1
  150. package/sidecar/dist/constants/cli.js.map +1 -1
  151. package/sidecar/dist/constants/codex.d.ts +5 -2
  152. package/sidecar/dist/constants/codex.js +5 -2
  153. package/sidecar/dist/constants/codex.js.map +1 -1
  154. package/sidecar/dist/constants/http.d.ts +0 -9
  155. package/sidecar/dist/constants/http.js +0 -19
  156. package/sidecar/dist/constants/http.js.map +1 -1
  157. package/sidecar/dist/constants/mcp.d.ts +1 -1
  158. package/sidecar/dist/constants/mcp.js +1 -1
  159. package/sidecar/dist/constants/mcp.js.map +1 -1
  160. package/sidecar/dist/constants/paths.d.ts +0 -1
  161. package/sidecar/dist/constants/paths.js +0 -1
  162. package/sidecar/dist/constants/paths.js.map +1 -1
  163. package/sidecar/dist/constants/ws.d.ts +3 -4
  164. package/sidecar/dist/constants/ws.js +3 -4
  165. package/sidecar/dist/constants/ws.js.map +1 -1
  166. package/sidecar/dist/index.d.ts +0 -1
  167. package/sidecar/dist/index.js +6 -10
  168. package/sidecar/dist/index.js.map +1 -1
  169. package/sidecar/dist/mcp/tools/find-pages-using.js +5 -10
  170. package/sidecar/dist/mcp/tools/find-pages-using.js.map +1 -1
  171. package/sidecar/dist/mcp/types.d.ts +1 -1
  172. package/sidecar/dist/pages/page-filepath.d.ts +4 -0
  173. package/sidecar/dist/pages/page-filepath.js +28 -0
  174. package/sidecar/dist/pages/page-filepath.js.map +1 -0
  175. package/sidecar/dist/prompts/agent.d.ts +1 -1
  176. package/sidecar/dist/prompts/agent.js +1 -1
  177. package/sidecar/dist/prompts/agent.js.map +1 -1
  178. package/sidecar/dist/protocol/events.js +2 -2
  179. package/sidecar/dist/protocol/events.js.map +1 -1
  180. package/sidecar/dist/protocol/messages.d.ts +4 -15
  181. package/sidecar/dist/protocol/messages.js +4 -11
  182. package/sidecar/dist/protocol/messages.js.map +1 -1
  183. package/sidecar/dist/providers/claude/config.d.ts +2 -2
  184. package/sidecar/dist/providers/claude/config.js +2 -2
  185. package/sidecar/dist/providers/claude/config.js.map +1 -1
  186. package/sidecar/dist/providers/claude/provider.js +1 -1
  187. package/sidecar/dist/providers/claude/provider.js.map +1 -1
  188. package/sidecar/dist/providers/codex/config.d.ts +3 -2
  189. package/sidecar/dist/providers/codex/config.js +3 -3
  190. package/sidecar/dist/providers/codex/config.js.map +1 -1
  191. package/sidecar/dist/providers/codex/process.js +107 -74
  192. package/sidecar/dist/providers/codex/process.js.map +1 -1
  193. package/sidecar/dist/providers/codex/provider.js +68 -31
  194. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  195. package/sidecar/dist/providers/codex/skills.d.ts +1 -0
  196. package/sidecar/dist/providers/codex/skills.js +4 -1
  197. package/sidecar/dist/providers/codex/skills.js.map +1 -1
  198. package/sidecar/dist/providers/codex/stop.d.ts +4 -0
  199. package/sidecar/dist/providers/codex/stop.js +71 -0
  200. package/sidecar/dist/providers/codex/stop.js.map +1 -0
  201. package/sidecar/dist/server/{iframe-socket-registry.d.ts → chat-socket-registry.d.ts} +3 -2
  202. package/sidecar/dist/server/{iframe-socket-registry.js → chat-socket-registry.js} +4 -3
  203. package/sidecar/dist/server/chat-socket-registry.js.map +1 -0
  204. package/sidecar/dist/server/client-auth.js +2 -14
  205. package/sidecar/dist/server/client-auth.js.map +1 -1
  206. package/sidecar/dist/server/host-command-router.d.ts +5 -0
  207. package/sidecar/dist/server/host-command-router.js +4 -0
  208. package/sidecar/dist/server/host-command-router.js.map +1 -0
  209. package/sidecar/dist/server/host-socket-registry.d.ts +1 -1
  210. package/sidecar/dist/server/host-socket-registry.js +16 -13
  211. package/sidecar/dist/server/host-socket-registry.js.map +1 -1
  212. package/sidecar/dist/server/http.d.ts +0 -1
  213. package/sidecar/dist/server/http.js +1 -44
  214. package/sidecar/dist/server/http.js.map +1 -1
  215. package/sidecar/dist/server/routing.d.ts +5 -6
  216. package/sidecar/dist/server/routing.js +18 -28
  217. package/sidecar/dist/server/routing.js.map +1 -1
  218. package/sidecar/dist/server/ws.d.ts +2 -2
  219. package/sidecar/dist/server/ws.js +41 -39
  220. package/sidecar/dist/server/ws.js.map +1 -1
  221. package/sidecar/dist/state/host-state.d.ts +10 -2
  222. package/sidecar/dist/state/host-state.js +13 -3
  223. package/sidecar/dist/state/host-state.js.map +1 -1
  224. package/sidecar/dist/state/settings-store.js +2 -2
  225. package/sidecar/dist/state/settings-store.js.map +1 -1
  226. package/sidecar/dist/state/settings-types.d.ts +3 -3
  227. package/sidecar/dist/state/settings-types.js +1 -1
  228. package/sidecar/dist/state/settings-types.js.map +1 -1
  229. package/sidecar/package.json +3 -5
  230. package/dist/chatbox/passthrough.d.ts +0 -3
  231. package/dist/chatbox/passthrough.js +0 -53
  232. package/dist/chatbox/passthrough.js.map +0 -1
  233. package/dist/constants/chatbox.d.ts +0 -10
  234. package/dist/constants/chatbox.js +0 -35
  235. package/dist/constants/chatbox.js.map +0 -1
  236. package/dist/routes/chatbox.d.ts +0 -7
  237. package/dist/routes/chatbox.js +0 -47
  238. package/dist/routes/chatbox.js.map +0 -1
  239. package/frontend-vue/app/runtime/overlay.ts +0 -400
  240. package/frontend-vue/app/runtime/theme-bridge.ts +0 -189
  241. package/sidecar/chatbox/dist/assets/index-C8u7mkHn.css +0 -1
  242. package/sidecar/chatbox/dist/assets/index-tdZvY7ee.js +0 -52
  243. package/sidecar/chatbox/dist/index.html +0 -13
  244. package/sidecar/dist/server/iframe-socket-registry.js.map +0 -1
@@ -1,44 +1,40 @@
1
- import { describe, expect, it } from 'vitest'
2
- import {
3
- createBackoff,
4
- nextDelay,
5
- resetBackoff,
6
- } from '../app/runtime/backoff'
7
- import { CHANNEL_RECONNECT_DELAYS_MS } from '../app/runtime/constants'
1
+ import { describe, expect, it } from "vitest";
2
+ import { createBackoff, nextDelay, resetBackoff } from "../app/runtime/backoff";
3
+ import { CHANNEL_RECONNECT_DELAYS_MS } from "../app/runtime/constants";
8
4
 
9
- describe('backoff helper', () => {
10
- it('returns each schedule entry in order then caps at the last value', () => {
11
- const state = createBackoff()
12
- const observed: number[] = []
5
+ describe("backoff helper", () => {
6
+ it("returns each schedule entry in order then caps at the last value", () => {
7
+ const state = createBackoff();
8
+ const observed: number[] = [];
13
9
  for (let i = 0; i < CHANNEL_RECONNECT_DELAYS_MS.length + 3; i += 1) {
14
- observed.push(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS))
10
+ observed.push(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS));
15
11
  }
16
12
  const expected = [
17
13
  ...CHANNEL_RECONNECT_DELAYS_MS,
18
14
  CHANNEL_RECONNECT_DELAYS_MS[CHANNEL_RECONNECT_DELAYS_MS.length - 1],
19
15
  CHANNEL_RECONNECT_DELAYS_MS[CHANNEL_RECONNECT_DELAYS_MS.length - 1],
20
16
  CHANNEL_RECONNECT_DELAYS_MS[CHANNEL_RECONNECT_DELAYS_MS.length - 1],
21
- ]
22
- expect(observed).toEqual(expected)
23
- })
17
+ ];
18
+ expect(observed).toEqual(expected);
19
+ });
24
20
 
25
- it('resetBackoff returns the schedule to schedule[0]', () => {
26
- const state = createBackoff()
27
- nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)
28
- nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)
29
- resetBackoff(state)
21
+ it("resetBackoff returns the schedule to schedule[0]", () => {
22
+ const state = createBackoff();
23
+ nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS);
24
+ nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS);
25
+ resetBackoff(state);
30
26
  expect(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)).toBe(
31
27
  CHANNEL_RECONNECT_DELAYS_MS[0],
32
- )
33
- })
28
+ );
29
+ });
34
30
 
35
- it('uses the configured 30s ceiling on the last delay', () => {
36
- const state = createBackoff()
37
- const lastIndex = CHANNEL_RECONNECT_DELAYS_MS.length - 1
31
+ it("uses the configured 30s ceiling on the last delay", () => {
32
+ const state = createBackoff();
33
+ const lastIndex = CHANNEL_RECONNECT_DELAYS_MS.length - 1;
38
34
  for (let i = 0; i < lastIndex; i += 1) {
39
- nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)
35
+ nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS);
40
36
  }
41
- expect(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)).toBe(30_000)
42
- expect(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)).toBe(30_000)
43
- })
44
- })
37
+ expect(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)).toBe(30_000);
38
+ expect(nextDelay(state, CHANNEL_RECONNECT_DELAYS_MS)).toBe(30_000);
39
+ });
40
+ });
@@ -1,48 +1,48 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest'
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import {
3
3
  type ChannelStatus,
4
4
  createChannelClient,
5
- } from '../app/runtime/channel-client'
6
- import { CHANNEL_RECONNECT_DELAYS_MS } from '../app/runtime/constants'
5
+ } from "../app/runtime/channel-client";
6
+ import { CHANNEL_RECONNECT_DELAYS_MS } from "../app/runtime/constants";
7
7
 
8
8
  interface FakeStream {
9
- path: string
10
- emit: (name: string, data: string) => void
11
- end: () => void
9
+ path: string;
10
+ emit: (name: string, data: string) => void;
11
+ end: () => void;
12
12
  }
13
13
 
14
14
  interface Harness {
15
- streams: FakeStream[]
16
- posts: Array<{ path: string; body: unknown }>
17
- statuses: ChannelStatus[]
18
- ready: ReturnType<typeof vi.fn>
19
- frames: string[]
15
+ streams: FakeStream[];
16
+ posts: Array<{ path: string; body: unknown }>;
17
+ statuses: ChannelStatus[];
18
+ ready: ReturnType<typeof vi.fn>;
19
+ frames: string[];
20
20
  }
21
21
 
22
- const encoder = new TextEncoder()
23
- const READY = '{"connections":{"host":"h-1","chat":"c 1"}}'
22
+ const encoder = new TextEncoder();
23
+ const READY = '{"connectionId":"c 1"}';
24
24
 
25
25
  function openFakeStream(
26
26
  streams: FakeStream[],
27
27
  path: string,
28
28
  signal: AbortSignal,
29
29
  ): Promise<Response> {
30
- let controller!: ReadableStreamDefaultController<Uint8Array>
30
+ let controller!: ReadableStreamDefaultController<Uint8Array>;
31
31
  const body = new ReadableStream<Uint8Array>({
32
32
  start: (c) => {
33
- controller = c
33
+ controller = c;
34
34
  },
35
- })
36
- signal.addEventListener('abort', () =>
37
- controller.error(new DOMException('aborted', 'AbortError')),
38
- )
35
+ });
36
+ signal.addEventListener("abort", () =>
37
+ controller.error(new DOMException("aborted", "AbortError")),
38
+ );
39
39
  streams.push({
40
40
  path,
41
41
  emit: (name, data) =>
42
42
  controller.enqueue(encoder.encode(`event: ${name}\ndata: ${data}\n\n`)),
43
43
  end: () => controller.close(),
44
- })
45
- return Promise.resolve(new Response(body, { status: 200 }))
44
+ });
45
+ return Promise.resolve(new Response(body, { status: 200 }));
46
46
  }
47
47
 
48
48
  function startClient(post = vi.fn(async () => undefined)) {
@@ -52,196 +52,195 @@ function startClient(post = vi.fn(async () => undefined)) {
52
52
  statuses: [],
53
53
  ready: vi.fn(),
54
54
  frames: [],
55
- }
55
+ };
56
56
  const client = createChannelClient({
57
- channels: ['host', 'chat'],
58
57
  post: async (path, body) => {
59
- harness.posts.push({ path, body })
60
- return post(path, body)
58
+ harness.posts.push({ path, body });
59
+ return post(path, body);
61
60
  },
62
61
  openStream: (path, signal) => openFakeStream(harness.streams, path, signal),
63
62
  onReady: harness.ready,
64
- onFrame: (channel, raw) => harness.frames.push(`${channel} ${raw}`),
63
+ onFrame: (raw) => harness.frames.push(raw),
65
64
  onStatusChange: (status) => harness.statuses.push(status),
66
- })
67
- client.start()
68
- return { client, harness }
65
+ });
66
+ client.start();
67
+ return { client, harness };
69
68
  }
70
69
 
71
70
  async function flush(): Promise<void> {
72
- for (let i = 0; i < 10; i += 1) await Promise.resolve()
73
- await new Promise((resolve) => setTimeout(resolve, 0))
71
+ for (let i = 0; i < 10; i += 1) await Promise.resolve();
72
+ await new Promise((resolve) => setTimeout(resolve, 0));
74
73
  }
75
74
 
76
75
  afterEach(() => {
77
- vi.useRealTimers()
78
- })
76
+ vi.useRealTimers();
77
+ });
79
78
 
80
- describe('channel client', () => {
81
- it('stays closed until started', async () => {
82
- const streams: FakeStream[] = []
79
+ describe("channel client", () => {
80
+ it("stays closed until started", async () => {
81
+ const streams: FakeStream[] = [];
83
82
  const client = createChannelClient({
84
- channels: ['host', 'chat'],
85
83
  post: vi.fn(),
86
84
  openStream: (path, signal) => openFakeStream(streams, path, signal),
87
- })
88
- await flush()
89
- expect(streams).toEqual([])
90
- expect(client.getStatus()).toBe('disconnected')
91
- })
92
-
93
- it('opens one stream for every channel, on the dashboard origin, with no credential', async () => {
94
- const { client, harness } = startClient()
95
- await flush()
85
+ });
86
+ await flush();
87
+ expect(streams).toEqual([]);
88
+ expect(client.getStatus()).toBe("disconnected");
89
+ });
90
+
91
+ it("opens one stream for the dashboard and its chat, on the dashboard origin, with no credential", async () => {
92
+ const { client, harness } = startClient();
93
+ await flush();
96
94
  expect(harness.streams.map((stream) => stream.path)).toEqual([
97
- '/ai/channels/host,chat/events',
98
- ])
99
- client.stop()
100
- })
101
-
102
- it('announces the connection, then routes each frame by channel, raw', async () => {
103
- const { client, harness } = startClient()
104
- await flush()
105
- harness.streams[0]?.emit('ready', READY)
106
- harness.streams[0]?.emit('host', '{"type":"host_command_navigate","path":"/x"}')
107
- harness.streams[0]?.emit('chat', '{"type":"run_done"}')
108
- harness.streams[0]?.emit('unknown', '{}')
109
- await flush()
110
- expect(harness.ready).toHaveBeenCalledTimes(1)
111
- expect(client.isConnected()).toBe(true)
95
+ "/ai/channel/events",
96
+ ]);
97
+ client.stop();
98
+ });
99
+
100
+ it("announces the connection, then hands over every frame raw, whoever it is for", async () => {
101
+ const { client, harness } = startClient();
102
+ await flush();
103
+ harness.streams[0]?.emit("ready", READY);
104
+ harness.streams[0]?.emit(
105
+ "message",
106
+ '{"type":"host_command_navigate","path":"/x"}',
107
+ );
108
+ harness.streams[0]?.emit("message", '{"type":"run_done"}');
109
+ harness.streams[0]?.emit("unknown", "{}");
110
+ await flush();
111
+ expect(harness.ready).toHaveBeenCalledTimes(1);
112
+ expect(client.isConnected()).toBe(true);
112
113
  expect(harness.frames).toEqual([
113
- 'host {"type":"host_command_navigate","path":"/x"}',
114
- 'chat {"type":"run_done"}',
115
- ])
116
- client.stop()
117
- })
118
-
119
- it('posts one message at a time, in order, to the connection of its channel', async () => {
120
- const pending: Array<() => void> = []
114
+ '{"type":"host_command_navigate","path":"/x"}',
115
+ '{"type":"run_done"}',
116
+ ]);
117
+ client.stop();
118
+ });
119
+
120
+ it("posts one message at a time, in order, to the connection", async () => {
121
+ const pending: Array<() => void> = [];
121
122
  const post = vi.fn(
122
123
  () => new Promise<void>((resolve) => pending.push(() => resolve())),
123
- )
124
- const { client, harness } = startClient(post)
125
- await flush()
126
- harness.streams[0]?.emit('ready', READY)
127
- await flush()
128
- client.send('chat', { n: 1 })
129
- client.send('host', { n: 2 })
130
- await flush()
124
+ );
125
+ const { client, harness } = startClient(post);
126
+ await flush();
127
+ harness.streams[0]?.emit("ready", READY);
128
+ await flush();
129
+ client.send({ n: 1 });
130
+ client.send({ n: 2 });
131
+ await flush();
131
132
  expect(harness.posts).toEqual([
132
- { path: '/ai/channels/c%201/messages', body: { n: 1 } },
133
- ])
134
- pending.shift()?.()
135
- await flush()
133
+ { path: "/ai/channel/c%201/messages", body: { n: 1 } },
134
+ ]);
135
+ pending.shift()?.();
136
+ await flush();
136
137
  expect(harness.posts.at(-1)).toEqual({
137
- path: '/ai/channels/h-1/messages',
138
+ path: "/ai/channel/c%201/messages",
138
139
  body: { n: 2 },
139
- })
140
- client.stop()
141
- })
142
-
143
- it('drops a message sent before the connection is ready, and says so', async () => {
144
- const { client, harness } = startClient()
145
- expect(client.send('chat', { n: 1 })).toBe(false)
146
- await flush()
147
- expect(harness.posts).toEqual([])
148
- harness.streams[0]?.emit('ready', READY)
149
- await flush()
150
- expect(client.send('chat', { n: 2 })).toBe(true)
151
- client.stop()
152
- })
153
-
154
- it('refuses a message for a channel the stream does not carry', async () => {
155
- const { client, harness } = startClient()
156
- await flush()
157
- harness.streams[0]?.emit('ready', READY)
158
- await flush()
159
- expect(client.send('unknown', { n: 1 })).toBe(false)
160
- await flush()
161
- expect(harness.posts).toEqual([])
162
- client.stop()
163
- })
164
-
165
- it('reconnects with backoff when the stream ends, and announces the new connection', async () => {
166
- vi.useFakeTimers()
167
- const { client, harness } = startClient()
168
- await vi.advanceTimersByTimeAsync(0)
169
- harness.streams[0]?.emit('ready', READY)
170
- await vi.advanceTimersByTimeAsync(0)
171
- harness.streams[0]?.end()
172
- await vi.advanceTimersByTimeAsync(0)
173
- expect(client.isConnected()).toBe(false)
174
- expect(harness.statuses).toEqual(['connecting', 'connected', 'reconnecting'])
175
- await vi.advanceTimersByTimeAsync(CHANNEL_RECONNECT_DELAYS_MS[0])
176
- harness.streams[1]?.emit('ready', READY)
177
- await vi.advanceTimersByTimeAsync(0)
178
- expect(harness.streams).toHaveLength(2)
179
- expect(harness.ready).toHaveBeenCalledTimes(2)
180
- expect(harness.statuses.at(-1)).toBe('connected')
181
- client.stop()
182
- })
183
-
184
- it('drops the connection and reconnects when a post fails', async () => {
185
- vi.useFakeTimers()
140
+ });
141
+ client.stop();
142
+ });
143
+
144
+ it("drops a message sent before the connection is ready, and says so", async () => {
145
+ const { client, harness } = startClient();
146
+ expect(client.send({ n: 1 })).toBe(false);
147
+ await flush();
148
+ expect(harness.posts).toEqual([]);
149
+ harness.streams[0]?.emit("ready", READY);
150
+ await flush();
151
+ expect(client.send({ n: 2 })).toBe(true);
152
+ client.stop();
153
+ });
154
+
155
+ it("reconnects with backoff when the stream ends, and announces the new connection", async () => {
156
+ vi.useFakeTimers();
157
+ const { client, harness } = startClient();
158
+ await vi.advanceTimersByTimeAsync(0);
159
+ harness.streams[0]?.emit("ready", READY);
160
+ await vi.advanceTimersByTimeAsync(0);
161
+ harness.streams[0]?.end();
162
+ await vi.advanceTimersByTimeAsync(0);
163
+ expect(client.isConnected()).toBe(false);
164
+ expect(harness.statuses).toEqual([
165
+ "connecting",
166
+ "connected",
167
+ "reconnecting",
168
+ ]);
169
+ await vi.advanceTimersByTimeAsync(CHANNEL_RECONNECT_DELAYS_MS[0]);
170
+ harness.streams[1]?.emit("ready", READY);
171
+ await vi.advanceTimersByTimeAsync(0);
172
+ expect(harness.streams).toHaveLength(2);
173
+ expect(harness.ready).toHaveBeenCalledTimes(2);
174
+ expect(harness.statuses.at(-1)).toBe("connected");
175
+ client.stop();
176
+ });
177
+
178
+ it("drops the connection and reconnects when a post fails", async () => {
179
+ vi.useFakeTimers();
186
180
  const post = vi.fn(async () => {
187
- throw new Error('404')
188
- })
189
- const { client, harness } = startClient(post)
190
- await vi.advanceTimersByTimeAsync(0)
191
- harness.streams[0]?.emit('ready', READY)
192
- await vi.advanceTimersByTimeAsync(0)
193
- client.send('chat', { n: 1 })
194
- await vi.advanceTimersByTimeAsync(0)
195
- expect(client.getStatus()).toBe('reconnecting')
196
- await vi.advanceTimersByTimeAsync(CHANNEL_RECONNECT_DELAYS_MS[0])
197
- expect(harness.streams).toHaveLength(2)
198
- client.stop()
199
- })
200
-
201
- it('says connecting while reconnectNow replaces a live stream, until the new one is ready', async () => {
202
- const { client, harness } = startClient()
203
- await flush()
204
- harness.streams[0]?.emit('ready', READY)
205
- await flush()
206
- client.reconnectNow()
207
- await flush()
208
- expect(client.getStatus()).toBe('connecting')
209
- expect(client.send('chat', { n: 1 })).toBe(false)
210
- harness.streams[1]?.emit('ready', READY)
211
- await flush()
212
- expect(harness.statuses).toEqual(['connecting', 'connected', 'connecting', 'connected'])
213
- expect(harness.ready).toHaveBeenCalledTimes(2)
214
- client.stop()
215
- })
216
-
217
- it('skips the backoff on reconnectNow', async () => {
218
- vi.useFakeTimers()
219
- const { client, harness } = startClient()
220
- await vi.advanceTimersByTimeAsync(0)
221
- harness.streams[0]?.end()
222
- await vi.advanceTimersByTimeAsync(0)
223
- client.reconnectNow()
224
- await vi.advanceTimersByTimeAsync(0)
225
- expect(harness.streams).toHaveLength(2)
226
- client.stop()
227
- })
228
-
229
- it('stops reconnecting once stopped, and opens again on start', async () => {
230
- vi.useFakeTimers()
231
- const { client, harness } = startClient()
232
- await vi.advanceTimersByTimeAsync(0)
233
- client.stop()
181
+ throw new Error("404");
182
+ });
183
+ const { client, harness } = startClient(post);
184
+ await vi.advanceTimersByTimeAsync(0);
185
+ harness.streams[0]?.emit("ready", READY);
186
+ await vi.advanceTimersByTimeAsync(0);
187
+ client.send({ n: 1 });
188
+ await vi.advanceTimersByTimeAsync(0);
189
+ expect(client.getStatus()).toBe("reconnecting");
190
+ await vi.advanceTimersByTimeAsync(CHANNEL_RECONNECT_DELAYS_MS[0]);
191
+ expect(harness.streams).toHaveLength(2);
192
+ client.stop();
193
+ });
194
+
195
+ it("says connecting while reconnectNow replaces a live stream, until the new one is ready", async () => {
196
+ const { client, harness } = startClient();
197
+ await flush();
198
+ harness.streams[0]?.emit("ready", READY);
199
+ await flush();
200
+ client.reconnectNow();
201
+ await flush();
202
+ expect(client.getStatus()).toBe("connecting");
203
+ expect(client.send({ n: 1 })).toBe(false);
204
+ harness.streams[1]?.emit("ready", READY);
205
+ await flush();
206
+ expect(harness.statuses).toEqual([
207
+ "connecting",
208
+ "connected",
209
+ "connecting",
210
+ "connected",
211
+ ]);
212
+ expect(harness.ready).toHaveBeenCalledTimes(2);
213
+ client.stop();
214
+ });
215
+
216
+ it("skips the backoff on reconnectNow", async () => {
217
+ vi.useFakeTimers();
218
+ const { client, harness } = startClient();
219
+ await vi.advanceTimersByTimeAsync(0);
220
+ harness.streams[0]?.end();
221
+ await vi.advanceTimersByTimeAsync(0);
222
+ client.reconnectNow();
223
+ await vi.advanceTimersByTimeAsync(0);
224
+ expect(harness.streams).toHaveLength(2);
225
+ client.stop();
226
+ });
227
+
228
+ it("stops reconnecting once stopped, and opens again on start", async () => {
229
+ vi.useFakeTimers();
230
+ const { client, harness } = startClient();
231
+ await vi.advanceTimersByTimeAsync(0);
232
+ client.stop();
234
233
  await vi.advanceTimersByTimeAsync(
235
234
  CHANNEL_RECONNECT_DELAYS_MS[CHANNEL_RECONNECT_DELAYS_MS.length - 1] * 2,
236
- )
237
- expect(harness.streams).toHaveLength(1)
238
- expect(client.getStatus()).toBe('disconnected')
239
- client.reconnectNow()
240
- await vi.advanceTimersByTimeAsync(0)
241
- expect(harness.streams).toHaveLength(1)
242
- client.start()
243
- await vi.advanceTimersByTimeAsync(0)
244
- expect(harness.streams).toHaveLength(2)
245
- client.stop()
246
- })
247
- })
235
+ );
236
+ expect(harness.streams).toHaveLength(1);
237
+ expect(client.getStatus()).toBe("disconnected");
238
+ client.reconnectNow();
239
+ await vi.advanceTimersByTimeAsync(0);
240
+ expect(harness.streams).toHaveLength(1);
241
+ client.start();
242
+ await vi.advanceTimersByTimeAsync(0);
243
+ expect(harness.streams).toHaveLength(2);
244
+ client.stop();
245
+ });
246
+ });