@vellumai/assistant 0.11.2-dev.202608051734.355dff7 → 0.11.2-dev.202608051933.deffc0f

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 (60) hide show
  1. package/ARCHITECTURE.md +4 -2
  2. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  3. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  4. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  5. package/openapi.yaml +22 -0
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -10
  9. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -0
  10. package/src/__tests__/conversation-tool-setup-attribution.test.ts +61 -0
  11. package/src/__tests__/cu-unified-flow.test.ts +2 -6
  12. package/src/__tests__/file-ops-service.test.ts +75 -4
  13. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  14. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  15. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  16. package/src/__tests__/host-file-proxy.test.ts +3 -7
  17. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  18. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  19. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  20. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  21. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  22. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -0
  23. package/src/__tests__/subagent-tools.test.ts +48 -92
  24. package/src/__tests__/tool-result-spool.test.ts +74 -18
  25. package/src/agent/loop.ts +6 -3
  26. package/src/calls/media-stream-server.ts +9 -1
  27. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  28. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  29. package/src/config/bundled-skills/subagent/SKILL.md +3 -3
  30. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  31. package/src/config/feature-flag-registry.json +0 -16
  32. package/src/config/llm-resolver.ts +41 -12
  33. package/src/config/schemas/stt.ts +10 -4
  34. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  35. package/src/context/tool-result-spool.ts +32 -18
  36. package/src/daemon/conversation-tool-setup.ts +1 -1
  37. package/src/persistence/conversation-attention-store.ts +30 -10
  38. package/src/persistence/conversation-queries.ts +74 -1
  39. package/src/platform/managed-speech.test.ts +20 -0
  40. package/src/platform/managed-speech.ts +9 -0
  41. package/src/providers/speech-to-text/__tests__/resolve.test.ts +164 -6
  42. package/src/providers/speech-to-text/deepgram.ts +1 -0
  43. package/src/providers/speech-to-text/resolve.ts +80 -10
  44. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  45. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  46. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  47. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  48. package/src/runtime/auth/same-actor.ts +38 -7
  49. package/src/runtime/auth/scopes.ts +12 -0
  50. package/src/runtime/auth/types.ts +2 -0
  51. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +279 -1
  52. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  53. package/src/runtime/routes/conversation-list-routes.ts +25 -0
  54. package/src/runtime/routes/conversation-query-routes.ts +0 -50
  55. package/src/stt/daemon-batch-transcriber.ts +8 -6
  56. package/src/tools/filesystem/read.ts +2 -2
  57. package/src/tools/host-filesystem/read.ts +14 -6
  58. package/src/tools/shared/filesystem/file-ops-service.ts +36 -3
  59. package/src/tools/subagent/spawn.ts +26 -78
  60. package/src/tools/types.ts +5 -3
@@ -709,7 +709,7 @@ describe("HostTransferProxy — targetClientId", () => {
709
709
  );
710
710
 
711
711
  expect(result.isError).toBe(true);
712
- expect(result.content).toContain("does not match");
712
+ expect(result.content).toContain("signed in as a different user");
713
713
  expect(sentMessages).toHaveLength(0);
714
714
  });
715
715
 
@@ -738,7 +738,7 @@ describe("HostTransferProxy — targetClientId", () => {
738
738
  );
739
739
 
740
740
  expect(result.isError).toBe(true);
741
- expect(result.content).toContain("does not match");
741
+ expect(result.content).toContain("no authenticated actor");
742
742
  expect(sentMessages).toHaveLength(0);
743
743
  });
744
744
 
@@ -767,7 +767,9 @@ describe("HostTransferProxy — targetClientId", () => {
767
767
  );
768
768
 
769
769
  expect(result.isError).toBe(true);
770
- expect(result.content).toContain("does not match");
770
+ expect(result.content).toContain(
771
+ "registered without an authenticated user",
772
+ );
771
773
  expect(sentMessages).toHaveLength(0);
772
774
  });
773
775
 
