@vellumai/assistant 0.4.48 → 0.4.49

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 (252) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/runbook-trusted-contacts.md +3 -8
  6. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  7. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +0 -1
  10. package/src/__tests__/anthropic-provider.test.ts +156 -0
  11. package/src/__tests__/approval-cascade.test.ts +810 -0
  12. package/src/__tests__/approval-primitive.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +2 -0
  14. package/src/__tests__/assistant-attachments.test.ts +12 -34
  15. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  16. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  17. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  18. package/src/__tests__/channel-guardian.test.ts +0 -2
  19. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  20. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  21. package/src/__tests__/checker.test.ts +9 -29
  22. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  23. package/src/__tests__/computer-use-tools.test.ts +2 -19
  24. package/src/__tests__/config-watcher.test.ts +0 -1
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  26. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  27. package/src/__tests__/context-token-estimator.test.ts +196 -13
  28. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  29. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  30. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  31. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  32. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  33. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  34. package/src/__tests__/credential-vault-unit.test.ts +280 -49
  35. package/src/__tests__/credential-vault.test.ts +138 -16
  36. package/src/__tests__/credentials-cli.test.ts +71 -0
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  38. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  39. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  40. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  41. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  42. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  43. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  44. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  45. package/src/__tests__/heartbeat-service.test.ts +0 -1
  46. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  47. package/src/__tests__/host-shell-tool.test.ts +27 -15
  48. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  49. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  50. package/src/__tests__/integration-status.test.ts +32 -51
  51. package/src/__tests__/intent-routing.test.ts +0 -1
  52. package/src/__tests__/invite-routes-http.test.ts +10 -9
  53. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  54. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  55. package/src/__tests__/oauth-cli.test.ts +373 -14
  56. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  57. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  58. package/src/__tests__/oauth-store.test.ts +756 -0
  59. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  60. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  61. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  62. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  63. package/src/__tests__/recording-handler.test.ts +3 -4
  64. package/src/__tests__/registry.test.ts +2 -2
  65. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  66. package/src/__tests__/schedule-store.test.ts +0 -1
  67. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  68. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  69. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  70. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  71. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  72. package/src/__tests__/sequence-store.test.ts +0 -1
  73. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  74. package/src/__tests__/skill-include-graph.test.ts +66 -0
  75. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  76. package/src/__tests__/skill-load-tool.test.ts +149 -1
  77. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  78. package/src/__tests__/skills-uninstall.test.ts +1 -1
  79. package/src/__tests__/skills.test.ts +3 -3
  80. package/src/__tests__/slack-channel-config.test.ts +67 -3
  81. package/src/__tests__/slack-share-routes.test.ts +17 -19
  82. package/src/__tests__/system-prompt.test.ts +0 -1
  83. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  84. package/src/__tests__/terminal-tools.test.ts +4 -3
  85. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  86. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  87. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  88. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  90. package/src/__tests__/tool-executor.test.ts +0 -1
  91. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  92. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  93. package/src/__tests__/trust-store.test.ts +1 -22
  94. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  95. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  96. package/src/__tests__/twilio-routes.test.ts +0 -16
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  99. package/src/agent/ax-tree-compaction.test.ts +235 -0
  100. package/src/agent/loop.ts +76 -130
  101. package/src/calls/call-domain.ts +1 -6
  102. package/src/calls/relay-server.ts +9 -13
  103. package/src/calls/twilio-config.ts +2 -7
  104. package/src/calls/twilio-routes.ts +1 -2
  105. package/src/calls/voice-ingress-preflight.ts +1 -1
  106. package/src/cli/commands/browser-relay.ts +18 -12
  107. package/src/cli/commands/completions.ts +0 -3
  108. package/src/cli/commands/credentials.ts +101 -15
  109. package/src/cli/commands/oauth/apps.ts +255 -0
  110. package/src/cli/commands/oauth/connections.ts +299 -0
  111. package/src/cli/commands/oauth/index.ts +52 -0
  112. package/src/cli/commands/oauth/providers.ts +242 -0
  113. package/src/cli/commands/skills.ts +4 -338
  114. package/src/cli/program.ts +1 -5
  115. package/src/cli/reference.ts +1 -3
  116. package/src/config/assistant-feature-flags.ts +0 -3
  117. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  118. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  119. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  120. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  121. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  122. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  123. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  124. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  125. package/src/config/env-registry.ts +14 -83
  126. package/src/config/env.ts +11 -50
  127. package/src/config/feature-flag-registry.json +16 -16
  128. package/src/config/loader.ts +0 -6
  129. package/src/config/schema.ts +3 -1
  130. package/src/config/skills.ts +21 -2
  131. package/src/context/image-dimensions.ts +229 -0
  132. package/src/context/token-estimator.ts +75 -12
  133. package/src/context/window-manager.ts +49 -10
  134. package/src/daemon/assistant-attachments.ts +1 -13
  135. package/src/daemon/handlers/config-ingress.ts +8 -33
  136. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  137. package/src/daemon/handlers/config-telegram.ts +32 -16
  138. package/src/daemon/handlers/sessions.ts +10 -24
  139. package/src/daemon/handlers/shared.ts +0 -130
  140. package/src/daemon/host-cu-proxy.ts +401 -0
  141. package/src/daemon/lifecycle.ts +36 -68
  142. package/src/daemon/message-protocol.ts +3 -0
  143. package/src/daemon/message-types/computer-use.ts +2 -119
  144. package/src/daemon/message-types/host-cu.ts +19 -0
  145. package/src/daemon/message-types/messages.ts +3 -0
  146. package/src/daemon/server.ts +14 -21
  147. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  148. package/src/daemon/session-attachments.ts +1 -2
  149. package/src/daemon/session-slash.ts +1 -1
  150. package/src/daemon/session-surfaces.ts +40 -28
  151. package/src/daemon/session-tool-setup.ts +2 -9
  152. package/src/daemon/session.ts +138 -15
  153. package/src/daemon/tool-side-effects.ts +2 -8
  154. package/src/daemon/watch-handler.ts +2 -2
  155. package/src/events/tool-metrics-listener.ts +2 -2
  156. package/src/hooks/manager.ts +1 -4
  157. package/src/inbound/public-ingress-urls.ts +7 -7
  158. package/src/logfire.ts +16 -5
  159. package/src/memory/conversation-key-store.ts +21 -0
  160. package/src/memory/db-init.ts +4 -0
  161. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  162. package/src/memory/migrations/index.ts +1 -0
  163. package/src/memory/schema/index.ts +1 -0
  164. package/src/memory/schema/oauth.ts +65 -0
  165. package/src/messaging/provider.ts +4 -4
  166. package/src/messaging/providers/gmail/client.ts +82 -2
  167. package/src/messaging/providers/gmail/people-client.ts +10 -10
  168. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  169. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  170. package/src/messaging/registry.ts +2 -32
  171. package/src/notifications/copy-composer.ts +0 -5
  172. package/src/notifications/signal.ts +4 -5
  173. package/src/oauth/byo-connection.test.ts +126 -25
  174. package/src/oauth/byo-connection.ts +22 -6
  175. package/src/oauth/connect-orchestrator.ts +113 -57
  176. package/src/oauth/connect-types.ts +17 -23
  177. package/src/oauth/connection-resolver.ts +35 -11
  178. package/src/oauth/connection.ts +1 -1
  179. package/src/oauth/manual-token-connection.ts +104 -0
  180. package/src/oauth/oauth-store.ts +496 -0
  181. package/src/oauth/platform-connection.test.ts +29 -0
  182. package/src/oauth/platform-connection.ts +6 -5
  183. package/src/oauth/provider-behaviors.ts +124 -0
  184. package/src/oauth/scope-policy.ts +9 -2
  185. package/src/oauth/seed-providers.ts +161 -0
  186. package/src/oauth/token-persistence.ts +74 -78
  187. package/src/permissions/checker.ts +3 -3
  188. package/src/permissions/defaults.ts +0 -1
  189. package/src/permissions/prompter.ts +10 -1
  190. package/src/permissions/trust-store.ts +13 -0
  191. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  192. package/src/prompts/system-prompt.ts +28 -40
  193. package/src/providers/anthropic/client.ts +133 -24
  194. package/src/providers/retry.ts +1 -27
  195. package/src/runtime/auth/route-policy.ts +0 -3
  196. package/src/runtime/channel-reply-delivery.ts +0 -40
  197. package/src/runtime/gateway-client.ts +0 -7
  198. package/src/runtime/http-server.ts +8 -6
  199. package/src/runtime/http-types.ts +2 -2
  200. package/src/runtime/middleware/twilio-validation.ts +1 -11
  201. package/src/runtime/pending-interactions.ts +14 -12
  202. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  203. package/src/runtime/routes/conversation-routes.ts +73 -19
  204. package/src/runtime/routes/events-routes.ts +21 -11
  205. package/src/runtime/routes/host-cu-routes.ts +97 -0
  206. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  207. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  208. package/src/runtime/routes/log-export-routes.ts +126 -8
  209. package/src/runtime/routes/settings-routes.ts +55 -48
  210. package/src/runtime/routes/surface-action-routes.ts +1 -1
  211. package/src/runtime/routes/watch-routes.ts +128 -0
  212. package/src/schedule/integration-status.ts +10 -9
  213. package/src/security/credential-key.ts +0 -156
  214. package/src/security/keychain-broker-client.ts +5 -6
  215. package/src/security/oauth2.ts +1 -1
  216. package/src/security/token-manager.ts +119 -46
  217. package/src/skills/catalog-install.ts +358 -0
  218. package/src/skills/include-graph.ts +32 -0
  219. package/src/telegram/bot-username.ts +2 -3
  220. package/src/tools/browser/network-recorder.ts +1 -1
  221. package/src/tools/browser/network-recording-types.ts +1 -1
  222. package/src/tools/computer-use/definitions.ts +46 -11
  223. package/src/tools/computer-use/registry.ts +4 -5
  224. package/src/tools/credentials/broker.ts +1 -2
  225. package/src/tools/credentials/metadata-store.ts +17 -121
  226. package/src/tools/credentials/vault.ts +94 -167
  227. package/src/tools/registry.ts +2 -7
  228. package/src/tools/skills/load.ts +62 -3
  229. package/src/tools/watch/watch-state.ts +0 -12
  230. package/src/util/logger.ts +7 -41
  231. package/src/util/platform.ts +9 -28
  232. package/src/watcher/providers/google-calendar.ts +2 -1
  233. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  234. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  235. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  236. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  237. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  238. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  239. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  240. package/src/cli/commands/dev.ts +0 -129
  241. package/src/cli/commands/map.ts +0 -391
  242. package/src/cli/commands/oauth.ts +0 -77
  243. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  244. package/src/daemon/computer-use-session.ts +0 -1026
  245. package/src/daemon/ride-shotgun-handler.ts +0 -569
  246. package/src/oauth/provider-base-urls.ts +0 -21
  247. package/src/oauth/provider-profiles.ts +0 -192
  248. package/src/prompts/computer-use-prompt.ts +0 -98
  249. package/src/runtime/routes/computer-use-routes.ts +0 -641
  250. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  251. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  252. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -1,452 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- import type { HandlerContext } from "../daemon/handlers/shared.js";
