@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
@@ -51,7 +51,6 @@ mock.module("../util/logger.js", () => ({
51
51
  ...realLogger,
52
52
  getLogger: () => noopLogger,
53
53
  getCliLogger: () => noopLogger,
54
- isDebug: () => false,
55
54
  truncateForLog: (v: string) => v,
56
55
  initLogger: () => {},
57
56
  pruneOldLogFiles: () => 0,
@@ -3,7 +3,6 @@ import { describe, expect, mock, test } from "bun:test";
3
3
  mock.module("../util/logger.js", () => ({
4
4
  getLogger: () =>
5
5
  new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
6
- isDebug: () => false,
7
6
  }));
8
7
 
9
8
  // Only mock sleep so retries complete instantly; keep real retry logic.
@@ -15,7 +15,6 @@ import { describe, expect, mock, test } from "bun:test";
15
15
  mock.module("../util/logger.js", () => ({
16
16
  getLogger: () =>
17
17
  new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
18
- isDebug: () => false,
19
18
  }));
20
19
 
21
20
  import { FailoverProvider } from "../providers/failover.js";
@@ -25,6 +25,7 @@ mock.module("../util/logger.js", () => ({
25
25
  getLogger: () => makeLoggerStub(),
26
26
  }));
27
27
 
28
+ import { setIngressPublicBaseUrl } from "../config/env.js";
28
29
  import {
29
30
  getOAuthCallbackUrl,
30
31
  getPublicBaseUrl,
@@ -40,19 +41,12 @@ import {
40
41
  // ---------------------------------------------------------------------------
41
42
 
42
43
  describe("getPublicBaseUrl", () => {
43
- let savedIngressEnv: string | undefined;
44
-
45
44
  beforeEach(() => {
46
- savedIngressEnv = process.env.INGRESS_PUBLIC_BASE_URL;
47
- delete process.env.INGRESS_PUBLIC_BASE_URL;
45
+ setIngressPublicBaseUrl(undefined);
48
46
  });
49
47
 
50
48
  afterEach(() => {
51
- if (savedIngressEnv !== undefined) {
52
- process.env.INGRESS_PUBLIC_BASE_URL = savedIngressEnv;
53
- } else {
54
- delete process.env.INGRESS_PUBLIC_BASE_URL;
55
- }
49
+ setIngressPublicBaseUrl(undefined);
56
50
  });
57
51
 
58
52
  test("returns ingress.publicBaseUrl when set", () => {
@@ -62,16 +56,16 @@ describe("getPublicBaseUrl", () => {
62
56
  expect(result).toBe("https://ingress.example.com");
63
57
  });
64
58
 
65
- test("falls back to INGRESS_PUBLIC_BASE_URL env var when ingress.publicBaseUrl is empty", () => {
66
- process.env.INGRESS_PUBLIC_BASE_URL = "https://ingress-env.example.com/";
59
+ test("falls back to module-level ingress state when ingress.publicBaseUrl is empty", () => {
60
+ setIngressPublicBaseUrl("https://ingress-env.example.com/");
67
61
  const result = getPublicBaseUrl({
68
62
  ingress: { publicBaseUrl: "" },
69
63
  });
70
64
  expect(result).toBe("https://ingress-env.example.com");
71
65
  });
72
66
 
73
- test("falls back to INGRESS_PUBLIC_BASE_URL env var when config is empty", () => {
74
- process.env.INGRESS_PUBLIC_BASE_URL = "https://ingress-env.example.com";
67
+ test("falls back to module-level ingress state when config is empty", () => {
68
+ setIngressPublicBaseUrl("https://ingress-env.example.com");
75
69
  const result = getPublicBaseUrl({});
76
70
  expect(result).toBe("https://ingress-env.example.com");
77
71
  });
@@ -118,8 +112,8 @@ describe("getPublicBaseUrl", () => {
118
112
  expect(result).toBe("https://example.com");
119
113
  });
120
114
 
121
- test("falls back to env var when enabled is undefined and no publicBaseUrl", () => {
122
- process.env.INGRESS_PUBLIC_BASE_URL = "https://env-fallback.example.com";
115
+ test("falls back to module-level state when enabled is undefined and no publicBaseUrl", () => {
116
+ setIngressPublicBaseUrl("https://env-fallback.example.com");
123
117
  const result = getPublicBaseUrl({
124
118
  ingress: { enabled: undefined, publicBaseUrl: "" },
125
119
  });
@@ -140,22 +134,22 @@ describe("getPublicBaseUrl", () => {
140
134
  expect(result).toBe("https://example.com");
141
135
  });
142
136
 
143
- test("skips whitespace-only ingress.publicBaseUrl and falls through to env", () => {
144
- process.env.INGRESS_PUBLIC_BASE_URL = "https://ingress-env.example.com";
137
+ test("skips whitespace-only ingress.publicBaseUrl and falls through to module state", () => {
138
+ setIngressPublicBaseUrl("https://ingress-env.example.com");
145
139
  const result = getPublicBaseUrl({
146
140
  ingress: { publicBaseUrl: " " },
147
141
  });
148
142
  expect(result).toBe("https://ingress-env.example.com");
149
143
  });
150
144
 
151
- test("normalizes trailing slashes from INGRESS_PUBLIC_BASE_URL", () => {
152
- process.env.INGRESS_PUBLIC_BASE_URL = "https://ingress-env.example.com///";
145
+ test("normalizes trailing slashes from module-level ingress state", () => {
146
+ setIngressPublicBaseUrl("https://ingress-env.example.com///");
153
147
  const result = getPublicBaseUrl({});
154
148
  expect(result).toBe("https://ingress-env.example.com");
155
149
  });
156
150
 
157
- test("trims whitespace from INGRESS_PUBLIC_BASE_URL", () => {
158
- process.env.INGRESS_PUBLIC_BASE_URL = " https://ingress-env.example.com ";
151
+ test("trims whitespace from module-level ingress state", () => {
152
+ setIngressPublicBaseUrl(" https://ingress-env.example.com ");
159
153
  const result = getPublicBaseUrl({});
160
154
  expect(result).toBe("https://ingress-env.example.com");
161
155
  });
@@ -32,8 +32,9 @@ mock.module("../config/loader.js", () => ({
32
32
  contextWindow: {
33
33
  enabled: true,
34
34
  maxInputTokens: 180000,
35
- targetBudgetRatio: 0.30,
36
- compactThreshold: 0.8, summaryBudgetRatio: 0.05,
35
+ targetBudgetRatio: 0.3,
36
+ compactThreshold: 0.8,
37
+ summaryBudgetRatio: 0.05,
37
38
  },
38
39
  }),
39
40
  invalidateConfigCache: noop,
@@ -167,8 +168,6 @@ function createCtx(): {
167
168
 
168
169
  const ctx: HandlerContext = {
169
170
  sessions: new Map(),
170
- cuSessions: new Map(),
171
- cuObservationParseSequence: new Map(),
172
171
  sharedRequestTimestamps: [],
173
172
  debounceTimers: new DebouncerMap({ defaultDelayMs: 200 }),
174
173
  suppressConfigReload: false,
@@ -510,7 +510,7 @@ describe("computer-use registration split", () => {
510
510
  // Start each test from a completely empty registry so assertions are
511
511
  // non-vacuous — the split functions must actually register tools.
512
512
 
513
- test("registerComputerUseActionTools registers all 10 CU action tools and nothing else", async () => {
513
+ test("registerComputerUseActionTools registers all 11 CU action tools and nothing else", async () => {
514
514
  const { registerComputerUseActionTools } =
515
515
  await import("../tools/computer-use/registry.js");
516
516
 
@@ -520,7 +520,7 @@ describe("computer-use registration split", () => {
520
520
  registerComputerUseActionTools();
521
521
 
522
522
  const registered = getAllTools();
523
- expect(registered).toHaveLength(10);
523
+ expect(registered).toHaveLength(11);
524
524
  expect(registered.every((t) => t.name.startsWith("computer_use_"))).toBe(
525
525
  true,
526
526
  );
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * Tests:
5
5
  * - 401 unauthorized (missing bearer token)
6
- * - 400 when conversationKey is absent
6
+ * - 200 when conversationKey is omitted (unfiltered subscription)
7
7
  * - Happy path: stream receives a published AssistantEvent
8
+ * - Unfiltered: streams events from multiple conversations
8
9
  */
9
10
  import { mkdtempSync, realpathSync, rmSync } from "node:fs";
10
11
  import { tmpdir } from "node:os";
@@ -125,15 +126,13 @@ describe("SSE assistant-events endpoint", () => {
125
126
 
126
127
  // ── Validation ────────────────────────────────────────────────────────────
127
128
 
128
- test("400 when conversationKey is missing", async () => {
129
+ test("200 when conversationKey is omitted (unfiltered subscription)", async () => {
129
130
  await startServer();
130
131
 
131
132
  const res = await fetch(eventsUrl(), { headers: AUTH_HEADERS });
132
- expect(res.status).toBe(400);
133
- const body = (await res.json()) as {
134
- error: { message: string; code?: string };
135
- };
136
- expect(body.error.message).toContain("conversationKey");
133
+ expect(res.status).toBe(200);
134
+ expect(res.headers.get("content-type")).toContain("text/event-stream");
135
+ await res.body?.cancel();
137
136
 
138
137
  await stopServer();
139
138
  });
@@ -184,4 +183,53 @@ describe("SSE assistant-events endpoint", () => {
184
183
  expect(frame).toContain(`"sessionId":"${conversationId}"`);
185
184
  expect(frame).toContain('"type":"pong"');
186
185
  });
186
+
187
+ // ── Unfiltered subscription ──────────────────────────────────────────────
188
+
189
+ test("streams all events when conversationKey is omitted", async () => {
190
+ // Subscribe without a conversationKey — should receive events from any session.
191
+ const ac = new AbortController();
192
+ const req = new Request("http://localhost/v1/events", {
193
+ signal: ac.signal,
194
+ });
195
+
196
+ const { AssistantEventHub } =
197
+ await import("../runtime/assistant-event-hub.js");
198
+ const testHub = new AssistantEventHub();
199
+
200
+ const { handleSubscribeAssistantEvents } =
201
+ await import("../runtime/routes/events-routes.js");
202
+ const response = handleSubscribeAssistantEvents(req, new URL(req.url), {
203
+ hub: testHub,
204
+ skipActorVerification: true,
205
+ });
206
+
207
+ expect(response.status).toBe(200);
208
+
209
+ const reader = response.body!.getReader();
210
+
211
+ // Consume the initial heartbeat.
212
+ const heartbeat = await reader.read();
213
+ expect(heartbeat.done).toBe(false);
214
+ expect(new TextDecoder().decode(heartbeat.value)).toBe(": heartbeat\n\n");
215
+
216
+ // Publish events with two different sessionIds.
217
+ const eventA = buildAssistantEvent("self", { type: "pong" }, "session-aaa");
218
+ const eventB = buildAssistantEvent("self", { type: "pong" }, "session-bbb");
219
+ await testHub.publish(eventA);
220
+ await testHub.publish(eventB);
221
+
222
+ // Read both frames from the stream.
223
+ const frameA = await reader.read();
224
+ expect(frameA.done).toBe(false);
225
+ const textA = new TextDecoder().decode(frameA.value);
226
+ expect(textA).toContain("session-aaa");
227
+
228
+ const frameB = await reader.read();
229
+ expect(frameB.done).toBe(false);
230
+ const textB = new TextDecoder().decode(frameB.value);
231
+ expect(textB).toContain("session-bbb");
232
+
233
+ ac.abort();
234
+ });
187
235
  });
@@ -22,7 +22,6 @@ mock.module("../util/logger.js", () => ({
22
22
  new Proxy({} as Record<string, unknown>, {
23
23
  get: () => () => {},
24
24
  }),
25
- isDebug: () => false,
26
25
  truncateForLog: (value: string) => value,
27
26
  }));
28
27
 
@@ -21,7 +21,6 @@ mock.module("../util/logger.js", () => ({
21
21
  new Proxy({} as Record<string, unknown>, {
22
22
  get: () => () => {},
23
23
  }),
24
- isDebug: () => false,
25
24
  truncateForLog: (value: string) => value,
26
25
  }));
27
26
 
@@ -21,7 +21,6 @@ mock.module("../util/logger.js", () => ({
21
21
  new Proxy({} as Record<string, unknown>, {
22
22
  get: () => () => {},
23
23
  }),
24
- isDebug: () => false,
25
24
  truncateForLog: (value: string) => value,
26
25
  }));
27
26
 
@@ -46,7 +46,6 @@ mock.module("../util/logger.js", () => ({
46
46
  new Proxy({} as Record<string, unknown>, {
47
47
  get: () => () => {},
48
48
  }),
49
- isDebug: () => false,
50
49
  truncateForLog: (value: string) => value,
51
50
  }));
52
51
 
@@ -22,7 +22,6 @@ mock.module("../util/logger.js", () => ({
22
22
  new Proxy({} as Record<string, unknown>, {
23
23
  get: () => () => {},
24
24
  }),
25
- isDebug: () => false,
26
25
  }));
27
26
 
28
27
  const { checkIngressForSecrets } =
@@ -117,6 +117,7 @@ function makeCompletingSession(): Session {
117
117
  updateClient: () => {},
118
118
  setHostBashProxy: () => {},
119
119
  setHostFileProxy: () => {},
120
+ setHostCuProxy: () => {},
120
121
  hasAnyPendingConfirmation: () => false,
121
122
  hasPendingConfirmation: () => false,
122
123
  denyAllPendingConfirmations: () => {},
@@ -173,6 +174,7 @@ function makeHangingSession(): Session {
173
174
  updateClient: () => {},
174
175
  setHostBashProxy: () => {},
175
176
  setHostFileProxy: () => {},
177
+ setHostCuProxy: () => {},
176
178
  hasAnyPendingConfirmation: () => false,
177
179
  hasPendingConfirmation: () => false,
178
180
  denyAllPendingConfirmations: () => {},
@@ -257,6 +259,7 @@ function makePendingApprovalSession(
257
259
  updateClient: () => {},
258
260
  setHostBashProxy: () => {},
259
261
  setHostFileProxy: () => {},
262
+ setHostCuProxy: () => {},
260
263
  hasAnyPendingConfirmation: () => pending.size > 0,
261
264
  hasPendingConfirmation: (candidateRequestId: string) =>
262
265
  pending.has(candidateRequestId),
@@ -364,11 +367,17 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
364
367
  interface: "macos",
365
368
  }),
366
369
  });
367
- const body = (await res.json()) as { accepted: boolean; messageId: string };
370
+ const body = (await res.json()) as {
371
+ accepted: boolean;
372
+ messageId: string;
373
+ conversationId: string;
374
+ };
368
375
 
369
376
  expect(res.status).toBe(202);
370
377
  expect(body.accepted).toBe(true);
371
378
  expect(body.messageId).toBeDefined();
379
+ expect(typeof body.conversationId).toBe("string");
380
+ expect(body.conversationId.length).toBeGreaterThan(0);
372
381
 
373
382
  await stopServer();
374
383
  });
@@ -485,8 +494,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
485
494
  createCanonicalGuardianRequest({
486
495
  id: requestId,
487
496
  kind: "tool_approval",
488
- sourceType: "desktop",
489
- sourceChannel: "vellum",
497
+ sourceType: "voice",
498
+ sourceChannel: "slack",
490
499
  conversationId,
491
500
  toolName: "call_start",
492
501
  status: "pending",
@@ -511,8 +520,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
511
520
  body: JSON.stringify({
512
521
  conversationKey,
513
522
  content: "sure let's do that",
514
- sourceChannel: "vellum",
515
- interface: "macos",
523
+ sourceChannel: "slack",
524
+ interface: "slack",
516
525
  }),
517
526
  });
518
527
  const body = (await res.json()) as {
@@ -808,11 +817,17 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
808
817
  interface: "macos",
809
818
  }),
810
819
  });
811
- const body2 = (await res2.json()) as { accepted: boolean; queued: boolean };
820
+ const body2 = (await res2.json()) as {
821
+ accepted: boolean;
822
+ queued: boolean;
823
+ conversationId: string;
824
+ };
812
825
 
813
826
  expect(res2.status).toBe(202);
814
827
  expect(body2.accepted).toBe(true);
815
828
  expect(body2.queued).toBe(true);
829
+ expect(typeof body2.conversationId).toBe("string");
830
+ expect(body2.conversationId.length).toBeGreaterThan(0);
816
831
 
817
832
  await stopServer();
818
833
  });
@@ -22,7 +22,6 @@ mock.module("../util/logger.js", () => ({
22
22
  new Proxy({} as Record<string, unknown>, {
23
23
  get: () => () => {},
24
24
  }),
25
- isDebug: () => false,
26
25
  truncateForLog: (value: string) => value,
27
26
  }));
28
27
 
@@ -127,7 +127,6 @@ mock.module("../util/logger.js", () => ({
127
127
  new Proxy({} as Record<string, unknown>, {
128
128
  get: () => () => {},
129
129
  }),
130
- isDebug: () => false,
131
130
  truncateForLog: (value: string, maxLen = 500) =>
132
131
  value.length > maxLen ? value.slice(0, maxLen) + "..." : value,
133
132
  initLogger: () => {},
@@ -342,7 +341,7 @@ describe("Session initialization benchmark", () => {
342
341
  }
343
342
 
344
343
  timings.sort((a, b) => a - b);
345
- expect(median(timings)).toBeLessThan(10);
344
+ expect(median(timings)).toBeLessThan(15);
346
345
  });
347
346
 
348
347
  test("buildSystemPrompt assembles prompt under 50ms (median of 5)", () => {
@@ -384,9 +383,9 @@ describe("Session initialization benchmark", () => {
384
383
  await initializeTools();
385
384
  const definitions = getAllToolDefinitions();
386
385
 
387
- // Sanity: we expect a meaningful number of core tools (at least 20)
386
+ // Sanity: we expect a meaningful number of core tools (at least 18)
388
387
  // but not an unreasonable explosion (under 200)
389
- expect(definitions.length).toBeGreaterThanOrEqual(20);
388
+ expect(definitions.length).toBeGreaterThanOrEqual(18);
390
389
  expect(definitions.length).toBeLessThan(200);
391
390
  });
392
391
  });
@@ -548,6 +547,6 @@ describe("End-to-end session creation benchmark", () => {
548
547
  }
549
548
 
550
549
  timings.sort((a, b) => a - b);
551
- expect(median(timings)).toBeLessThan(10);
550
+ expect(median(timings)).toBeLessThan(15);
552
551
  });
553
552
  });
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import type { SkillSummary } from "../config/skills.js";
4
4
  import {
5
+ collectAllMissing,
5
6
  getImmediateChildren,
6
7
  indexCatalogById,
7
8
  traverseIncludes,
@@ -298,3 +299,68 @@ describe("validateIncludes — cycle detection", () => {
298
299
  }
299
300
  });
300
301
  });
302
+
303
+ describe("collectAllMissing", () => {
304
+ test("returns empty set when skill has no includes", () => {
305
+ const catalog = [makeSkill("root")];
306
+ const index = indexCatalogById(catalog);
307
+ expect(collectAllMissing("root", index)).toEqual(new Set([]));
308
+ });
309
+
310
+ test("returns empty set when all includes are present", () => {
311
+ const catalog = [makeSkill("A", ["B"]), makeSkill("B")];
312
+ const index = indexCatalogById(catalog);
313
+ expect(collectAllMissing("A", index)).toEqual(new Set([]));
314
+ });
315
+
316
+ test("returns immediate missing children", () => {
317
+ const catalog = [makeSkill("A", ["B", "C"]), makeSkill("C")];
318
+ const index = indexCatalogById(catalog);
319
+ expect(collectAllMissing("A", index)).toEqual(new Set(["B"]));
320
+ });
321
+
322
+ test("returns transitive missing children", () => {
323
+ const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
324
+ const index = indexCatalogById(catalog);
325
+ expect(collectAllMissing("A", index)).toEqual(new Set(["C"]));
326
+ });
327
+
328
+ test("returns multiple missing at different levels", () => {
329
+ // A→B→C, B present but C missing
330
+ const catalog1 = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
331
+ const index1 = indexCatalogById(catalog1);
332
+ expect(collectAllMissing("A", index1)).toEqual(new Set(["C"]));
333
+
334
+ // A includes B and C, both missing
335
+ const catalog2 = [makeSkill("A", ["B", "C"])];
336
+ const index2 = indexCatalogById(catalog2);
337
+ expect(collectAllMissing("A", index2)).toEqual(new Set(["B", "C"]));
338
+ });
339
+
340
+ test("handles diamond with missing leaf", () => {
341
+ const catalog = [
342
+ makeSkill("A", ["B", "C"]),
343
+ makeSkill("B", ["D"]),
344
+ makeSkill("C", ["D"]),
345
+ ];
346
+ const index = indexCatalogById(catalog);
347
+ const result = collectAllMissing("A", index);
348
+ expect(result).toEqual(new Set(["D"]));
349
+ // Verify no duplicates (Set handles this, but confirm size)
350
+ expect(result.size).toBe(1);
351
+ });
352
+
353
+ test("does not loop infinitely on cycles", () => {
354
+ const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["A"])];
355
+ const index = indexCatalogById(catalog);
356
+ expect(collectAllMissing("A", index)).toEqual(new Set([]));
357
+ });
358
+
359
+ test("handles cycle with missing node", () => {
360
+ const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
361
+ // C is missing, and if C referenced B it would be a cycle — but C isn't in catalog
362
+ // So A→B→C, C missing
363
+ const index = indexCatalogById(catalog);
364
+ expect(collectAllMissing("A", index)).toEqual(new Set(["C"]));
365
+ });
366
+ });
@@ -61,7 +61,6 @@ mock.module("../util/logger.js", () => ({
61
61
  ...realLogger,
62
62
  getLogger: () => noopLogger,
63
63
  getCliLogger: () => noopLogger,
64
- isDebug: () => false,
65
64
  truncateForLog: (value: string) => value,
66
65
  initLogger: () => {},
67
66
  pruneOldLogFiles: () => 0,