@@ -863,7 +865,7 @@ describe("HostTransferProxy — targetClientId", () => {
863
865
  );
864
866
 
865
867
  expect(result.isError).toBe(true);
866
- expect(result.content).toContain("does not match");
868
+ expect(result.content).toContain("signed in as a different user");
867
869
  expect(sentMessages).toHaveLength(0);
868
870
  });
869
871
 
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import { CODE_DEFAULT_PROFILE_ENTRIES } from "../config/default-profile-catalog.js";
4
4
  import {
5
+ composeCallSiteTweak,
5
6
  type ResolutionFallbackReason,
6
7
  resolveCallSiteConfig,
7
8
  resolveDefaultProfileKey,
@@ -178,6 +179,146 @@ describe("fallback and completeness", () => {
178
179
  });
179
180
  });
180
181
 
182
+ describe("composeCallSiteTweak", () => {
183
+ // `CallSiteDefaultConfig` supports a shipped `model` (and the provider it
184
+ // implies). No entry sets one today, so these pin the invariant the merge
185
+ // depends on rather than waiting for the first entry that would break it.
186
+ test("a workspace entry owns selection: a shipped model/provider is dropped", () => {
187
+ const tweak = composeCallSiteTweak(
188
+ { model: "shipped-model", provider: "openai", effort: "low" },
189
+ { profile: "mine" },
190
+ );
191
+ expect("model" in tweak).toBe(false);
192
+ expect("provider" in tweak).toBe(false);
193
+ // The shipped tuning still layers in.
194
+ expect(tweak.effort).toBe("low");
195
+ });
196
+
197
+ test("with no workspace entry the shipped fragment applies whole", () => {
198
+ const tweak = composeCallSiteTweak(
199
+ { model: "shipped-model", provider: "openai", effort: "low" },
200
+ undefined,
201
+ );
202
+ expect(tweak.model).toBe("shipped-model");
203
+ expect(tweak.provider).toBe("openai");
204
+ });
205
+
206
+ test("a workspace provider/model still applies", () => {
207
+ const tweak = composeCallSiteTweak(
208
+ { model: "shipped-model", effort: "low" },
209
+ { provider: "anthropic", model: "chosen-model" },
210
+ );
211
+ expect(tweak.model).toBe("chosen-model");
212
+ expect(tweak.provider).toBe("anthropic");
213
+ expect(tweak.effort).toBe("low");
214
+ });
215
+
216
+ test("profile and logitBias never reach the fragment", () => {
217
+ const tweak = composeCallSiteTweak(
218
+ { effort: "low" },
219
+ { profile: "mine", logitBias: "suppress-cjk", maxTokens: 10 },
220
+ );
221
+ expect("profile" in tweak).toBe(false);
222
+ expect("logitBias" in tweak).toBe(false);
223
+ expect(tweak.maxTokens).toBe(10);
224
+ });
225
+ });
226
+
227
+ describe("shipped call-site tuning", () => {
228
+ // A workspace entry that only repoints the profile must not cost the call
229
+ // site the tuning it ships with. `recall` carries a full set: a token
230
+ // budget, effort, sampling, thinking, and cache posture.
231
+ test("a profile-only entry keeps the shipped tuning and takes the new model", () => {
232
+ const shipped = resolveCallSiteConfig(
233
+ "recall",
234
+ LLMSchema.parse({ profiles: { mine: completeCustom }, ...anthropicDp }),
235
+ );
236
+ const repointed = resolveCallSiteConfig(
237
+ "recall",
238
+ LLMSchema.parse({
239
+ profiles: { mine: completeCustom },
240
+ callSites: { recall: { profile: "mine" } },
241
+ ...anthropicDp,
242
+ }),
243
+ );
244
+
245
+ // The pin is what selects the model.
246
+ expect(repointed.model).toBe("gpt-5.5");
247
+ expect(repointed.model).not.toBe(shipped.model);
248
+
249
+ // Everything the call site ships with survives the repoint.
250
+ expect(repointed.maxTokens).toBe(4096);
251
+ expect(repointed.effort).toBe("low");
252
+ expect(repointed.temperature).toBe(0);
253
+ expect(repointed.disableCache).toBe(true);
254
+ expect(repointed.thinking.enabled).toBe(false);
255
+ expect(repointed.thinking.streamThinking).toBe(false);
256
+ });
257
+
258
+ test("a profile-only entry keeps a shipped nested budget", () => {
259
+ const resolved = resolveCallSiteConfig(
260
+ "memoryRouter",
261
+ LLMSchema.parse({
262
+ profiles: { mine: completeCustom },
263
+ callSites: { memoryRouter: { profile: "mine" } },
264
+ ...anthropicDp,
265
+ }),
266
+ );
267
+ expect(resolved.contextWindow.maxInputTokens).toBe(1000000);
268
+ });
269
+
270
+ test("an explicit workspace field beats the shipped one, field by field", () => {
271
+ const resolved = resolveCallSiteConfig(
272
+ "recall",
273
+ LLMSchema.parse({
274
+ profiles: { mine: completeCustom },
275
+ callSites: {
276
+ recall: { profile: "mine", maxTokens: 512, effort: "high" },
277
+ },
278
+ ...anthropicDp,
279
+ }),
280
+ );
281
+ // The two fields the entry names come from the entry.
282
+ expect(resolved.maxTokens).toBe(512);
283
+ expect(resolved.effort).toBe("high");
284
+ // The ones it does not name still come from the shipped tuning.
285
+ expect(resolved.temperature).toBe(0);
286
+ expect(resolved.disableCache).toBe(true);
287
+ expect(resolved.thinking.enabled).toBe(false);
288
+ });
289
+
290
+ test("a provider/model entry keeps the shipped tuning too", () => {
291
+ const resolved = resolveCallSiteConfig(
292
+ "recall",
293
+ LLMSchema.parse({
294
+ profiles: { mine: completeCustom },
295
+ callSites: {
296
+ recall: { provider: "openai", model: "gpt-5.5" },
297
+ },
298
+ ...anthropicDp,
299
+ }),
300
+ );
301
+ expect(resolved.model).toBe("gpt-5.5");
302
+ expect(resolved.maxTokens).toBe(4096);
303
+ expect(resolved.effort).toBe("low");
304
+ expect(resolved.disableCache).toBe(true);
305
+ });
306
+
307
+ test("a call site with no shipped tuning is unaffected", () => {
308
+ const resolved = resolveCallSiteConfig(
309
+ "conversationSummarization",
310
+ LLMSchema.parse({
311
+ profiles: { mine: completeCustom },
312
+ callSites: { conversationSummarization: { profile: "mine" } },
313
+ ...anthropicDp,
314
+ }),
315
+ );
316
+ expect(resolved.model).toBe("gpt-5.5");
317
+ expect(resolved.maxTokens).toBe(completeCustom.maxTokens);
318
+ expect(resolved.effort).toBe(completeCustom.effort);
319
+ });
320
+ });
321
+
181
322
  describe("composition", () => {
182
323
  test("base + winner + site-tweak composition (nested tweaks combine leaf-wise)", () => {
183
324
  const llm = LLMSchema.parse({
@@ -406,6 +406,8 @@ mock.module("../calls/access-request-wait.js", () => ({
406
406
  // Now import the module under test.
407
407
  // ---------------------------------------------------------------------------
408
408
 
409
+ import { GATEWAY_TUNNEL_LOST_WS_CLOSE_CODE } from "@vellumai/service-contracts/ingress";
410
+
409
411
  import { revokeScopedApprovalGrantsForContext } from "../approvals/scoped-approval-grants.js";
410
412
  import { CallController } from "../calls/call-controller.js";
411
413
  import { postPointerMessageSafe } from "../calls/call-pointer-messages.js";
@@ -746,6 +748,40 @@ describe("MediaStreamCallSession", () => {
746
748
  );
747
749
  });
748
750
 
751
+ test("tunnel-lost close code decodes to a readable failure detail", () => {
752
+ const mock = createMockWs();
753
+ mockSessions.set("call-1", {
754
+ id: "call-1",
755
+ conversationId: "conv-1",
756
+ status: "in_progress",
757
+ startedAt: Date.now() - 60000,
758
+ toNumber: "+16175550142",
759
+ initiatedFromConversationId: "conv-origin",
760
+ });
761
+
762
+ const session = new MediaStreamCallSession(mock.ws, "call-1");
763
+ // The gateway's close reason is dropped in relay, so only the code
764
+ // arrives; it must decode to something a human can act on.
765
+ session.handleTransportClosed(GATEWAY_TUNNEL_LOST_WS_CLOSE_CODE);
766
+
767
+ expect(updateCallSession).toHaveBeenCalledWith(
768
+ "call-1",
769
+ expect.objectContaining({
770
+ status: "failed",
771
+ lastError:
772
+ "Media stream WebSocket closed unexpectedly: public ingress tunnel disconnected",
773
+ }),
774
+ );
775
+ expect(postPointerMessageSafe).toHaveBeenCalledWith(
776
+ "conv-origin",
777
+ "failed",
778
+ "+16175550142",
779
+ expect.objectContaining({
780
+ reason: "public ingress tunnel disconnected",
781
+ }),
782
+ );
783
+ });
784
+
749
785
  test("close on already-terminal session is a no-op", () => {
750
786
  const mock = createMockWs();
751
787
  mockSessions.set("call-1", {
@@ -129,3 +129,41 @@ describe("web STT language catalog stays in sync with the daemon roster", () =>
129
129
  expect(nonExtended).toEqual(expected);
130
130
  });
131
131
  });
132
+
133
+ /**
134
+ * The settings skill accepts plain language names ("tamil", "hindi") and its
135
+ * tool description advertises that, so a roster code with no English-name
136
+ * alias is reachable only by someone who knows the raw BCP-47 code. Adding a
137
+ * language to the roster without its alias is the easy half-step, so pin the
138
+ * two together the same way the catalog copies are pinned.
139
+ */
140
+ const SETTINGS_TOOL_SOURCE = readFileSync(
141
+ join(
142
+ REPO_ROOT,
143
+ "assistant/src/config/bundled-skills/settings/tools/voice-config-update.ts",
144
+ ),
145
+ "utf-8",
146
+ );
147
+
148
+ describe("settings-skill language aliases", () => {
149
+ test("every roster language is reachable by an English name", () => {
150
+ const aliasBlockStart = SETTINGS_TOOL_SOURCE.indexOf(
151
+ "const STT_LANGUAGE_ALIASES",
152
+ );
153
+ expect(aliasBlockStart).toBeGreaterThan(-1);
154
+ const aliasBlockEnd = SETTINGS_TOOL_SOURCE.indexOf("};", aliasBlockStart);
155
+ const aliasBlock = SETTINGS_TOOL_SOURCE.slice(
156
+ aliasBlockStart,
157
+ aliasBlockEnd,
158
+ );
159
+ // Every value in the alias map, i.e. the codes a plain name can reach.
160
+ const aliasedCodes = new Set(
161
+ [...aliasBlock.matchAll(/:\s*"([a-z-]+)"/g)].map((match) => match[1]),
162
+ );
163
+
164
+ const unreachable = DEEPGRAM_NOVA3_MONOLINGUAL_CODES.filter(
165
+ (code) => !aliasedCodes.has(code),
166
+ );
167
+ expect(unreachable).toEqual([]);
168
+ });
169
+ });
@@ -116,6 +116,7 @@ function makeSetupCtx(overrides: Partial<Conversation> = {}): Conversation {
116
116
  conversationId: "conv-test",
117
117
  currentRequestId: "req-1",
118
118
  workingDir: "/tmp/test",
119
+ getTurnActorPrincipalId: () => undefined,
119
120
  abortController: null,
120
121
  sendToClient: mock(() => {}),
121
122
  pendingSurfaceActions: new Map(),