@vellumai/assistant 0.12.3 → 0.12.4-staging.2

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 (103) hide show
  1. package/Dockerfile +7 -0
  2. package/README.md +12 -12
  3. package/docker/mousepad/90-vellum.gschema.override +4 -0
  4. package/openapi.yaml +9 -3
  5. package/package.json +1 -1
  6. package/src/__tests__/approval-routes-http.test.ts +4 -2
  7. package/src/__tests__/call-routes-http.test.ts +4 -2
  8. package/src/__tests__/config-loader-backfill.test.ts +1 -0
  9. package/src/__tests__/conversation-resurface-done.test.ts +289 -0
  10. package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
  11. package/src/__tests__/gemini-provider.test.ts +31 -0
  12. package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
  13. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  14. package/src/__tests__/llm-resolver-override-or-default.test.ts +43 -0
  15. package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
  16. package/src/__tests__/provider-error-scenarios.test.ts +46 -0
  17. package/src/__tests__/provider-platform-proxy-integration.test.ts +45 -15
  18. package/src/__tests__/retry-fallback-escalation.test.ts +22 -0
  19. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
  20. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
  21. package/src/__tests__/send-endpoint-busy.test.ts +4 -2
  22. package/src/__tests__/steer-on-enqueue-question.test.ts +265 -34
  23. package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
  24. package/src/api/requests/dictation.ts +2 -2
  25. package/src/calls/__tests__/typesafe-noul.test.ts +66 -0
  26. package/src/calls/typesafe-noul.ts +8 -2
  27. package/src/calls/voice-escalation-judge.ts +5 -4
  28. package/src/cli/commands/channel-verification-sessions.help.ts +12 -2
  29. package/src/cli/lib/bundled-marketplace.json +4 -4
  30. package/src/config/__tests__/default-profile-catalog.test.ts +33 -1
  31. package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
  32. package/src/config/call-site-defaults.ts +11 -11
  33. package/src/config/default-profile-catalog.ts +88 -18
  34. package/src/config/default-profile-names.ts +35 -0
  35. package/src/config/feature-flag-registry.json +8 -8
  36. package/src/config/inference-profile-validation.ts +7 -6
  37. package/src/config/llm-resolver.ts +16 -0
  38. package/src/config/profile-text-generation.test.ts +9 -0
  39. package/src/config/schemas/call-site-catalog.ts +1 -1
  40. package/src/config/schemas/llm.ts +54 -9
  41. package/src/config/sidebar-done-gate.ts +27 -0
  42. package/src/daemon/conversation-slash.ts +2 -2
  43. package/src/daemon/conversation-turn-finalize.ts +12 -0
  44. package/src/daemon/dictation-text-processing.ts +4 -4
  45. package/src/daemon/handlers/__tests__/conversation-history-search.test.ts +61 -0
  46. package/src/daemon/handlers/config-channels.ts +3 -1
  47. package/src/daemon/handlers/conversation-history.ts +11 -1
  48. package/src/daemon/handlers/conversations.ts +118 -10
  49. package/src/desktop/desktop-browser-endpoint.ts +1 -0
  50. package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +49 -1
  51. package/src/ipc/routes/conversation-sync-ipc-routes.ts +38 -2
  52. package/src/permissions/question-prompter.ts +14 -4
  53. package/src/persistence/conversation-crud.ts +114 -3
  54. package/src/persistence/conversation-queries.ts +48 -3
  55. package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +157 -32
  56. package/src/persistence/embeddings/embedding-backend-shutdown.test.ts +47 -0
  57. package/src/persistence/embeddings/embedding-backend.ts +23 -41
  58. package/src/persistence/embeddings/embedding-gemini.test.ts +17 -0
  59. package/src/persistence/embeddings/embedding-gemini.ts +3 -1
  60. package/src/persistence/embeddings/embedding-local.ts +72 -17
  61. package/src/persistence/embeddings/embedding-types.ts +0 -19
  62. package/src/plugin-api/model-profiles.ts +5 -3
  63. package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +2 -0
  64. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +5 -0
  65. package/src/providers/gemini/client.ts +17 -1
  66. package/src/providers/malformed-tool-call.ts +55 -0
  67. package/src/providers/model-catalog.ts +23 -4
  68. package/src/providers/openai/__tests__/chat-completions-malformed-tool-call.test.ts +105 -0
  69. package/src/providers/openai/chat-completions-provider.ts +30 -0
  70. package/src/providers/platform-proxy/constants.ts +5 -0
  71. package/src/providers/retry.ts +68 -36
  72. package/src/providers/vellum-model-routing.test.ts +1 -0
  73. package/src/runtime/__tests__/agent-wake-archived-gate.test.ts +90 -0
  74. package/src/runtime/__tests__/question-resolution.test.ts +32 -0
  75. package/src/runtime/agent-wake.ts +9 -2
  76. package/src/runtime/auth/__tests__/middleware.test.ts +138 -0
  77. package/src/runtime/auth/middleware.ts +15 -11
  78. package/src/runtime/middleware/auth.ts +7 -1
  79. package/src/runtime/pending-interactions.ts +8 -8
  80. package/src/runtime/question-resolution.ts +54 -8
  81. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +146 -0
  82. package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
  83. package/src/runtime/routes/__tests__/global-search-routes.test.ts +74 -1
  84. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
  85. package/src/runtime/routes/conversation-list-routes.ts +16 -14
  86. package/src/runtime/routes/conversation-query-routes.ts +4 -0
  87. package/src/runtime/routes/conversation-routes.ts +4 -2
  88. package/src/runtime/routes/diagnostics-routes.ts +66 -229
  89. package/src/runtime/routes/global-search-routes.ts +22 -7
  90. package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
  91. package/src/runtime/sync/documents-sidecar-publish.test.ts +50 -4
  92. package/src/runtime/sync/resource-sync-events.ts +11 -0
  93. package/src/runtime/sync/worker-daemon-notify.test.ts +30 -0
  94. package/src/runtime/sync/worker-daemon-notify.ts +44 -0
  95. package/src/runtime/verification-outbound-actions.ts +2 -2
  96. package/src/tools/ask-question/ask-question-tool.test.ts +38 -0
  97. package/src/tools/ask-question/ask-question-tool.ts +12 -1
  98. package/src/tools/workflows/manage-workflows.ts +4 -2
  99. package/src/util/worker-pipe.test.ts +100 -0
  100. package/src/util/worker-pipe.ts +44 -0
  101. package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
  102. package/src/workspace/migrations/registry.ts +2 -0
  103. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
