@vellumai/assistant 0.12.3 → 0.12.4-dev.202609231620.a15f321
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.
- package/Dockerfile +7 -0
- package/README.md +12 -12
- package/docker/mousepad/90-vellum.gschema.override +4 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/notification-avatar.test.ts +48 -4
- package/node_modules/@vellumai/avatar-manifest/src/notification-avatar.ts +12 -8
- package/openapi.yaml +97 -4
- package/package.json +1 -1
- package/src/__tests__/approval-routes-http.test.ts +4 -2
- package/src/__tests__/call-routes-http.test.ts +4 -2
- package/src/__tests__/channel-readiness-discord.test.ts +8 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +8 -0
- package/src/__tests__/config-loader-backfill.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-resurface-done.test.ts +289 -0
- package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
- package/src/__tests__/gemini-provider.test.ts +31 -0
- package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
- package/src/__tests__/job-handler-registration-guard.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +43 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +3 -1
- package/src/__tests__/platform-callback-registration.test.ts +46 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/pricing.test.ts +33 -0
- package/src/__tests__/provider-error-scenarios.test.ts +46 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +45 -15
- package/src/__tests__/retry-fallback-escalation.test.ts +22 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +116 -66
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -2
- package/src/__tests__/skill-load-inline-command.test.ts +35 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -11
- package/src/__tests__/skill-tool-factory.test.ts +50 -2
- package/src/__tests__/steer-on-enqueue-question.test.ts +265 -34
- package/src/__tests__/validate-input.test.ts +73 -0
- package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
- package/src/api/ask-question-tool.ts +50 -0
- package/src/api/events/tool-result.ts +11 -0
- package/src/api/index.ts +7 -0
- package/src/api/requests/dictation.ts +2 -2
- package/src/api/responses/home.ts +27 -0
- package/src/avatar/notification-avatar.test.ts +42 -34
- package/src/avatar/notification-avatar.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +63 -1
- package/src/calls/voice-escalation-judge.ts +6 -5
- package/src/calls/voice-screen-annotation.ts +44 -0
- package/src/calls/voice-session-bridge.ts +22 -0
- package/src/cli/commands/__tests__/plugins.test.ts +29 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +12 -2
- package/src/cli/commands/platform/index.help.ts +2 -1
- package/src/cli/commands/platform/index.ts +8 -2
- package/src/cli/commands/plugins.ts +16 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +29 -0
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +92 -2
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +15 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +66 -2
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +21 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +65 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +43 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +26 -0
- package/src/cli/lib/bundled-marketplace.json +4 -4
- package/src/cli/lib/inspect-plugin.ts +5 -0
- package/src/cli/lib/plugin-catalog-cache.ts +41 -4
- package/src/cli/lib/plugin-catalog-local.ts +9 -0
- package/src/cli/lib/plugin-catalog-platform.ts +48 -9
- package/src/cli/lib/plugin-catalog-visibility.ts +41 -0
- package/src/cli/lib/plugin-details.ts +10 -7
- package/src/cli/lib/search-plugins.ts +2 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +61 -1
- package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +71 -0
- package/src/config/bundled-skills/screen-annotation/SKILL.md +6 -3
- package/src/config/call-site-defaults.ts +17 -11
- package/src/config/default-profile-catalog.ts +118 -19
- package/src/config/default-profile-names.ts +69 -0
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/inference-profile-validation.ts +7 -6
- package/src/config/llm-resolver.ts +16 -0
- package/src/config/profile-text-generation.test.ts +9 -0
- package/src/config/schemas/call-site-catalog.ts +8 -1
- package/src/config/schemas/llm.ts +70 -9
- package/src/config/sidebar-done-gate.ts +27 -0
- package/src/config/sync-gated-profiles.ts +96 -44
- package/src/daemon/__tests__/auto-profile-router.test.ts +228 -0
- package/src/daemon/auto-profile-router.ts +220 -0
- package/src/daemon/conversation-agent-loop.ts +77 -4
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-turn-finalize.ts +12 -0
- package/src/daemon/dictation-text-processing.ts +4 -4
- package/src/daemon/handlers/__tests__/conversation-history-search.test.ts +61 -0
- package/src/daemon/handlers/config-channels.ts +3 -1
- package/src/daemon/handlers/conversation-history.ts +11 -1
- package/src/daemon/handlers/conversations.ts +118 -10
- package/src/daemon/pkb-reminder-builder.test.ts +10 -3
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/desktop/desktop-browser-endpoint.ts +1 -0
- package/src/home/feed-types.ts +2 -0
- package/src/inbound/platform-callback-registration.ts +19 -6
- package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +49 -1
- package/src/ipc/routes/conversation-sync-ipc-routes.ts +38 -2
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +27 -0
- package/src/live-voice/__tests__/protocol.test.ts +22 -0
- package/src/live-voice/continuation-judge.ts +1 -1
- package/src/live-voice/live-voice-session.ts +7 -6
- package/src/live-voice/protocol.ts +16 -5
- package/src/messaging/providers/gmail/client.ts +4 -0
- package/src/notifications/README.md +11 -3
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +87 -3
- package/src/notifications/conversation-pairing.ts +23 -8
- package/src/notifications/home-feed-mirror.ts +39 -0
- package/src/notifications/home-feed-side-effect.ts +46 -19
- package/src/oauth/AGENTS.md +7 -1
- package/src/permissions/question-prompter.ts +14 -4
- package/src/persistence/conversation-crud.ts +114 -3
- package/src/persistence/conversation-queries.ts +48 -3
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +228 -32
- package/src/persistence/embeddings/embedding-backend-shutdown.test.ts +47 -0
- package/src/persistence/embeddings/embedding-backend.ts +23 -41
- package/src/persistence/embeddings/embedding-gemini.test.ts +17 -0
- package/src/persistence/embeddings/embedding-gemini.ts +3 -1
- package/src/persistence/embeddings/embedding-local.ts +72 -17
- package/src/persistence/embeddings/embedding-types.ts +0 -19
- package/src/persistence/jobs-store.ts +229 -0
- package/src/persistence/migrations/383-normalize-opencode-host-connections.ts +44 -0
- package/src/persistence/migrations/__tests__/383-normalize-opencode-host-connections.test.ts +102 -0
- package/src/persistence/steps.ts +12 -0
- package/src/platform/sync-avatar.test.ts +29 -0
- package/src/plugin-api/model-profiles.ts +5 -3
- package/src/plugins/defaults/memory/AGENTS.md +5 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-boundary-guard.test.ts +45 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-job.test.ts +580 -0
- package/src/plugins/defaults/memory/graph/tools.ts +9 -5
- package/src/plugins/defaults/memory/hooks/conversation-deleted.ts +4 -0
- package/src/plugins/defaults/memory/job-handlers.ts +8 -2
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +5 -0
- package/src/plugins/defaults/memory/skill-update-receipt-job.ts +334 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/tools.test.ts +17 -0
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +3 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +2 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +2 -2
- package/src/providers/gemini/client.ts +17 -1
- package/src/providers/inference/auth.ts +25 -0
- package/src/providers/jev/ask.test.ts +66 -0
- package/src/{calls/typesafe-noul.ts → providers/jev/ask.ts} +75 -36
- package/src/providers/malformed-tool-call.ts +55 -0
- package/src/providers/model-catalog.ts +257 -4
- package/src/providers/openai/__tests__/chat-completions-malformed-tool-call.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +30 -0
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/retry.ts +68 -36
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/vellum-model-routing.test.ts +1 -0
- package/src/runtime/__tests__/agent-wake-archived-gate.test.ts +90 -0
- package/src/runtime/__tests__/question-resolution.test.ts +32 -0
- package/src/runtime/agent-wake.ts +9 -2
- package/src/runtime/auth/__tests__/middleware.test.ts +138 -0
- package/src/runtime/auth/middleware.ts +15 -11
- package/src/runtime/middleware/auth.ts +7 -1
- package/src/runtime/pending-interactions.ts +8 -8
- package/src/runtime/question-resolution.ts +54 -8
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +146 -0
- package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +74 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +60 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +8 -0
- package/src/runtime/routes/conversation-list-routes.ts +16 -14
- package/src/runtime/routes/conversation-query-routes.ts +4 -0
- package/src/runtime/routes/conversation-routes.ts +4 -2
- package/src/runtime/routes/diagnostics-routes.ts +66 -229
- package/src/runtime/routes/global-search-routes.ts +22 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +25 -8
- package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
- package/src/runtime/routes/platform-routes.ts +6 -1
- package/src/runtime/routes/plugins-routes.ts +5 -2
- package/src/runtime/sync/documents-sidecar-publish.test.ts +50 -4
- package/src/runtime/sync/resource-sync-events.ts +11 -0
- package/src/runtime/sync/worker-daemon-notify.test.ts +30 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -0
- package/src/runtime/verification-outbound-actions.ts +2 -2
- package/src/skills/validate-input.ts +75 -11
- package/src/telegram/__tests__/webhook-health.test.ts +8 -0
- package/src/tools/ask-question/ask-question-tool.test.ts +38 -0
- package/src/tools/ask-question/ask-question-tool.ts +12 -1
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +28 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +34 -0
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +35 -1
- package/src/tools/network/__tests__/web-search.test.ts +96 -0
- package/src/tools/network/web-fetch.ts +30 -9
- package/src/tools/network/web-search.ts +235 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/format-input-schema.ts +15 -0
- package/src/tools/skills/load.ts +15 -43
- package/src/tools/skills/scaffold-managed.ts +86 -71
- package/src/tools/skills/skill-tool-factory.ts +2 -1
- package/src/tools/workflows/manage-workflows.ts +4 -2
- package/src/util/worker-pipe.test.ts +146 -0
- package/src/util/worker-pipe.ts +49 -0
- package/src/workspace/migrations/158-repoint-opencode-host-provider-fragments.ts +163 -0
- package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
- package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts +188 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
|
@@ -67,6 +67,61 @@ export function isBackupProfileKey(value: string): value is BackupProfileKey {
|
|
|
67
67
|
return (BACKUP_PROFILE_KEYS as readonly string[]).includes(value);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Managed profile for the TypeSafe decision model (Jev). Deliberately NOT
|
|
72
|
+
* part of `DEFAULT_PROFILE_KEYS`: it has no BYOK column, because the
|
|
73
|
+
* platform serves Jev only through the managed route, and its model returns
|
|
74
|
+
* structured answers rather than chat text, so it can never be the active
|
|
75
|
+
* or conversation profile. It exists as a pin target for the call sites
|
|
76
|
+
* that consume a yes/no verdict (the voice judges, the memory selector).
|
|
77
|
+
*/
|
|
78
|
+
export const JEV_MANAGED_PROFILE_KEY = "jev-managed";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Managed profile that picks one of the default profiles per message: the
|
|
82
|
+
* agent loop asks Jev which default fits the user's latest message and runs
|
|
83
|
+
* the turn on that profile (`daemon/auto-profile-router.ts`). Its own body
|
|
84
|
+
* is the managed Balanced body, so any path that dispatches the name
|
|
85
|
+
* directly, or a turn on which the router is unavailable, runs Balanced.
|
|
86
|
+
* Managed column only, because the router depends on the managed Jev route,
|
|
87
|
+
* and flag-gated: it exists only while the `auto-profile` feature flag has
|
|
88
|
+
* reconciled it into the workspace (see `sync-gated-profiles.ts`).
|
|
89
|
+
* Deliberately NOT part of `DEFAULT_PROFILE_KEYS`: it is a selection over
|
|
90
|
+
* those keys, not a fifth intent.
|
|
91
|
+
*/
|
|
92
|
+
export const AUTO_PROFILE_KEY = "auto";
|
|
93
|
+
export const AUTO_PROFILE_FEATURE_FLAG_KEY = "auto-profile";
|
|
94
|
+
|
|
95
|
+
/** The call site the automatic profile router asks Jev through. */
|
|
96
|
+
export const AUTO_PROFILE_ROUTER_CALL_SITE = "autoProfileRouter";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The call sites that consume a structured verdict and so may be pinned to
|
|
100
|
+
* the Jev profile. Every other site expects chat text or a tool call, which
|
|
101
|
+
* the decision model never produces.
|
|
102
|
+
*/
|
|
103
|
+
export const JEV_MANAGED_PROFILE_CALL_SITES = [
|
|
104
|
+
"voiceEscalationJudge",
|
|
105
|
+
"voiceContinuationJudge",
|
|
106
|
+
"memoryV3SelectL2",
|
|
107
|
+
AUTO_PROFILE_ROUTER_CALL_SITE,
|
|
108
|
+
] as const;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Keys whose profile exists on the managed (`vellum`) column only: the
|
|
112
|
+
* backups, the Jev profile, and the Auto profile. Under a BYOK or ChatGPT
|
|
113
|
+
* default provider these names have no body to resolve to.
|
|
114
|
+
*/
|
|
115
|
+
export const MANAGED_ONLY_PROFILE_KEYS = [
|
|
116
|
+
...BACKUP_PROFILE_KEYS,
|
|
117
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
118
|
+
AUTO_PROFILE_KEY,
|
|
119
|
+
] as const;
|
|
120
|
+
|
|
121
|
+
export function isManagedOnlyProfileKey(value: string): boolean {
|
|
122
|
+
return (MANAGED_ONLY_PROFILE_KEYS as readonly string[]).includes(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
70
125
|
/**
|
|
71
126
|
* Flag-gated default profile: only available while the `os-beta` feature
|
|
72
127
|
* flag has reconciled it into the workspace (see `sync-gated-profiles.ts`).
|
|
@@ -76,6 +131,20 @@ export function isBackupProfileKey(value: string): value is BackupProfileKey {
|
|
|
76
131
|
*/
|
|
77
132
|
export const OS_BETA_PROFILE_KEY = "os-beta";
|
|
78
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Managed profiles that resolve only while a workspace entry exists, because
|
|
136
|
+
* a feature flag reconciles that entry in and out. Never always-valid
|
|
137
|
+
* reference targets: a reference to one is valid only with the entry present.
|
|
138
|
+
*/
|
|
139
|
+
export const FLAG_GATED_PROFILE_KEYS = [
|
|
140
|
+
OS_BETA_PROFILE_KEY,
|
|
141
|
+
AUTO_PROFILE_KEY,
|
|
142
|
+
] as const;
|
|
143
|
+
|
|
144
|
+
export function isFlagGatedProfileKey(value: string): boolean {
|
|
145
|
+
return (FLAG_GATED_PROFILE_KEYS as readonly string[]).includes(value);
|
|
146
|
+
}
|
|
147
|
+
|
|
79
148
|
/**
|
|
80
149
|
* The named columns of the intent × provider matrix. `vellum` is the
|
|
81
150
|
* platform-managed column (routed through the single `vellum` connection to
|
|
@@ -170,14 +170,6 @@
|
|
|
170
170
|
"description": "Enable Anthropic fast mode for Opus models (4.6, 4.7, 4.8), delivering up to 2.5x higher output tokens per second at premium pricing",
|
|
171
171
|
"defaultEnabled": false
|
|
172
172
|
},
|
|
173
|
-
{
|
|
174
|
-
"id": "teleport",
|
|
175
|
-
"scope": "client",
|
|
176
|
-
"key": "teleport",
|
|
177
|
-
"label": "Teleport",
|
|
178
|
-
"description": "Enable the local-to-platform teleport UI in General settings. Platform-to-local teleport is GA and not gated by this flag",
|
|
179
|
-
"defaultEnabled": false
|
|
180
|
-
},
|
|
181
173
|
{
|
|
182
174
|
"id": "apple-container",
|
|
183
175
|
"scope": "client",
|
|
@@ -346,6 +338,14 @@
|
|
|
346
338
|
"description": "Show the Add Server action on the MCP settings page. The MCP page itself remains visible; this flag gates only creating new Model Context Protocol server connections.",
|
|
347
339
|
"defaultEnabled": false
|
|
348
340
|
},
|
|
341
|
+
{
|
|
342
|
+
"id": "mcp-catalog-qa-integrations",
|
|
343
|
+
"scope": "assistant",
|
|
344
|
+
"key": "mcp-catalog-qa-integrations",
|
|
345
|
+
"label": "MCP Catalog QA Integrations",
|
|
346
|
+
"description": "Show Gamma, Guru, Intercom, Ramp, Semrush, and Typeform in the installable plugin catalog while their OAuth flows are validated. When off, catalog search, detail, and install-by-name cannot resolve them. Existing installed connections remain available.",
|
|
347
|
+
"defaultEnabled": false
|
|
348
|
+
},
|
|
349
349
|
{
|
|
350
350
|
"id": "os-beta",
|
|
351
351
|
"scope": "assistant",
|
|
@@ -354,6 +354,14 @@
|
|
|
354
354
|
"description": "Enable the OS Beta model profile (MiniMax M3 / Together) in the assistant's model profile selection.",
|
|
355
355
|
"defaultEnabled": false
|
|
356
356
|
},
|
|
357
|
+
{
|
|
358
|
+
"id": "auto-profile",
|
|
359
|
+
"scope": "assistant",
|
|
360
|
+
"key": "auto-profile",
|
|
361
|
+
"label": "Auto Profile",
|
|
362
|
+
"description": "Enable the Auto model profile, which asks Jev which default profile should answer each message.",
|
|
363
|
+
"defaultEnabled": false
|
|
364
|
+
},
|
|
357
365
|
{
|
|
358
366
|
"id": "new-thread-suggestions",
|
|
359
367
|
"scope": "client",
|
|
@@ -557,7 +565,15 @@
|
|
|
557
565
|
"scope": "client",
|
|
558
566
|
"key": "assistant-inbox",
|
|
559
567
|
"label": "Assistant Inbox",
|
|
560
|
-
"description": "Shows an Assistant Inbox entry above Preferences in the web sidebar and the /assistant/inbox page it opens: a mailbox for the assistant's managed vellum.me address (received and sent, with a reading pane), the inline email setup card for entitled orgs with no address yet, and an upgrade card for orgs without the managed_email entitlement. Off hides the entry
|
|
568
|
+
"description": "Shows an Assistant Inbox entry above Preferences in the web sidebar and the /assistant/inbox page it opens: a mailbox for the assistant's managed vellum.me address (received and sent, with a reading pane), the inline email setup card for entitled orgs with no address yet, and an upgrade card for orgs without the managed_email entitlement. With it on, email setup happens only there: the billing wizard hands off to the inbox instead of its own domain step, the billing page's finish-setup nudge gives way to the inbox's rail entry, and the Channels page's Email section points there instead of registering an address itself. Off hides the entry, redirects the route to chat, and keeps the older setup surfaces.",
|
|
569
|
+
"defaultEnabled": false
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"id": "sidebar-done",
|
|
573
|
+
"scope": "both",
|
|
574
|
+
"key": "sidebar-done",
|
|
575
|
+
"label": "Sidebar Done",
|
|
576
|
+
"description": "Turns archiving a chat into marking it done: the sidebar row carries a Done check instead of the overflow menu, an All chats page lists the full history, and done chats stay findable in search by default. Off keeps today's Archive wording, the overflow menu, and search that hides archived rows unless the query asks for them.",
|
|
561
577
|
"defaultEnabled": false
|
|
562
578
|
}
|
|
563
579
|
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getConversationProfilesForProvider } from "./default-profile-catalog.js";
|
|
2
2
|
import { getConfigReadOnly } from "./loader.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -6,17 +6,18 @@ import { getConfigReadOnly } from "./loader.js";
|
|
|
6
6
|
* (code-defined defaults + workspace `llm.profiles`). Returns a user-facing
|
|
7
7
|
* error message when the key is empty or unknown, or `null` when valid.
|
|
8
8
|
*
|
|
9
|
-
* Resolved through the `llm.defaultProvider`-aware
|
|
10
|
-
*
|
|
11
|
-
* routes remain available to automatic
|
|
12
|
-
*
|
|
9
|
+
* Resolved through the `llm.defaultProvider`-aware conversation view, since
|
|
10
|
+
* every caller (schedules, subagent spawns, per-conversation pins) runs chat
|
|
11
|
+
* turns on the result. Managed backup routes remain available to automatic
|
|
12
|
+
* fallback resolution, and the managed Jev profile to the verdict call
|
|
13
|
+
* sites, but neither is reported as a selectable profile here.
|
|
13
14
|
*/
|
|
14
15
|
export function validateInferenceProfileKey(profile: string): string | null {
|
|
15
16
|
if (!profile.trim()) {
|
|
16
17
|
return "inferenceProfile must be a non-empty string";
|
|
17
18
|
}
|
|
18
19
|
const llm = getConfigReadOnly().llm;
|
|
19
|
-
const profiles =
|
|
20
|
+
const profiles = getConversationProfilesForProvider(
|
|
20
21
|
llm?.profiles,
|
|
21
22
|
llm?.defaultProvider ?? null,
|
|
22
23
|
);
|
|
@@ -627,6 +627,22 @@ export function resolveDefaultProfileKey(
|
|
|
627
627
|
return selectWinningProfile(callSite, llm, {}).profileName ?? undefined;
|
|
628
628
|
}
|
|
629
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Returns the code-owned profile tier for catalog display when that tier is
|
|
632
|
+
* available through the workspace's default provider. Workspace call-site
|
|
633
|
+
* pins do not affect this value. A managed-only tier on a BYOK install returns
|
|
634
|
+
* undefined so clients do not report a profile that cannot run there.
|
|
635
|
+
*/
|
|
636
|
+
export function resolveShippedDefaultProfileKey(
|
|
637
|
+
callSite: LLMCallSite,
|
|
638
|
+
llm: z.infer<typeof LLMSchema>,
|
|
639
|
+
): string | undefined {
|
|
640
|
+
const intent = CALL_SITE_DEFAULTS[callSite]?.profile ?? "balanced";
|
|
641
|
+
return usableDefaultIntent(intent, llm, {}, () => undefined) == null
|
|
642
|
+
? undefined
|
|
643
|
+
: intent;
|
|
644
|
+
}
|
|
645
|
+
|
|
630
646
|
/**
|
|
631
647
|
* Returns the profile key that `resolveCallSiteConfig` treats as the winning
|
|
632
648
|
* (highest-precedence) profile for a turn — the profile whose fragment supplies
|
|
@@ -15,6 +15,15 @@ describe("profileSupportsTextGeneration", () => {
|
|
|
15
15
|
).toBe(false);
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
test("false for the managed Jev profile shape", () => {
|
|
19
|
+
expect(
|
|
20
|
+
profileSupportsTextGeneration(
|
|
21
|
+
{ provider: "vellum", model: "jev-latest" },
|
|
22
|
+
{},
|
|
23
|
+
),
|
|
24
|
+
).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
18
27
|
test("true for a chat model and for unlisted custom ids", () => {
|
|
19
28
|
expect(
|
|
20
29
|
profileSupportsTextGeneration(
|
|
@@ -307,7 +307,7 @@ const CATALOG_RECORD: CatalogRecord = {
|
|
|
307
307
|
id: "voiceEscalationJudge",
|
|
308
308
|
displayName: "Voice Escalation Judge",
|
|
309
309
|
description:
|
|
310
|
-
"Second opinion on the voice front door's escalate decision.
|
|
310
|
+
"Second opinion on the voice front door's escalate decision. Uses managed Jev when Vellum-managed inference is available, or an explicitly configured TypeSafe profile; a confident 'needs tools' verdict hands the turn to the escalated leg.",
|
|
311
311
|
domain: "agentLoop",
|
|
312
312
|
},
|
|
313
313
|
voiceContinuationJudge: {
|
|
@@ -317,6 +317,13 @@ const CATALOG_RECORD: CatalogRecord = {
|
|
|
317
317
|
"Decides from the caller's interruption whether a barged-in request should keep running in the background. Runs only when pinned to a TypeSafe profile.",
|
|
318
318
|
domain: "agentLoop",
|
|
319
319
|
},
|
|
320
|
+
autoProfileRouter: {
|
|
321
|
+
id: "autoProfileRouter",
|
|
322
|
+
displayName: "Auto Profile Router",
|
|
323
|
+
description:
|
|
324
|
+
"Picks which default profile answers a message when the conversation runs on the Auto profile. Asks managed Jev one choice question over the latest message; when unavailable the turn runs on Balanced.",
|
|
325
|
+
domain: "agentLoop",
|
|
326
|
+
},
|
|
320
327
|
voiceContinuationLabel: {
|
|
321
328
|
id: "voiceContinuationLabel",
|
|
322
329
|
displayName: "Voice Continuation Label",
|
|
@@ -14,15 +14,28 @@ import {
|
|
|
14
14
|
parseVellumModel,
|
|
15
15
|
} from "../../providers/vellum-model-routing.js";
|
|
16
16
|
import {
|
|
17
|
-
|
|
17
|
+
AUTO_PROFILE_KEY,
|
|
18
18
|
DEFAULT_PROFILE_KEYS,
|
|
19
19
|
DEFAULT_PROFILE_PROVIDERS,
|
|
20
20
|
FALLBACK_PROFILE_BY_KEY,
|
|
21
|
-
isBackupProfileKey,
|
|
22
21
|
isDefaultProfileKey,
|
|
22
|
+
isFlagGatedProfileKey,
|
|
23
|
+
isManagedOnlyProfileKey,
|
|
24
|
+
JEV_MANAGED_PROFILE_CALL_SITES,
|
|
25
|
+
JEV_MANAGED_PROFILE_KEY,
|
|
26
|
+
MANAGED_ONLY_PROFILE_KEYS,
|
|
23
27
|
} from "../default-profile-names.js";
|
|
24
28
|
import { InputModalitiesSchema } from "../input-modalities.js";
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Managed-only keys that are valid reference targets on the managed column
|
|
32
|
+
* whether or not a workspace stub exists. The flag-gated ones (`os-beta`,
|
|
33
|
+
* `auto`) are excluded: they resolve only while their reconciled stub is
|
|
34
|
+
* present, so they enter the reference set through `llm.profiles` alone.
|
|
35
|
+
*/
|
|
36
|
+
const ALWAYS_RESOLVING_MANAGED_ONLY_PROFILE_KEYS =
|
|
37
|
+
MANAGED_ONLY_PROFILE_KEYS.filter((key) => !isFlagGatedProfileKey(key));
|
|
38
|
+
|
|
26
39
|
/**
|
|
27
40
|
* Unified LLM configuration schema.
|
|
28
41
|
*
|
|
@@ -232,6 +245,7 @@ export const LLMCallSiteEnum = z.enum([
|
|
|
232
245
|
"voiceEscalationJudge",
|
|
233
246
|
"voiceContinuationJudge",
|
|
234
247
|
"voiceContinuationLabel",
|
|
248
|
+
"autoProfileRouter",
|
|
235
249
|
"trustRuleSuggestion",
|
|
236
250
|
"homeGreeting",
|
|
237
251
|
"homeSuggestedPrompts",
|
|
@@ -766,10 +780,16 @@ function unresolvableProfileReason(
|
|
|
766
780
|
name: string,
|
|
767
781
|
backupsResolve: boolean,
|
|
768
782
|
): string {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
783
|
+
if (backupsResolve || !isManagedOnlyProfileKey(name)) {
|
|
784
|
+
return "is not defined in llm.profiles";
|
|
785
|
+
}
|
|
786
|
+
if (name === JEV_MANAGED_PROFILE_KEY) {
|
|
787
|
+
return "is the managed Jev profile, which resolves only while llm.defaultProvider is the managed provider";
|
|
788
|
+
}
|
|
789
|
+
if (name === AUTO_PROFILE_KEY) {
|
|
790
|
+
return "is the managed Auto profile, which resolves only while llm.defaultProvider is the managed provider";
|
|
791
|
+
}
|
|
792
|
+
return "is a managed backup profile, which resolves only while llm.defaultProvider is the managed provider";
|
|
773
793
|
}
|
|
774
794
|
|
|
775
795
|
/**
|
|
@@ -800,7 +820,7 @@ function referenceableProfileKeys(
|
|
|
800
820
|
): string[] {
|
|
801
821
|
return Object.entries(profiles ?? {})
|
|
802
822
|
.filter(([name, value]) => {
|
|
803
|
-
if (backupsResolve || !
|
|
823
|
+
if (backupsResolve || !isManagedOnlyProfileKey(name)) {
|
|
804
824
|
return true;
|
|
805
825
|
}
|
|
806
826
|
const entry =
|
|
@@ -854,7 +874,7 @@ export function collectFallbackProfileIssues(
|
|
|
854
874
|
const profileNames = new Set([
|
|
855
875
|
...referenceableProfileKeys(profiles, backupsResolve),
|
|
856
876
|
...DEFAULT_PROFILE_KEYS,
|
|
857
|
-
...(backupsResolve ?
|
|
877
|
+
...(backupsResolve ? ALWAYS_RESOLVING_MANAGED_ONLY_PROFILE_KEYS : []),
|
|
858
878
|
]);
|
|
859
879
|
const mixProfileNames = new Set(
|
|
860
880
|
entries
|
|
@@ -1004,6 +1024,8 @@ export const LLMSchema = z
|
|
|
1004
1024
|
// they join the set only under a managed `llm.defaultProvider`: on a BYOK
|
|
1005
1025
|
// or ChatGPT default provider they have no body to resolve to, and
|
|
1006
1026
|
// keeping the reference would strand a selection the picker cannot show.
|
|
1027
|
+
// The managed Jev profile (`JEV_MANAGED_PROFILE_KEY`) is scoped the same
|
|
1028
|
+
// way, as a pin target for the judge call sites.
|
|
1007
1029
|
// The flag-gated `os-beta` is excluded: it resolves only while a
|
|
1008
1030
|
// workspace entry exists, so a reference to it is valid only when that
|
|
1009
1031
|
// entry is present in `config.profiles`. A backup name materialized as a
|
|
@@ -1018,9 +1040,23 @@ export const LLMSchema = z
|
|
|
1018
1040
|
backupsResolve,
|
|
1019
1041
|
),
|
|
1020
1042
|
...DEFAULT_PROFILE_KEYS,
|
|
1021
|
-
...(backupsResolve ?
|
|
1043
|
+
...(backupsResolve ? ALWAYS_RESOLVING_MANAGED_ONLY_PROFILE_KEYS : []),
|
|
1022
1044
|
]);
|
|
1023
1045
|
for (const [siteId, siteConfig] of Object.entries(config.callSites ?? {})) {
|
|
1046
|
+
// The Jev profile answers with structured verdicts, so only the sites
|
|
1047
|
+
// built to read one may pin it; a text-producing site would receive
|
|
1048
|
+
// output it cannot use. (Mix arms are covered by the profile-level
|
|
1049
|
+
// mix validation below, which bars the profile from any mix.)
|
|
1050
|
+
if (
|
|
1051
|
+
siteConfig?.profile === JEV_MANAGED_PROFILE_KEY &&
|
|
1052
|
+
!(JEV_MANAGED_PROFILE_CALL_SITES as readonly string[]).includes(siteId)
|
|
1053
|
+
) {
|
|
1054
|
+
ctx.addIssue({
|
|
1055
|
+
code: "custom",
|
|
1056
|
+
path: ["callSites", siteId, "profile"],
|
|
1057
|
+
message: `Profile "${JEV_MANAGED_PROFILE_KEY}" returns structured answers rather than chat text, so it can only be pinned to ${JEV_MANAGED_PROFILE_CALL_SITES.join(", ")}`,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1024
1060
|
if (siteConfig?.profile == null) {
|
|
1025
1061
|
continue;
|
|
1026
1062
|
}
|
|
@@ -1052,6 +1088,20 @@ export const LLMSchema = z
|
|
|
1052
1088
|
message: `Profile "${config.advisorProfile}" referenced by llm.advisorProfile ${unresolvableProfileReason(config.advisorProfile, backupsResolve)}`,
|
|
1053
1089
|
});
|
|
1054
1090
|
}
|
|
1091
|
+
// The managed Jev profile is a valid reference target for call-site pins
|
|
1092
|
+
// only. Its model returns structured verdicts rather than chat text, so
|
|
1093
|
+
// the conversation positions reject it here as well as at the write
|
|
1094
|
+
// routes, which keeps a raw config write from handing the main agent or
|
|
1095
|
+
// the advisor a model that cannot answer in prose.
|
|
1096
|
+
for (const field of ["activeProfile", "advisorProfile"] as const) {
|
|
1097
|
+
if (config[field] === JEV_MANAGED_PROFILE_KEY) {
|
|
1098
|
+
ctx.addIssue({
|
|
1099
|
+
code: "custom",
|
|
1100
|
+
path: [field],
|
|
1101
|
+
message: `Profile "${JEV_MANAGED_PROFILE_KEY}" referenced by llm.${field} returns structured answers rather than chat text, so it cannot be the conversation or advisor profile`,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1055
1105
|
|
|
1056
1106
|
// --- Mix profile validation --------------------------------------------
|
|
1057
1107
|
// Config keys a mix profile must NOT also set (a mix only references other
|
|
@@ -1105,6 +1155,17 @@ export const LLMSchema = z
|
|
|
1105
1155
|
});
|
|
1106
1156
|
continue;
|
|
1107
1157
|
}
|
|
1158
|
+
// A mix is a conversation profile, so an arm that returns structured
|
|
1159
|
+
// verdicts rather than text would hand a chat turn to the decision
|
|
1160
|
+
// model whenever the seeded pick lands on it.
|
|
1161
|
+
if (arm.profile === JEV_MANAGED_PROFILE_KEY) {
|
|
1162
|
+
ctx.addIssue({
|
|
1163
|
+
code: "custom",
|
|
1164
|
+
path: ["profiles", name, "mix", index, "profile"],
|
|
1165
|
+
message: `Mix profile "${name}" references "${JEV_MANAGED_PROFILE_KEY}", which returns structured answers rather than chat text and cannot be a mix constituent.`,
|
|
1166
|
+
});
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1108
1169
|
// (b) No nesting — a mix arm must be a standard (non-mix) profile.
|
|
1109
1170
|
if (mixProfileNames.has(arm.profile)) {
|
|
1110
1171
|
ctx.addIssue({
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isAssistantFeatureFlagEnabled } from "./assistant-feature-flags.js";
|
|
2
|
+
import type { AssistantConfig } from "./schema.js";
|
|
3
|
+
|
|
4
|
+
export const SIDEBAR_DONE_FLAG_KEY = "sidebar-done" as const;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Whether archiving a chat means marking it done.
|
|
8
|
+
*
|
|
9
|
+
* The flag is `both`-scoped: clients gate the Done affordances and the Old
|
|
10
|
+
* chats page on it, and the daemon gates the halves the feature needs. Done is
|
|
11
|
+
* `archived_at`, so no row changes shape in either state.
|
|
12
|
+
*
|
|
13
|
+
* On the daemon side there are two. The first is search reach: off, an
|
|
14
|
+
* archived conversation is hidden from search unless the query names it
|
|
15
|
+
* (`is:archived`); on, a done chat is findable by default and `is:unarchived`
|
|
16
|
+
* is the opt-out, because a chat the user marked done is filed, not deleted,
|
|
17
|
+
* and search is how they get back to it.
|
|
18
|
+
*
|
|
19
|
+
* The second is that a done chat keeps working. Off, an archived conversation
|
|
20
|
+
* rejects every wake and nothing but the unarchive route clears the column, so
|
|
21
|
+
* marking a chat done silently kills its schedule or its channel thread. On, a
|
|
22
|
+
* wake still runs on it, and a message the user reads landing in it brings the
|
|
23
|
+
* conversation back to the list.
|
|
24
|
+
*/
|
|
25
|
+
export function isSidebarDoneEnabled(config?: AssistantConfig): boolean {
|
|
26
|
+
return isAssistantFeatureFlagEnabled(SIDEBAR_DONE_FLAG_KEY, config);
|
|
27
|
+
}
|
|
@@ -2,8 +2,16 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
2
2
|
|
|
3
3
|
import { getLogger } from "../util/logger.js";
|
|
4
4
|
import { isAssistantFeatureFlagEnabled } from "./assistant-feature-flags.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
AUTO_PROFILE_TEMPLATE,
|
|
7
|
+
type DefaultProfileTemplate,
|
|
8
|
+
OS_BETA_PROFILE_TEMPLATE,
|
|
9
|
+
} from "./default-profile-catalog.js";
|
|
10
|
+
import {
|
|
11
|
+
AUTO_PROFILE_FEATURE_FLAG_KEY,
|
|
12
|
+
AUTO_PROFILE_KEY,
|
|
13
|
+
OS_BETA_PROFILE_KEY,
|
|
14
|
+
} from "./default-profile-names.js";
|
|
7
15
|
import {
|
|
8
16
|
getConfigReadOnly,
|
|
9
17
|
invalidateConfigCache,
|
|
@@ -18,16 +26,56 @@ import {
|
|
|
18
26
|
|
|
19
27
|
const log = getLogger("sync-gated-profiles");
|
|
20
28
|
|
|
29
|
+
interface GatedProfile {
|
|
30
|
+
key: string;
|
|
31
|
+
flag: string;
|
|
32
|
+
template: DefaultProfileTemplate;
|
|
33
|
+
/** Insert the key into a `profileOrder` that does not yet list it. */
|
|
34
|
+
place: (profileOrder: string[]) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Whether a BYOK install first materializes the stub disabled. True for a
|
|
37
|
+
* profile whose managed route exists but is unusable until the user enables
|
|
38
|
+
* the managed connection; false for a managed-only profile, whose stub has
|
|
39
|
+
* no body on a BYOK column and so is never offered there anyway.
|
|
40
|
+
*/
|
|
41
|
+
byokStartsDisabled: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const GATED_PROFILES: readonly GatedProfile[] = [
|
|
45
|
+
{
|
|
46
|
+
key: OS_BETA_PROFILE_KEY,
|
|
47
|
+
flag: OS_BETA_FEATURE_FLAG_KEY,
|
|
48
|
+
template: OS_BETA_PROFILE_TEMPLATE,
|
|
49
|
+
place: (profileOrder) => {
|
|
50
|
+
const balancedIndex = profileOrder.indexOf("balanced");
|
|
51
|
+
if (balancedIndex >= 0) {
|
|
52
|
+
profileOrder.splice(balancedIndex + 1, 0, OS_BETA_PROFILE_KEY);
|
|
53
|
+
} else {
|
|
54
|
+
profileOrder.push(OS_BETA_PROFILE_KEY);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
byokStartsDisabled: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: AUTO_PROFILE_KEY,
|
|
61
|
+
flag: AUTO_PROFILE_FEATURE_FLAG_KEY,
|
|
62
|
+
template: AUTO_PROFILE_TEMPLATE,
|
|
63
|
+
// Auto leads the picker.
|
|
64
|
+
place: (profileOrder) => profileOrder.unshift(AUTO_PROFILE_KEY),
|
|
65
|
+
byokStartsDisabled: false,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
21
69
|
/**
|
|
22
70
|
* Reconcile flag-gated managed profiles against the current feature-flag state.
|
|
23
71
|
*
|
|
24
72
|
* `seedInferenceProfiles()` runs synchronously at boot before feature flags are
|
|
25
|
-
* available, so the OS Beta
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
73
|
+
* available, so the gated profiles (OS Beta, Auto) are materialized here once
|
|
74
|
+
* flags have loaded. When a profile's flag is on, its managed stub is created
|
|
75
|
+
* and placed in `profileOrder`; when it is off, a previously managed entry is
|
|
76
|
+
* removed with `profileOrder` / `activeProfile` / `advisorProfile` / call-site
|
|
77
|
+
* fallbacks. The reconcile is idempotent and never touches a user-owned
|
|
78
|
+
* profile of the same name.
|
|
31
79
|
*
|
|
32
80
|
* Returns whether the on-disk config changed.
|
|
33
81
|
*/
|
|
@@ -49,44 +97,52 @@ export function reconcileFlagGatedProfiles(): boolean {
|
|
|
49
97
|
: [];
|
|
50
98
|
llm.profileOrder = profileOrder;
|
|
51
99
|
|
|
52
|
-
// The resolver reads flag state from the gateway-populated override cache and
|
|
53
|
-
// ignores the config argument; pass the read-only config for signature parity
|
|
54
|
-
// without mutating disk before the reconcile decision is made.
|
|
55
|
-
const enabled = isAssistantFeatureFlagEnabled(
|
|
56
|
-
OS_BETA_FEATURE_FLAG_KEY,
|
|
57
|
-
getConfigReadOnly(),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
100
|
const isPlatform =
|
|
61
101
|
process.env.IS_PLATFORM === "true" || process.env.IS_PLATFORM === "1";
|
|
62
102
|
const isByokMode = !isPlatform;
|
|
63
103
|
|
|
64
|
-
|
|
104
|
+
let changed = false;
|
|
105
|
+
for (const gated of GATED_PROFILES) {
|
|
106
|
+
// The resolver reads flag state from the gateway-populated override cache
|
|
107
|
+
// and ignores the config argument; pass the read-only config for signature
|
|
108
|
+
// parity without mutating disk before the reconcile decision is made.
|
|
109
|
+
const enabled = isAssistantFeatureFlagEnabled(
|
|
110
|
+
gated.flag,
|
|
111
|
+
getConfigReadOnly(),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const previous = readObject(profiles[gated.key]);
|
|
65
115
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
116
|
+
// Never clobber a user-owned profile that happens to share the name. The
|
|
117
|
+
// entry is ours to manage only when it is absent or already managed; a
|
|
118
|
+
// user-sourced entry of the same name is left untouched on every path.
|
|
119
|
+
const isOursToManage = previous == null || previous.source === "managed";
|
|
120
|
+
if (!isOursToManage) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
73
123
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
124
|
+
const order = llm.profileOrder as string[];
|
|
125
|
+
const profileChanged = enabled
|
|
126
|
+
? enableProfile(gated, profiles, order, previous, isByokMode)
|
|
127
|
+
: disableProfile(gated.key, llm, profiles, order, previous);
|
|
128
|
+
if (profileChanged) {
|
|
129
|
+
changed = true;
|
|
130
|
+
log.info(
|
|
131
|
+
{ profile: gated.key, enabled },
|
|
132
|
+
"Reconciled flag-gated profile",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
77
136
|
|
|
78
137
|
if (changed) {
|
|
79
138
|
saveRawConfig(config);
|
|
80
139
|
invalidateConfigCache();
|
|
81
|
-
log.info(
|
|
82
|
-
{ profile: OS_BETA_PROFILE_KEY, enabled },
|
|
83
|
-
"Reconciled flag-gated profile",
|
|
84
|
-
);
|
|
85
140
|
}
|
|
86
141
|
return changed;
|
|
87
142
|
}
|
|
88
143
|
|
|
89
144
|
function enableProfile(
|
|
145
|
+
gated: GatedProfile,
|
|
90
146
|
profiles: Record<string, Record<string, unknown>>,
|
|
91
147
|
profileOrder: string[],
|
|
92
148
|
previous: Record<string, unknown> | null,
|
|
@@ -100,13 +156,13 @@ function enableProfile(
|
|
|
100
156
|
|
|
101
157
|
// BYOK installs create the stub disabled: the managed inference connection
|
|
102
158
|
// backing this profile isn't usable until the user enables it, so a fresh
|
|
103
|
-
//
|
|
159
|
+
// entry starts disabled to avoid offering an unusable route. The
|
|
104
160
|
// " (Managed)" label suffix disambiguates it from personal profiles in
|
|
105
161
|
// pickers. A user's own overrides (preserved below) win on later
|
|
106
162
|
// reconciles.
|
|
107
|
-
if (isByokMode && !previous) {
|
|
163
|
+
if (gated.byokStartsDisabled && isByokMode && !previous) {
|
|
108
164
|
next.status = "disabled";
|
|
109
|
-
next.label = `${
|
|
165
|
+
next.label = `${gated.template.label} (Managed)`;
|
|
110
166
|
}
|
|
111
167
|
|
|
112
168
|
if (previous) {
|
|
@@ -124,17 +180,12 @@ function enableProfile(
|
|
|
124
180
|
|
|
125
181
|
let changed = false;
|
|
126
182
|
if (!previous || !isDeepStrictEqual(previous, next)) {
|
|
127
|
-
profiles[
|
|
183
|
+
profiles[gated.key] = next as ProfileEntry;
|
|
128
184
|
changed = true;
|
|
129
185
|
}
|
|
130
186
|
|
|
131
|
-
if (!profileOrder.includes(
|
|
132
|
-
|
|
133
|
-
if (balancedIndex >= 0) {
|
|
134
|
-
profileOrder.splice(balancedIndex + 1, 0, OS_BETA_PROFILE_KEY);
|
|
135
|
-
} else {
|
|
136
|
-
profileOrder.push(OS_BETA_PROFILE_KEY);
|
|
137
|
-
}
|
|
187
|
+
if (!profileOrder.includes(gated.key)) {
|
|
188
|
+
gated.place(profileOrder);
|
|
138
189
|
changed = true;
|
|
139
190
|
}
|
|
140
191
|
|
|
@@ -146,6 +197,7 @@ function enableProfile(
|
|
|
146
197
|
const MIX_MIN_ARMS = 2;
|
|
147
198
|
|
|
148
199
|
function disableProfile(
|
|
200
|
+
key: string,
|
|
149
201
|
llm: Record<string, unknown>,
|
|
150
202
|
profiles: Record<string, Record<string, unknown>>,
|
|
151
203
|
profileOrder: string[],
|
|
@@ -155,13 +207,13 @@ function disableProfile(
|
|
|
155
207
|
return false;
|
|
156
208
|
}
|
|
157
209
|
|
|
158
|
-
delete profiles[
|
|
210
|
+
delete profiles[key];
|
|
159
211
|
|
|
160
212
|
// The removal closure: every name here is absent from `profiles` once the
|
|
161
213
|
// closure settles, so the written config can never reference one. A mix that
|
|
162
214
|
// loses arms below the >= 2 minimum is itself invalid, so it joins the set
|
|
163
215
|
// and the loop runs to a fixpoint to resolve any references that cascade.
|
|
164
|
-
const removed = new Set<string>([
|
|
216
|
+
const removed = new Set<string>([key]);
|
|
165
217
|
let cascading = true;
|
|
166
218
|
while (cascading) {
|
|
167
219
|
cascading = false;
|