4
-
5
- // ── Mocks ──────────────────────────────────────────────────────────
6
-
7
- let mockEnsureChromeResult = {
8
- baseUrl: "http://localhost:9222",
9
- launchedByUs: false,
10
- userDataDir: "/tmp/cdp-test",
11
- };
12
- let mockEnsureChromeShouldThrow = false;
13
- let mockMinimizeCalled = false;
14
- let mockMinimizeBaseUrl: string | undefined;
15
-
16
- mock.module("../tools/browser/chrome-cdp.js", () => ({
17
- ensureChromeWithCdp: async () => {
18
- if (mockEnsureChromeShouldThrow) {
19
- throw new Error("Chrome launch failed");
20
- }
21
- return { ...mockEnsureChromeResult };
22
- },
23
- minimizeChromeWindow: async (baseUrl: string) => {
24
- mockMinimizeCalled = true;
25
- mockMinimizeBaseUrl = baseUrl;
26
- },
27
- isCdpReady: async () => true,
28
- restoreChromeWindow: async () => {},
29
- }));
30
-
31
- let mockRecorderStartCalls = 0;
32
- let mockRecorderStartShouldThrow = false;
33
- let mockRecorderStartThrowCount = 0;
34
- let mockRecorderConstructorCdpBaseUrl: string | undefined;
35
-
36
- mock.module("../tools/browser/network-recorder.js", () => ({
37
- NetworkRecorder: class MockNetworkRecorder {
38
- loginSignals: string[] = [];
39
- onLoginDetected?: () => void;
40
- get entryCount() {
41
- return 0;
42
- }
43
-
44
- constructor(_targetDomain?: string, cdpBaseUrl?: string) {
45
- mockRecorderConstructorCdpBaseUrl = cdpBaseUrl;
46
- }
47
-
48
- async startDirect() {
49
- mockRecorderStartCalls++;
50
- if (
51
- mockRecorderStartShouldThrow &&
52
- mockRecorderStartCalls <= mockRecorderStartThrowCount
53
- ) {
54
- throw new Error("CDP not ready");
55
- }
56
- }
57
-
58
- async stop() {
59
- return [];
60
- }
61
-
62
- async extractCookies() {
63
- return [];
64
- }
65
- },
66
- }));
67
-
68
- mock.module("../tools/browser/recording-store.js", () => ({
69
- saveRecording: () => "/tmp/test-recording.json",
70
- }));
71
-
72
- mock.module("../tools/browser/auto-navigate.js", () => ({
73
- autoNavigate: async () => [],
74
- }));
75
-
76
- mock.module("../util/logger.js", () => ({
77
- getLogger: () => ({
78
- info: () => {},
79
- debug: () => {},
80
- warn: () => {},
81
- error: () => {},
82
- }),
83
- }));
84
-
85
- const mockLastSummaryBySession = new Map<string, string>();
86
- mock.module("../daemon/watch-handler.js", () => ({
87
- generateSummary: async () => {},
88
- lastSummaryBySession: mockLastSummaryBySession,
89
- }));
90
-
91
- // ── Import under test (after mocks) ───────────────────────────────
92
-
93
- const { handleRideShotgunStart, handleRideShotgunStop } =
94
- await import("../daemon/ride-shotgun-handler.js");
95
- const { watchSessions } = await import("../tools/watch/watch-state.js");
96
-
97
- // ── Helpers ────────────────────────────────────────────────────────
98
-
99
- function makeMockCtx() {
100
- const sent: unknown[] = [];
101
- return {
102
- send: (msg: unknown) => sent.push(msg),
103
- sent,
104
- } as unknown as HandlerContext & { sent: unknown[] };
105
- }
106
-
107
- function waitForRecorderStart(timeoutMs = 3000): Promise<void> {
108
- return new Promise((resolve, reject) => {
109
- const start = Date.now();
110
- const poll = setInterval(() => {
111
- if (mockRecorderStartCalls > 0) {
112
- clearInterval(poll);
113
- resolve();
114
- } else if (Date.now() - start > timeoutMs) {
115
- clearInterval(poll);
116
- reject(new Error("Timed out waiting for recorder start"));
117
- }
118
- }, 50);
119
- });
120
- }
121
-
122
- // ── Tests ──────────────────────────────────────────────────────────
123
-
124
- describe("ride-shotgun-handler", () => {
125
- beforeEach(() => {
126
- mockRecorderStartCalls = 0;
127
- mockRecorderStartShouldThrow = false;
128
- mockRecorderStartThrowCount = 0;
129
- mockRecorderConstructorCdpBaseUrl = undefined;
130
- mockMinimizeCalled = false;
131
- mockMinimizeBaseUrl = undefined;
132
- mockEnsureChromeShouldThrow = false;
133
- mockEnsureChromeResult = {
134
- baseUrl: "http://localhost:9222",
135
- launchedByUs: false,
136
- userDataDir: "/tmp/cdp-test",
137
- };
138
- mockLastSummaryBySession.clear();
139
- watchSessions.clear();
140
- });
141
-
142
- afterEach(() => {
143
- // Clean up any dangling sessions
144
- for (const [, session] of watchSessions) {
145
- if (session.timeoutHandle) clearTimeout(session.timeoutHandle);
146
- }
147
- watchSessions.clear();
148
- });
149
-
150
- test("learn mode calls ensureChromeWithCdp before starting recorder", async () => {
151
- const ctx = makeMockCtx();
152
-
153
- await handleRideShotgunStart(
154
- {
155
- type: "ride_shotgun_start",
156
- durationSeconds: 60,
157
- intervalSeconds: 5,
158
- mode: "learn",
159
- targetDomain: "example.com",
160
- autoNavigate: false,
161
- },
162
- ctx,
163
- );
164
-
165
- // Background recording start — wait for it
166
- await waitForRecorderStart();
167
-
168
- expect(mockRecorderStartCalls).toBe(1);
169
- // The recorder should receive the CDP base URL from the session
170
- expect(mockRecorderConstructorCdpBaseUrl).toBe("http://localhost:9222");
171
- });
172
-
173
- test("learn mode passes CDP base URL to NetworkRecorder constructor", async () => {
174
- mockEnsureChromeResult = {
175
- baseUrl: "http://localhost:9333",
176
- launchedByUs: true,
177
- userDataDir: "/tmp/cdp-custom",
178
- };
179
-
180
- const ctx = makeMockCtx();
181
-
182
- await handleRideShotgunStart(
183
- {
184
- type: "ride_shotgun_start",
185
- durationSeconds: 60,
186
- intervalSeconds: 5,
187
- mode: "learn",
188
- targetDomain: "example.com",
189
- autoNavigate: false,
190
- },
191
- ctx,
192
- );
193
-
194
- await waitForRecorderStart();
195
-
196
- expect(mockRecorderConstructorCdpBaseUrl).toBe("http://localhost:9333");
197
- });
198
-
199
- test("learn mode does not start recorder when ensureChromeWithCdp fails", async () => {
200
- mockEnsureChromeShouldThrow = true;
201
-
202
- const ctx = makeMockCtx();
203
-
204
- await handleRideShotgunStart(
205
- {
206
- type: "ride_shotgun_start",
207
- durationSeconds: 60,
208
- intervalSeconds: 5,
209
- mode: "learn",
210
- targetDomain: "example.com",
211
- autoNavigate: false,
212
- },
213
- ctx,
214
- );
215
-
216
- // Give background task time to execute
217
- await new Promise((r) => setTimeout(r, 200));
218
-
219
- expect(mockRecorderStartCalls).toBe(0);
220
- });
221
-
222
- test("learn mode minimizes Chrome on completion when assistant launched it", async () => {
223
- mockEnsureChromeResult = {
224
- baseUrl: "http://localhost:9222",
225
- launchedByUs: true,
226
- userDataDir: "/tmp/cdp-test",
227
- };
228
-
229
- const ctx = makeMockCtx();
230
-
231
- await handleRideShotgunStart(
232
- {
233
- type: "ride_shotgun_start",
234
- durationSeconds: 60,
235
- intervalSeconds: 5,
236
- mode: "learn",
237
- targetDomain: "example.com",
238
- autoNavigate: false,
239
- },
240
- ctx,
241
- );
242
-
243
- await waitForRecorderStart();
244
-
245
- // Find the session and stop it
246
- const watchId = [...watchSessions.keys()][0]!;
247
- await handleRideShotgunStop({ type: "ride_shotgun_stop", watchId }, ctx);
248
-
249
- expect(mockMinimizeCalled).toBe(true);
250
- expect(mockMinimizeBaseUrl).toBe("http://localhost:9222");
251
- });
252
-
253
- test("learn mode does not minimize Chrome on completion when user launched it", async () => {
254
- mockEnsureChromeResult = {
255
- baseUrl: "http://localhost:9222",
256
- launchedByUs: false,
257
- userDataDir: "/tmp/cdp-test",
258
- };
259
-
260
- const ctx = makeMockCtx();
261
-
262
- await handleRideShotgunStart(
263
- {
264
- type: "ride_shotgun_start",
265
- durationSeconds: 60,
266
- intervalSeconds: 5,
267
- mode: "learn",
268
- targetDomain: "example.com",
269
- autoNavigate: false,
270
- },
271
- ctx,
272
- );
273
-
274
- await waitForRecorderStart();
275
-
276
- // Find the session and stop it
277
- const watchId = [...watchSessions.keys()][0]!;
278
- await handleRideShotgunStop({ type: "ride_shotgun_stop", watchId }, ctx);
279
-
280
- expect(mockMinimizeCalled).toBe(false);
281
- });
282
-
283
- test("observe mode does not call ensureChromeWithCdp", async () => {
284
- const ctx = makeMockCtx();
285
-
286
- await handleRideShotgunStart(
287
- {
288
- type: "ride_shotgun_start",
289
- durationSeconds: 60,
290
- intervalSeconds: 5,
291
- mode: "observe",
292
- },
293
- ctx,
294
- );
295
-
296
- // Give time for any background tasks
297
- await new Promise((r) => setTimeout(r, 200));
298
-
299
- // In observe mode, no recorder should be started
300
- expect(mockRecorderStartCalls).toBe(0);
301
-
302
- // Clean up
303
- const watchId = [...watchSessions.keys()][0]!;
304
- await handleRideShotgunStop({ type: "ride_shotgun_stop", watchId }, ctx);
305
- });
306
-
307
- test("sends watch_started message with session IDs", async () => {
308
- const ctx = makeMockCtx();
309
-
310
- await handleRideShotgunStart(
311
- {
312
- type: "ride_shotgun_start",
313
- durationSeconds: 30,
314
- intervalSeconds: 5,
315
- mode: "learn",
316
- targetDomain: "example.com",
317
- autoNavigate: false,
318
- },
319
- ctx,
320
- );
321
-
322
- const startMsg = ctx.sent.find(
323
- (m: any) => m.type === "watch_started",
324
- ) as any;
325
- expect(startMsg).toBeDefined();
326
- expect(startMsg.sessionId).toBeDefined();
327
- expect(startMsg.watchId).toBeDefined();
328
- expect(startMsg.durationSeconds).toBe(30);
329
-
330
- // Clean up
331
- const watchId = startMsg.watchId;
332
- await handleRideShotgunStop({ type: "ride_shotgun_stop", watchId }, ctx);
333
- });
334
-
335
- test("sends ride_shotgun_error when ensureChromeWithCdp fails", async () => {
336
- mockEnsureChromeShouldThrow = true;
337
-
338
- const ctx = makeMockCtx();
339
-
340
- await handleRideShotgunStart(
341
- {
342
- type: "ride_shotgun_start",
343
- durationSeconds: 60,
344
- intervalSeconds: 5,
345
- mode: "learn",
346
- targetDomain: "example.com",
347
- autoNavigate: false,
348
- },
349
- ctx,
350
- );
351
-
352
- // Give background task time to execute and complete session
353
- await new Promise((r) => setTimeout(r, 500));
354
-
355
- const errorMsg = ctx.sent.find(
356
- (m: any) => m.type === "ride_shotgun_error",
357
- ) as any;
358
- expect(errorMsg).toBeDefined();
359
- expect(errorMsg.watchId).toBeDefined();
360
- expect(errorMsg.sessionId).toBeDefined();
361
- expect(errorMsg.message).toContain("Chrome CDP");
362
- });
363
-
364
- test("cleans up session when ensureChromeWithCdp fails", async () => {
365
- mockEnsureChromeShouldThrow = true;
366
-
367
- const ctx = makeMockCtx();
368
-
369
- await handleRideShotgunStart(
370
- {
371
- type: "ride_shotgun_start",
372
- durationSeconds: 60,
373
- intervalSeconds: 5,
374
- mode: "learn",
375
- targetDomain: "example.com",
376
- autoNavigate: false,
377
- },
378
- ctx,
379
- );
380
-
381
- // Give background task time to execute
382
- await new Promise((r) => setTimeout(r, 500));
383
-
384
- // Session should be completed (not left hanging for the full duration)
385
- const session = [...watchSessions.values()][0];
386
- expect(session?.status).toBe("completed");
387
- });
388
-
389
- test("reports failure summary when no recorder ever started", async () => {
390
- mockEnsureChromeShouldThrow = true;
391
-
392
- const ctx = makeMockCtx();
393
-
394
- await handleRideShotgunStart(
395
- {
396
- type: "ride_shotgun_start",
397
- durationSeconds: 60,
398
- intervalSeconds: 5,
399
- mode: "learn",
400
- targetDomain: "example.com",
401
- autoNavigate: false,
402
- },
403
- ctx,
404
- );
405
-
406
- // Give background task time to execute
407
- await new Promise((r) => setTimeout(r, 500));
408
-
409
- // The result message should indicate the specific CDP failure
410
- const resultMsg = ctx.sent.find(
411
- (m: any) => m.type === "ride_shotgun_result",
412
- ) as any;
413
- expect(resultMsg).toBeDefined();
414
- expect(resultMsg.summary).toContain("failed");
415
- expect(resultMsg.summary).toContain("browser could not be started");
416
- expect(resultMsg.summary).not.toContain("recording saved");
417
- });
418
-
419
- test("sends ride_shotgun_error when all 10 recorder retries fail", async () => {
420
- mockRecorderStartShouldThrow = true;
421
- mockRecorderStartThrowCount = 10;
422
-
423
- const ctx = makeMockCtx();
424
-
425
- await handleRideShotgunStart(
426
- {
427
- type: "ride_shotgun_start",
428
- durationSeconds: 60,
429
- intervalSeconds: 5,
430
- mode: "learn",
431
- targetDomain: "example.com",
432
- autoNavigate: false,
433
- },
434
- ctx,
435
- );
436
-
437
- // Wait for all 10 retry attempts (each has a 2s delay except the last)
438
- // 9 retries * 2s = 18s, but mock doesn't actually wait — it should complete quickly
439
- // The mock delays are real setTimeout calls, so we need enough time
440
- await new Promise((r) => setTimeout(r, 25000));
441
-
442
- const errorMsg = ctx.sent.find(
443
- (m: any) => m.type === "ride_shotgun_error",
444
- ) as any;
445
- expect(errorMsg).toBeDefined();
446
- expect(errorMsg.message).toContain("10 attempts");
447
-
448
- // Session should be completed
449
- const session = [...watchSessions.values()][0];
450
- expect(session?.status).toBe("completed");
451
- }, 30000); // Extended timeout for retry delays
452
- });
@@ -1,129 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { join } from "node:path";
3
-
4
- import type { Command } from "commander";
5
-
6
- import { getDaemonStatus, stopDaemon } from "../../daemon/lifecycle.js";
7
- import { log } from "../logger.js";
8
-
9
- export function registerDevCommand(program: Command): void {
10
- program
11
- .command("dev")
12
- .description("Run the assistant in dev mode")
13
- .option(
14
- "--watch",
15
- "Auto-restart on source file changes (disruptive during Claude Code sessions)",
16
- )
17
- .addHelpText(
18
- "after",
19
- `
20
- Starts the assistant in foreground dev mode for local development. If an
21
- existing assistant is running, it is stopped first (waits up to 5 seconds
22
- for an unresponsive assistant before force-killing it).
23
-
24
- Behavioral notes:
25
- - Sets VELLUM_DEBUG=1 for DEBUG-level logging
26
- - Sets VELLUM_LOG_STDERR=1 so logs stream to stderr (visible in terminal)
27
- - Sets BASE_DATA_DIR to the repository root
28
- - The assistant runs in the foreground; press Ctrl+C to stop
29
-
30
- The --watch flag passes bun --watch to the child process, which
31
- auto-restarts the assistant whenever source files change. This is useful
32
- during development but disruptive if a Claude Code session is active,
33
- since the restart kills the running assistant mid-conversation.
34
-
35
- Examples:
36
- $ assistant dev
37
- $ assistant dev --watch`,
38
- )
39
- .action(async (opts: { watch?: boolean }) => {
40
- let status = await getDaemonStatus();
41
- if (status.running) {
42
- log.info("Stopping existing assistant...");
43
- const stopResult = await stopDaemon();
44
- if (!stopResult.stopped && stopResult.reason === "stop_failed") {
45
- log.error(
46
- "Failed to stop existing assistant — process survived SIGKILL",
47
- );
48
- process.exit(1);
49
- }
50
- } else if (status.pid) {
51
- // PID file references a live process but the socket is unresponsive.
52
- // This can happen during the daemon startup window before the socket
53
- // is bound. Wait briefly for it to come up before replacing.
54
- log.info(
55
- "Assistant process alive but socket unresponsive — waiting for startup...",
56
- );
57
- const maxWait = 5000;
58
- const interval = 500;
59
- let waited = 0;
60
- let resolved = false;
61
- while (waited < maxWait) {
62
- await new Promise((r) => setTimeout(r, interval));
63
- waited += interval;
64
- status = await getDaemonStatus();
65
- if (status.running) {
66
- // Socket came up — stop the daemon normally.
67
- log.info("Assistant became responsive, stopping it...");
68
- const stopResult = await stopDaemon();
69
- if (!stopResult.stopped && stopResult.reason === "stop_failed") {
70
- log.error(
71
- "Failed to stop existing assistant — process survived SIGKILL",
72
- );
73
- process.exit(1);
74
- }
75
- resolved = true;
76
- break;
77
- }
78
- if (!status.pid) {
79
- // Process exited on its own — PID file already cleaned up.
80
- resolved = true;
81
- break;
82
- }
83
- }
84
- if (!resolved) {
85
- // Still alive but unresponsive after waiting — stop it via stopDaemon()
86
- // which handles SIGTERM → SIGKILL escalation and PID file cleanup.
87
- log.info("Assistant still unresponsive after wait — stopping it...");
88
- const stopResult = await stopDaemon();
89
- if (!stopResult.stopped && stopResult.reason === "stop_failed") {
90
- log.error(
91
- "Failed to stop existing assistant — process survived SIGKILL",
92
- );
93
- process.exit(1);
94
- }
95
- }
96
- }
97
-
98
- const mainPath = `${import.meta.dirname}/../../daemon/main.ts`;
99
-
100
- const useWatch = opts.watch === true;
101
- log.info(
102
- `Starting assistant in dev mode${
103
- useWatch ? " with file watching" : ""
104
- } (Ctrl+C to stop)`,
105
- );
106
-
107
- const repoRoot = join(import.meta.dirname, "..", "..", "..", "..");
108
- const args = useWatch ? ["--watch", "run", mainPath] : ["run", mainPath];
109
- const child = spawn("bun", args, {
110
- stdio: "inherit",
111
- env: {
112
- ...process.env,
113
- BASE_DATA_DIR: repoRoot,
114
- VELLUM_LOG_STDERR: "1",
115
- VELLUM_DEBUG: "1",
116
- },
117
- });
118
-
119
- const forward = (signal: NodeJS.Signals) => {
120
- child.kill(signal);
121
- };
122
- process.on("SIGINT", () => forward("SIGINT"));
123
- process.on("SIGTERM", () => forward("SIGTERM"));
124
-
125
- child.on("exit", (code) => {
126
- process.exit(code ?? 0);
127
- });
128
- });
129
- }