@@ -0,0 +1,197 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { Database } from "bun:sqlite";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
14
+ import { assertNotLiveDb } from "./assert-not-live-db.js";
15
+
16
+ const migration = WORKSPACE_MIGRATIONS.find(
17
+ (entry) => entry.id === "159-rename-colliding-jev-profile-name",
18
+ )!;
19
+
20
+ let workspaceDir: string;
21
+
22
+ function writeConfig(data: Record<string, unknown>): void {
23
+ writeFileSync(
24
+ join(workspaceDir, "config.json"),
25
+ JSON.stringify(data, null, 2) + "\n",
26
+ );
27
+ }
28
+
29
+ function readConfig(): Record<string, any> {
30
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
31
+ }
32
+
33
+ function seedProfilePinTables(rows: {
34
+ conversations: string[];
35
+ cronJobs: string[];
36
+ }): void {
37
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
38
+ const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
39
+ db.run(
40
+ `CREATE TABLE conversations (id TEXT PRIMARY KEY, inference_profile TEXT)`,
41
+ );
42
+ db.run(
43
+ `CREATE TABLE cron_jobs (id TEXT PRIMARY KEY, inference_profile TEXT)`,
44
+ );
45
+ rows.conversations.forEach((profile, index) => {
46
+ db.query(
47
+ `INSERT INTO conversations (id, inference_profile) VALUES (?, ?)`,
48
+ ).run(`conv-${index}`, profile);
49
+ });
50
+ rows.cronJobs.forEach((profile, index) => {
51
+ db.query(`INSERT INTO cron_jobs (id, inference_profile) VALUES (?, ?)`).run(
52
+ `job-${index}`,
53
+ profile,
54
+ );
55
+ });
56
+ db.close();
57
+ }
58
+
59
+ function readPins(table: string): string[] {
60
+ const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
61
+ try {
62
+ return (
63
+ db
64
+ .query(`SELECT inference_profile AS p FROM ${table} ORDER BY id`)
65
+ .all() as Array<{ p: string }>
66
+ ).map((row) => row.p);
67
+ } finally {
68
+ db.close();
69
+ }
70
+ }
71
+
72
+ beforeEach(() => {
73
+ workspaceDir = join(
74
+ tmpdir(),
75
+ `vellum-migration-159-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
76
+ );
77
+ mkdirSync(workspaceDir, { recursive: true });
78
+ });
79
+
80
+ afterEach(() => {
81
+ if (existsSync(workspaceDir)) {
82
+ assertNotLiveDb(workspaceDir);
83
+ rmSync(workspaceDir, { recursive: true, force: true });
84
+ }
85
+ });
86
+
87
+ describe("159-rename-colliding-jev-profile-name migration", () => {
88
+ test("is registered under its id with checkpoint retry", () => {
89
+ expect(migration).toBeDefined();
90
+ expect(migration.retryFailedCheckpoint).toBe(true);
91
+ });
92
+
93
+ test("renames a colliding user profile and rewrites every reference", async () => {
94
+ writeConfig({
95
+ llm: {
96
+ defaultProvider: { provider: "vellum" },
97
+ profiles: {
98
+ "jev-managed": {
99
+ source: "user",
100
+ provider: "anthropic",
101
+ model: "claude-opus-4-7",
102
+ label: "Mine",
103
+ },
104
+ scratch: { source: "user", fallbackProfile: "jev-managed" },
105
+ armA: { source: "user", speed: "fast" },
106
+ blend: {
107
+ source: "user",
108
+ mix: [
109
+ { profile: "jev-managed", weight: 1 },
110
+ { profile: "armA", weight: 1 },
111
+ ],
112
+ },
113
+ },
114
+ profileOrder: ["balanced", "jev-managed", "blend"],
115
+ activeProfile: "jev-managed",
116
+ advisorProfile: "jev-managed",
117
+ callSites: { recall: { profile: "jev-managed", maxTokens: 4096 } },
118
+ },
119
+ });
120
+ seedProfilePinTables({
121
+ conversations: ["jev-managed", "balanced"],
122
+ cronJobs: ["jev-managed"],
123
+ });
124
+
125
+ await migration.run(workspaceDir);
126
+
127
+ const llm = readConfig().llm;
128
+ expect(llm.profiles["jev-managed"]).toBeUndefined();
129
+ expect(llm.profiles["jev-managed-custom"]).toEqual({
130
+ source: "user",
131
+ provider: "anthropic",
132
+ model: "claude-opus-4-7",
133
+ label: "Mine",
134
+ });
135
+ expect(Object.keys(llm.profiles)).toEqual([
136
+ "jev-managed-custom",
137
+ "scratch",
138
+ "armA",
139
+ "blend",
140
+ ]);
141
+ expect(llm.activeProfile).toBe("jev-managed-custom");
142
+ expect(llm.advisorProfile).toBe("jev-managed-custom");
143
+ expect(llm.profileOrder).toEqual([
144
+ "balanced",
145
+ "jev-managed-custom",
146
+ "blend",
147
+ ]);
148
+ expect(llm.callSites.recall.profile).toBe("jev-managed-custom");
149
+ expect(llm.profiles.scratch.fallbackProfile).toBe("jev-managed-custom");
150
+ expect(llm.profiles.blend.mix[0].profile).toBe("jev-managed-custom");
151
+ expect(readPins("conversations")).toEqual([
152
+ "jev-managed-custom",
153
+ "balanced",
154
+ ]);
155
+ expect(readPins("cron_jobs")).toEqual(["jev-managed-custom"]);
156
+ });
157
+
158
+ test("keeps suffixing past taken and reserved names", async () => {
159
+ writeConfig({
160
+ llm: {
161
+ profiles: {
162
+ "jev-managed": {
163
+ source: "user",
164
+ provider: "openai",
165
+ model: "gpt-5.5",
166
+ },
167
+ "jev-managed-custom": { source: "user", provider: "openai" },
168
+ },
169
+ },
170
+ });
171
+
172
+ await migration.run(workspaceDir);
173
+
174
+ const keys = Object.keys(readConfig().llm.profiles);
175
+ expect(keys).toEqual(["jev-managed-custom-2", "jev-managed-custom"]);
176
+ });
177
+
178
+ test("leaves a managed stub alone and is idempotent", async () => {
179
+ const original = {
180
+ llm: {
181
+ profiles: {
182
+ "jev-managed": { source: "managed", status: "disabled" },
183
+ balanced: { source: "managed" },
184
+ },
185
+ },
186
+ };
187
+ writeConfig(original);
188
+
189
+ await migration.run(workspaceDir);
190
+ await migration.run(workspaceDir);
191
+
192
+ expect(readConfig()).toEqual(original);
193
+ expect(existsSync(join(workspaceDir, "data", "db", "assistant.db"))).toBe(
194
+ false,
195
+ );
196
+ });
197
+ });
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Wire contract for the dictation REST endpoint (`POST /dictation`).
3
3
  *
4
- * The body web/macOS sends when asking the daemon to clean up a raw voice
5
- * transcript and classify it as dictation vs. action. `context` is its own
4
+ * The body web/macOS sends to apply explicit dictation replacements, classify
5
+ * voice input, or edit selected text. `context` is its own
6
6
  * named schema/type so consumers (web client) can import `DictationContext`
7
7
  * directly instead of inferring it from the generated client.
8
8
  *
@@ -0,0 +1,66 @@
1
+ import { afterAll, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { Provider } from "../../providers/types.js";
4
+
5
+ let configured: {
6
+ provider: Provider;
7
+ configuredProviderName: string;
8
+ entryRouted: boolean;
9
+ } | null = null;
10
+
11
+ const actual = await import("../../providers/provider-send-message.js");
12
+ mock.module("../../providers/provider-send-message.js", () => ({
13
+ ...actual,
14
+ resolveConfiguredProvider: async () => configured,
15
+ }));
16
+
17
+ const { resolveTypesafeProvider } = await import("../typesafe-noul.js");
18
+
19
+ afterAll(() => {
20
+ mock.restore();
21
+ });
22
+
23
+ function stub(name: string): Provider {
24
+ return {
25
+ name,
26
+ defaultModel: "jev-latest",
27
+ sendMessage: async () => {
28
+ throw new Error("not dispatched in this test");
29
+ },
30
+ } as unknown as Provider;
31
+ }
32
+
33
+ describe("resolveTypesafeProvider", () => {
34
+ test("a BYOK TypeSafe profile resolves", async () => {
35
+ configured = {
36
+ provider: stub("typesafe"),
37
+ configuredProviderName: "typesafe",
38
+ entryRouted: false,
39
+ };
40
+ expect((await resolveTypesafeProvider("voiceEscalationJudge"))?.name).toBe(
41
+ "typesafe",
42
+ );
43
+ });
44
+
45
+ test("a managed Jev profile resolves although it is configured as vellum", async () => {
46
+ configured = {
47
+ provider: stub("typesafe"),
48
+ configuredProviderName: "vellum",
49
+ entryRouted: false,
50
+ };
51
+ expect((await resolveTypesafeProvider("voiceEscalationJudge"))?.name).toBe(
52
+ "typesafe",
53
+ );
54
+ });
55
+
56
+ test("any other dispatched provider is unavailable", async () => {
57
+ configured = {
58
+ provider: stub("anthropic"),
59
+ configuredProviderName: "vellum",
60
+ entryRouted: false,
61
+ };
62
+ expect(await resolveTypesafeProvider("voiceEscalationJudge")).toBeNull();
63
+ configured = null;
64
+ expect(await resolveTypesafeProvider("voiceEscalationJudge")).toBeNull();
65
+ });
66
+ });
@@ -28,12 +28,18 @@ export interface TypesafeNoulResult {
28
28
  latencyMs: number;
29
29
  }
30
30
 
31
- /** The call site's provider when it resolves to TypeSafe, else null. */
31
+ /**
32
+ * The call site's provider when it resolves to TypeSafe, else null. Judged
33
+ * on the dispatched adapter's name rather than the configured provider: a
34
+ * managed profile is configured as `vellum` and only substitutes the
35
+ * TypeSafe upstream at dispatch, so the configured name alone would report
36
+ * the managed Jev profile as unavailable.
37
+ */
32
38
  export async function resolveTypesafeProvider(
33
39
  callSite: LLMCallSite,
34
40
  ): Promise<Provider | null> {
35
41
  const resolved = await resolveConfiguredProvider(callSite);
36
- return resolved?.configuredProviderName === TYPESAFE_PROVIDER_NAME
42
+ return resolved?.provider.name === TYPESAFE_PROVIDER_NAME
37
43
  ? resolved.provider
38
44
  : null;
39
45
  }
@@ -9,10 +9,11 @@
9
9
  * correct reply need something the front door cannot do from here? A
10
10
  * confident yes overrules a front-door answer and hands the turn off.
11
11
  *
12
- * The judge runs only when the `voiceEscalationJudge` call site resolves to
13
- * the TypeSafe provider. Every other outcome (no such profile, provider
14
- * error, timeout, unparseable answer) is a "clear" verdict, so the front
15
- * door's own decision stands exactly as it would without the judge.
12
+ * The judge uses managed Jev by default when Vellum-managed inference is
13
+ * available. An explicit TypeSafe call-site override can use a user-owned
14
+ * connection. Every other outcome (unavailable route, provider error,
15
+ * timeout, unparseable answer) is a "clear" verdict, so the front door's own
16
+ * decision stands exactly as it would without the judge.
16
17
  */
17
18
 
18
19
  import type { Message, Provider } from "../providers/types.js";
@@ -44,7 +44,11 @@ Examples:
44
44
  description:
45
45
  "Destination address for outbound verification (handle, phone number, user ID, or email address)",
46
46
  },
47
- { flags: "--rebind", description: "Replace existing guardian binding" },
47
+ {
48
+ flags: "--rebind",
49
+ description:
50
+ "Create a code although the channel already has a guardian (see below)",
51
+ },
48
52
  {
49
53
  flags: "--conversation-id <conversationId>",
50
54
  description: "Conversation ID for inbound challenges",
@@ -74,7 +78,13 @@ Routes between three creation modes based on the provided options:
74
78
  2. Outbound: --channel <ch> --destination <dest>
75
79
  Sends a verification code to the given destination. Supports telegram
76
80
  (handle or chat ID), phone (E.164 number), slack and discord (user ID),
77
- and email. Use --rebind to replace an existing guardian binding.
81
+ and email.
82
+
83
+ --rebind creates a code although the channel already has a guardian. On
84
+ telegram, slack, discord and email, only the account already bound can redeem
85
+ that code, which is how the same guardian verifies again. A code never moves
86
+ the guardian to a different account there: run "revoke" first, then create
87
+ without --rebind. On phone, an outbound code replaces the bound number.
78
88
 
79
89
  3. Inbound: --channel <ch> (no --destination)
80
90
  Generates a challenge secret for the guardian to send back on the
@@ -176,16 +176,16 @@
176
176
  "icon": "\ud83d\udcbb"
177
177
  },
178
178
  {
179
- "name": "ecommerce-wismo-supporter",
179
+ "name": "ecommerce-order-supporter",
180
180
  "description": "Resolves ecommerce Where-Is-My-Order (WISMO) support inquiries, cross-checks real-time carrier tracking milestones, flags delivery exceptions, and stages customer update drafts without helpdesk seat taxes.",
181
181
  "category": "commerce",
182
182
  "icon": "📦",
183
183
  "license": "MIT",
184
- "homepage": "https://github.com/ZeebBoyBlue/ecommerce-wismo-supporter",
184
+ "homepage": "https://github.com/ZeebBoyBlue/ecommerce-order-supporter",
185
185
  "source": {
186
186
  "source": "github",
187
- "repo": "ZeebBoyBlue/ecommerce-wismo-supporter",
188
- "ref": "9218c23d325c749500d59b6ae651f38264a43cd1"
187
+ "repo": "ZeebBoyBlue/ecommerce-order-supporter",
188
+ "ref": "35e2165a14447a897aee750a198c3f166458226b"
189
189
  }
190
190
  },
191
191
  {
@@ -5,6 +5,7 @@ import { resolveModelIntent } from "../../providers/model-intents.js";
5
5
  import { CALL_SITE_DEFAULTS } from "../call-site-defaults.js";
6
6
  import {
7
7
  CODE_DEFAULT_PROFILE_ENTRIES,
8
+ getConversationProfilesForProvider,
8
9
  getEffectiveProfile,
9
10
  getEffectiveProfiles,
10
11
  getEffectiveProfilesForProvider,
@@ -59,6 +60,37 @@ describe("getEffectiveProfiles", () => {
59
60
  }
60
61
  });
61
62
 
63
+ test("the managed Jev profile routes jev-latest through TypeSafe and is managed-only", () => {
64
+ const jev = CODE_DEFAULT_PROFILE_ENTRIES["jev-managed"];
65
+ expect(jev.model).toBe("jev-latest");
66
+ expect(jev.provider_connection).toBeUndefined();
67
+ expect(resolveRoutingIdentity(jev.provider, jev.model)).toEqual({
68
+ connectionName: "vellum",
69
+ expectedProvider: "typesafe",
70
+ });
71
+ expect(
72
+ resolveDefaultProfileForProvider(undefined, "jev-managed", null),
73
+ ).toBeDefined();
74
+ expect(
75
+ resolveDefaultProfileForProvider(undefined, "jev-managed", {
76
+ provider: "anthropic",
77
+ connectionName: "anthropic-personal",
78
+ }),
79
+ ).toBeUndefined();
80
+ });
81
+
82
+ test("the conversation view drops the managed Jev profile but keeps it selectable for call sites", () => {
83
+ expect(
84
+ Object.keys(getConversationProfilesForProvider(undefined, null)),
85
+ ).not.toContain("jev-managed");
86
+ expect(
87
+ Object.keys(getUserSelectableProfilesForProvider(undefined, null)),
88
+ ).toContain("jev-managed");
89
+ expect(
90
+ Object.keys(getConversationProfilesForProvider(undefined, null)),
91
+ ).toContain("balanced");
92
+ });
93
+
62
94
  test("the managed Balanced profile routes GLM 5.3 Flash through Fireworks", () => {
63
95
  const balanced = CODE_DEFAULT_PROFILE_ENTRIES.balanced;
64
96
  expect(balanced.model).toBe("accounts/fireworks/models/glm-5p3-flash");
@@ -80,7 +112,7 @@ describe("getEffectiveProfiles", () => {
80
112
  test("defaults absent from the workspace resolve from the catalog; os-beta stays flag-gated", () => {
81
113
  const effective = getEffectiveProfiles(undefined);
82
114
  expect(Object.keys(effective).sort()).toEqual(
83
- [...DEFAULT_PROFILE_KEYS, ...BACKUP_PROFILE_KEYS].sort(),
115
+ [...DEFAULT_PROFILE_KEYS, ...BACKUP_PROFILE_KEYS, "jev-managed"].sort(),
84
116
  );
85
117
  expect(getEffectiveProfile({}, "balanced")?.model).toBe(
86
118
  CODE_DEFAULT_PROFILE_ENTRIES.balanced.model as string,
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { LLMSchema } from "../schemas/llm.js";
4
+
5
+ const PIN = { callSites: { voiceEscalationJudge: { profile: "jev-managed" } } };
6
+
7
+ describe("jev-managed as a call-site pin target", () => {
8
+ test("resolves under the managed default provider", () => {
9
+ expect(LLMSchema.safeParse(PIN).success).toBe(true);
10
+ expect(
11
+ LLMSchema.safeParse({
12
+ ...PIN,
13
+ defaultProvider: { provider: "vellum" },
14
+ }).success,
15
+ ).toBe(true);
16
+ });
17
+
18
+ test("can be pinned only to the verdict-consuming call sites", () => {
19
+ for (const site of [
20
+ "voiceEscalationJudge",
21
+ "voiceContinuationJudge",
22
+ "memoryV3SelectL2",
23
+ ]) {
24
+ expect(
25
+ LLMSchema.safeParse({
26
+ callSites: { [site]: { profile: "jev-managed" } },
27
+ }).success,
28
+ ).toBe(true);
29
+ }
30
+ const result = LLMSchema.safeParse({
31
+ callSites: { conversationTitle: { profile: "jev-managed" } },
32
+ });
33
+ expect(result.success).toBe(false);
34
+ expect(JSON.stringify(result.error?.issues)).toContain(
35
+ "can only be pinned to",
36
+ );
37
+ });
38
+
39
+ test("cannot be a mix constituent", () => {
40
+ const result = LLMSchema.safeParse({
41
+ profiles: {
42
+ blend: {
43
+ mix: [
44
+ { profile: "jev-managed", weight: 1 },
45
+ { profile: "balanced", weight: 1 },
46
+ ],
47
+ },
48
+ },
49
+ });
50
+ expect(result.success).toBe(false);
51
+ expect(JSON.stringify(result.error?.issues)).toContain(
52
+ "cannot be a mix constituent",
53
+ );
54
+ });
55
+
56
+ test("is rejected as the active or advisor profile even on the managed column", () => {
57
+ for (const field of ["activeProfile", "advisorProfile"] as const) {
58
+ const result = LLMSchema.safeParse({ [field]: "jev-managed" });
59
+ expect(result.success).toBe(false);
60
+ expect(JSON.stringify(result.error?.issues)).toContain(
61
+ "structured answers rather than chat text",
62
+ );
63
+ }
64
+ expect(LLMSchema.safeParse({ activeProfile: "balanced" }).success).toBe(
65
+ true,
66
+ );
67
+ });
68
+
69
+ test("is rejected under a BYOK default provider, where it has no body", () => {
70
+ const result = LLMSchema.safeParse({
71
+ ...PIN,
72
+ defaultProvider: {
73
+ provider: "anthropic",
74
+ connectionName: "anthropic-personal",
75
+ },
76
+ });
77
+ expect(result.success).toBe(false);
78
+ expect(JSON.stringify(result.error?.issues)).toContain(
79
+ "managed Jev profile",
80
+ );
81
+ });
82
+ });
@@ -1,3 +1,4 @@
1
+ import { JEV_MANAGED_PROFILE_KEY } from "./default-profile-names.js";
1
2
  import { type LLMCallSite } from "./schemas/llm.js";
2
3
 
3
4
  type CallSiteDefaultConfig = {
@@ -123,9 +124,8 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
123
124
  effort: "low",
124
125
  thinking: { enabled: false },
125
126
  },
126
- // The dictation cleanup pass, and nothing else runs on this site. The
127
- // caller has already stopped speaking and has nothing on screen until this
128
- // answers, so it is priced as a latency call.
127
+ // Voice intent classification and explicit selected-text edits are
128
+ // latency-sensitive: the caller is waiting for insertion or dispatch.
129
129
  interactionClassifier: {
130
130
  profile: "latency-optimized",
131
131
  effort: "low",
@@ -153,14 +153,14 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
153
153
  effort: "low",
154
154
  thinking: { enabled: false },
155
155
  },
156
- // The escalation judge only runs when this site resolves to the TypeSafe
157
- // provider (see calls/voice-escalation-judge.ts), so the shipped default is
158
- // any ordinary profile: the judge stays off until a user pins a TypeSafe
159
- // profile here.
160
- voiceEscalationJudge: { profile: "cost-optimized" },
161
- // Same arrangement for the barge-in continuation judge
162
- // (live-voice/continuation-judge.ts): off until a TypeSafe profile is
163
- // pinned here.
156
+ // The managed-only profile resolves through the Vellum connection. On a
157
+ // BYOK install it has no body, so the judge is unavailable and the front
158
+ // door's own decision stands.
159
+ voiceEscalationJudge: {
160
+ profile: JEV_MANAGED_PROFILE_KEY,
161
+ },
162
+ // The barge-in continuation judge remains off until a TypeSafe profile is
163
+ // pinned here (see live-voice/continuation-judge.ts).
164
164
  voiceContinuationJudge: { profile: "cost-optimized" },
165
165
  // Names the background continuation a barge-in spawns, from the interrupted
166
166
  // transcript. The label is fixed at spawn, so the call runs under a short