@vellumai/assistant 0.10.3 → 0.10.4-staging.1
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/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -46,6 +46,8 @@ export interface GitContext {
|
|
|
46
46
|
repo: string;
|
|
47
47
|
dirty: boolean;
|
|
48
48
|
pushed: boolean;
|
|
49
|
+
/** Repo-relative path to the plugin directory, or "" if at repo root. */
|
|
50
|
+
pluginPath: string;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface PublishPayload {
|
|
@@ -225,6 +227,12 @@ export async function resolveGitContext(dir: string): Promise<GitContext> {
|
|
|
225
227
|
}
|
|
226
228
|
const repo = remoteMatch[1];
|
|
227
229
|
|
|
230
|
+
// Compute the repo-relative path to the plugin directory.
|
|
231
|
+
const repoRoot = await runGit(dir, ["rev-parse", "--show-toplevel"]);
|
|
232
|
+
const pluginPath = resolve(dir)
|
|
233
|
+
.slice(resolve(repoRoot).length + 1)
|
|
234
|
+
.replace(/\\/g, "/");
|
|
235
|
+
|
|
228
236
|
// Check if the commit is pushed
|
|
229
237
|
let pushed = true;
|
|
230
238
|
try {
|
|
@@ -235,7 +243,7 @@ export async function resolveGitContext(dir: string): Promise<GitContext> {
|
|
|
235
243
|
pushed = true;
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
return { sha, repo, dirty, pushed };
|
|
246
|
+
return { sha, repo, dirty, pushed, pluginPath };
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
// ---------------------------------------------------------------------------
|
|
@@ -261,6 +269,7 @@ export function buildPublishPayload(
|
|
|
261
269
|
source: "github",
|
|
262
270
|
repo: git.repo,
|
|
263
271
|
ref: git.sha,
|
|
272
|
+
...(git.pluginPath ? { path: git.pluginPath } : {}),
|
|
264
273
|
},
|
|
265
274
|
category,
|
|
266
275
|
};
|
|
@@ -379,7 +388,228 @@ export function formatValidationResult(validation: PublishValidation): string {
|
|
|
379
388
|
return lines.join("\n");
|
|
380
389
|
}
|
|
381
390
|
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
// CLI entrypoint
|
|
393
|
+
// ---------------------------------------------------------------------------
|
|
394
|
+
|
|
395
|
+
export interface PublishCliOptions {
|
|
396
|
+
print?: boolean;
|
|
397
|
+
path?: string;
|
|
398
|
+
force?: boolean;
|
|
399
|
+
json?: boolean;
|
|
400
|
+
category?: string;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface PublishCliDeps {
|
|
404
|
+
confirmPrompt: (opts: {
|
|
405
|
+
question: string;
|
|
406
|
+
isTTY: boolean;
|
|
407
|
+
refuseNonInteractiveMessage: string;
|
|
408
|
+
}) => Promise<string>;
|
|
409
|
+
}
|
|
410
|
+
|
|
382
411
|
/**
|
|
412
|
+
* Run the full `plugins publish` flow. This is the single entrypoint that
|
|
413
|
+
* the CLI command calls. All validation, git resolution, confirmation,
|
|
414
|
+
* and submission logic lives here rather than in the command file.
|
|
415
|
+
*
|
|
416
|
+
* Returns `true` on success, `false` on failure (with an appropriate
|
|
417
|
+
* message already printed).
|
|
418
|
+
*/
|
|
419
|
+
export async function runPublish(
|
|
420
|
+
opts: PublishCliOptions,
|
|
421
|
+
deps: PublishCliDeps,
|
|
422
|
+
): Promise<boolean> {
|
|
423
|
+
// 1. Find the plugin root
|
|
424
|
+
const searchDir = opts.path ?? process.cwd();
|
|
425
|
+
const pluginDir = findPluginRoot(searchDir);
|
|
426
|
+
if (!pluginDir) {
|
|
427
|
+
if (opts.json) {
|
|
428
|
+
process.stdout.write(
|
|
429
|
+
JSON.stringify({
|
|
430
|
+
ok: false,
|
|
431
|
+
error: "no_package_json",
|
|
432
|
+
message: `No package.json found in ${searchDir} or any parent directory.`,
|
|
433
|
+
}) + "\n",
|
|
434
|
+
);
|
|
435
|
+
} else {
|
|
436
|
+
console.error(
|
|
437
|
+
`No package.json found in ${searchDir} or any parent directory.`,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 2. Validate the plugin
|
|
444
|
+
const validation = validatePluginForPublish(pluginDir);
|
|
445
|
+
if (!validation.valid) {
|
|
446
|
+
if (opts.json) {
|
|
447
|
+
process.stdout.write(
|
|
448
|
+
JSON.stringify({ ok: false, errors: validation.issues }) + "\n",
|
|
449
|
+
);
|
|
450
|
+
} else {
|
|
451
|
+
console.error(formatValidationResult(validation));
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!opts.json && validation.warnings.length > 0) {
|
|
457
|
+
console.warn(formatValidationResult(validation));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 3. Handle --print mode early (no git context needed)
|
|
461
|
+
if (opts.print) {
|
|
462
|
+
// For --print, resolve git context best-effort to fill source fields
|
|
463
|
+
let git: GitContext | null = null;
|
|
464
|
+
try {
|
|
465
|
+
git = await resolveGitContext(pluginDir);
|
|
466
|
+
} catch {
|
|
467
|
+
// Git not available — use placeholder values
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const category = opts.category ?? "other";
|
|
471
|
+
const payload = git
|
|
472
|
+
? buildPublishPayload(validation, git, category)
|
|
473
|
+
: {
|
|
474
|
+
name: validation.packageJson.name!,
|
|
475
|
+
source: {
|
|
476
|
+
source: "github" as const,
|
|
477
|
+
repo: "<unknown>",
|
|
478
|
+
ref: "<unknown>",
|
|
479
|
+
},
|
|
480
|
+
category,
|
|
481
|
+
...(validation.packageJson.description
|
|
482
|
+
? { description: validation.packageJson.description }
|
|
483
|
+
: {}),
|
|
484
|
+
...(validation.packageJson.license
|
|
485
|
+
? { license: validation.packageJson.license }
|
|
486
|
+
: {}),
|
|
487
|
+
...(validation.packageJson.homepage
|
|
488
|
+
? { homepage: validation.packageJson.homepage }
|
|
489
|
+
: {}),
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
if (opts.json) {
|
|
493
|
+
process.stdout.write(JSON.stringify({ ok: true, payload }) + "\n");
|
|
494
|
+
} else {
|
|
495
|
+
console.log(formatPayloadForPrint(payload));
|
|
496
|
+
}
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// 4. Resolve git context (required for submission)
|
|
501
|
+
let git: GitContext;
|
|
502
|
+
try {
|
|
503
|
+
git = await resolveGitContext(pluginDir);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
506
|
+
if (opts.json) {
|
|
507
|
+
process.stdout.write(
|
|
508
|
+
JSON.stringify({
|
|
509
|
+
ok: false,
|
|
510
|
+
error: "git_resolution_failed",
|
|
511
|
+
message: `Git context resolution failed: ${message}`,
|
|
512
|
+
}) + "\n",
|
|
513
|
+
);
|
|
514
|
+
} else {
|
|
515
|
+
console.error(`Git context resolution failed: ${message}`);
|
|
516
|
+
}
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (git.dirty) {
|
|
521
|
+
console.warn(
|
|
522
|
+
"Warning: working tree is dirty. The pinned commit SHA should match a clean, pushed commit.",
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (!git.pushed) {
|
|
527
|
+
if (opts.json) {
|
|
528
|
+
process.stdout.write(
|
|
529
|
+
JSON.stringify({
|
|
530
|
+
ok: false,
|
|
531
|
+
error: "not_pushed",
|
|
532
|
+
message: `Commit ${git.sha.slice(0, 7)} has not been pushed to the remote. Push your changes first.`,
|
|
533
|
+
}) + "\n",
|
|
534
|
+
);
|
|
535
|
+
} else {
|
|
536
|
+
console.error(
|
|
537
|
+
`Commit ${git.sha.slice(0, 7)} has not been pushed to the remote. Push your changes first.`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 5. Determine category and build payload
|
|
544
|
+
const category = opts.category ?? "other";
|
|
545
|
+
const payload = buildPublishPayload(validation, git, category);
|
|
546
|
+
|
|
547
|
+
// 6. Confirm before submitting (unless --force or --json)
|
|
548
|
+
if (!opts.force && !opts.json) {
|
|
549
|
+
console.log("\nPlugin entry to submit:");
|
|
550
|
+
console.log(formatPayloadForPrint(payload));
|
|
551
|
+
console.log("");
|
|
552
|
+
const result = await deps.confirmPrompt({
|
|
553
|
+
question: "Submit this entry to the Vellum marketplace? [y/N] ",
|
|
554
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
555
|
+
refuseNonInteractiveMessage:
|
|
556
|
+
"Refusing to publish non-interactively. Pass --force to confirm.",
|
|
557
|
+
});
|
|
558
|
+
if (result === "non-interactive" || result === "denied") {
|
|
559
|
+
if (result === "non-interactive") {
|
|
560
|
+
console.error(
|
|
561
|
+
"Refusing to publish non-interactively. Pass --force to confirm.",
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 7. Submit to the platform API
|
|
569
|
+
const platformDeps = await resolvePlatformDeps();
|
|
570
|
+
if (!platformDeps) {
|
|
571
|
+
const msg =
|
|
572
|
+
"Not connected to Vellum platform. Run `assistant platform connect` to connect, or use --print to generate the entry without submitting.";
|
|
573
|
+
if (opts.json) {
|
|
574
|
+
process.stdout.write(
|
|
575
|
+
JSON.stringify({
|
|
576
|
+
ok: false,
|
|
577
|
+
error: "not_connected",
|
|
578
|
+
message: msg,
|
|
579
|
+
}) + "\n",
|
|
580
|
+
);
|
|
581
|
+
} else {
|
|
582
|
+
console.error(msg);
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
try {
|
|
588
|
+
const result = await postPublishRequest(payload, platformDeps);
|
|
589
|
+
|
|
590
|
+
if (opts.json) {
|
|
591
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
592
|
+
} else {
|
|
593
|
+
console.log(formatPublishResult(result));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return result.ok;
|
|
597
|
+
} catch (err) {
|
|
598
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
599
|
+
if (opts.json) {
|
|
600
|
+
process.stdout.write(
|
|
601
|
+
JSON.stringify({
|
|
602
|
+
ok: false,
|
|
603
|
+
error: "request_failed",
|
|
604
|
+
message,
|
|
605
|
+
}) + "\n",
|
|
606
|
+
);
|
|
607
|
+
} else {
|
|
608
|
+
console.error(`Publish request failed: ${message}`);
|
|
609
|
+
}
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
}/**
|
|
383
613
|
* Format a publish result for terminal output.
|
|
384
614
|
*/
|
|
385
615
|
export function formatPublishResult(result: PublishResult): string {
|
|
@@ -153,7 +153,6 @@ describe("reconcileFlagGatedProfiles", () => {
|
|
|
153
153
|
const raw = readConfig();
|
|
154
154
|
raw.llm.profiles["os-beta"]!.label = "My OS Beta";
|
|
155
155
|
raw.llm.profiles["os-beta"]!.status = "disabled";
|
|
156
|
-
raw.llm.profiles["os-beta"]!.advisorEnabled = true;
|
|
157
156
|
raw.llm.profiles["os-beta"]!.topP = 0.8;
|
|
158
157
|
writeConfig(raw);
|
|
159
158
|
invalidateConfigCache();
|
|
@@ -163,7 +162,6 @@ describe("reconcileFlagGatedProfiles", () => {
|
|
|
163
162
|
const after = readConfig().llm.profiles["os-beta"]!;
|
|
164
163
|
expect(after.label).toBe("My OS Beta");
|
|
165
164
|
expect(after.status).toBe("disabled");
|
|
166
|
-
expect(after.advisorEnabled).toBe(true);
|
|
167
165
|
expect(after.topP).toBe(0.8);
|
|
168
166
|
expect(after.model).toBe("MiniMaxAI/MiniMax-M3");
|
|
169
167
|
expect(after.provider_connection).toBe("together-managed");
|
|
@@ -324,7 +322,7 @@ describe("reconcileFlagGatedProfiles", () => {
|
|
|
324
322
|
|
|
325
323
|
const raw = readConfig();
|
|
326
324
|
(raw.llm as Record<string, unknown>).callSites = {
|
|
327
|
-
|
|
325
|
+
inference: { profile: "os-beta", temperature: 0.3 },
|
|
328
326
|
};
|
|
329
327
|
writeConfig(raw);
|
|
330
328
|
invalidateConfigCache();
|
|
@@ -333,14 +331,14 @@ describe("reconcileFlagGatedProfiles", () => {
|
|
|
333
331
|
expect(reconcileFlagGatedProfiles()).toBe(true);
|
|
334
332
|
|
|
335
333
|
const after = readConfig();
|
|
336
|
-
const
|
|
334
|
+
const inference = (
|
|
337
335
|
after.llm as unknown as Record<
|
|
338
336
|
string,
|
|
339
337
|
Record<string, Record<string, unknown>>
|
|
340
338
|
>
|
|
341
|
-
).callSites.
|
|
342
|
-
expect(
|
|
343
|
-
expect(
|
|
339
|
+
).callSites.inference;
|
|
340
|
+
expect(inference.profile).toBeUndefined();
|
|
341
|
+
expect(inference.temperature).toBe(0.3);
|
|
344
342
|
|
|
345
343
|
expect(LLMSchema.safeParse(after.llm).success).toBe(true);
|
|
346
344
|
});
|
|
@@ -40,9 +40,24 @@ Each subagent is spawned with a role that determines its tool access. Choose the
|
|
|
40
40
|
| `coder` | `bash`, `file_read`, `file_write`, `file_edit`, `web_search`, `recall`, `notify_parent` | Code changes, file editing, running commands, build/test tasks |
|
|
41
41
|
| `planner` | `file_read`, `file_list`, `web_search`, `web_fetch`, `recall`, `notify_parent` | Analysis, planning, synthesizing information, reviewing approaches |
|
|
42
42
|
| `investigator` | `code_search`, `file_read`, `file_list`, `web_search`, `web_fetch`, `recall`, `notify_parent` | Root-cause analysis: debugging, log forensics, tracing behavior across many files. Read-only search/read tools only (no shell): use `code_search` to grep file contents across directories, `file_list` to enumerate paths, `file_read` to read whole files and logs; returns a compact root-cause report |
|
|
43
|
+
| `advisor` | None (tool-less) | Read-only senior-advisor consult. Runs on a stronger model, inherits full parent context, and BLOCKS until it returns guidance |
|
|
43
44
|
|
|
44
45
|
All specialized roles (`researcher`, `coder`, `planner`) include `notify_parent` for mid-run communication with the parent.
|
|
45
46
|
|
|
47
|
+
## Consulting the Advisor
|
|
48
|
+
|
|
49
|
+
The `advisor` role is the one role you spawn on your own judgment, unprompted — you do NOT wait for the user to ask for a subagent. The other roles (`researcher`, `coder`, `planner`, `investigator`, `general`) stay delegation-driven: reach for them to offload work, typically when the user's request calls for it. The advisor is different: proactively consult it whenever the conditions below are met.
|
|
50
|
+
|
|
51
|
+
Orient yourself first (read the relevant files, understand the task), then consult the advisor:
|
|
52
|
+
|
|
53
|
+
- **Before you commit to an approach and start building** — to shape a plan when you don't have one, or to pressure-test and sharpen a plan you've already drafted.
|
|
54
|
+
- **When you get stuck or are weighing a change in direction.**
|
|
55
|
+
- **Once before you declare the task done.**
|
|
56
|
+
|
|
57
|
+
The consult is synchronous and read-only: spawning an `advisor` subagent BLOCKS until it returns guidance. It runs on a stronger model and inherits your full context, so it sees the task, your tool calls, and their results without you re-explaining. Give its guidance serious weight; only override it when primary-source evidence contradicts a specific claim — and say so when you do.
|
|
58
|
+
|
|
59
|
+
Spawn the advisor **alone** — do NOT batch the consult in the same turn as other tool calls (especially file edits, shell commands, or anything destructive or expensive). Tool calls you issue in the same turn run concurrently with the consult, so they would execute before you see its guidance. Consult the advisor by itself, read its guidance, then act.
|
|
60
|
+
|
|
46
61
|
## Parent Communication
|
|
47
62
|
|
|
48
63
|
Subagents use `notify_parent` to send messages to the parent conversation while still running. Each notification has an urgency level:
|
|
@@ -79,7 +94,7 @@ Set `inference_profile` to an `llm.profiles` key when a subagent should run unde
|
|
|
79
94
|
|
|
80
95
|
Forks are sub-agents that inherit the parent's full context -- messages, system prompt, and memory -- sharing the KV cache for near-free context inheritance. Use forks when the task benefits from knowing what you've been discussing; use a regular sub-agent when the task is self-contained.
|
|
81
96
|
|
|
82
|
-
**Key behaviors:** Forks
|
|
97
|
+
**Key behaviors:** Forks default to `general` role (the `role` parameter is ignored for forks), except the special `advisor` role, which is honored even as a fork. `send_result_to_user` defaults to `false`. Read fork output with `last_n: 1` to get only the final synthesis.
|
|
83
98
|
|
|
84
99
|
**When to fork vs regular sub-agent:**
|
|
85
100
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "subagent_spawn",
|
|
6
|
-
"description": "Spawn an independent subagent to work on a task in parallel. The subagent runs autonomously and its results are reported back when complete.\n\nTwo modes:\n- **Regular sub-agent** (fork: false or omitted): Gets only the objective + context fields. Use for self-contained tasks with clear objectives. Can use scoped roles (researcher, coder, planner).\n- **Fork** (fork: true): Inherits full parent context (messages, system prompt, memory). Shares KV cache for near-free context inheritance. Use when the task benefits from knowing what you've been discussing.
|
|
6
|
+
"description": "Spawn an independent subagent to work on a task in parallel. The subagent runs autonomously and its results are reported back when complete.\n\nTwo modes:\n- **Regular sub-agent** (fork: false or omitted): Gets only the objective + context fields. Use for self-contained tasks with clear objectives. Can use scoped roles (researcher, coder, planner).\n- **Fork** (fork: true): Inherits full parent context (messages, system prompt, memory). Shares KV cache for near-free context inheritance. Use when the task benefits from knowing what you've been discussing. Defaults to general role (the 'advisor' role is the exception — it is honored as a fork). Results are internal by default (send_result_to_user: false). Read with last_n: 1 to get only the final synthesis.\n\nDecision heuristic: Does the task need to know what we've been talking about? Fork. Is it self-contained? Regular sub-agent.\n\nThe 'advisor' role is a synchronous, read-only \"consult a stronger advisor\" mode: it inherits your full context, has no tools, and BLOCKS until it returns focused strategic guidance in a single response.",
|
|
7
7
|
"category": "orchestration",
|
|
8
8
|
"risk": "low",
|
|
9
9
|
"input_schema": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"fork": {
|
|
25
25
|
"type": "boolean",
|
|
26
|
-
"description": "When true, the subagent inherits the parent's full context (messages, system prompt, memory) instead of receiving only the objective + context fields. Fork mode
|
|
26
|
+
"description": "When true, the subagent inherits the parent's full context (messages, system prompt, memory) instead of receiving only the objective + context fields. Fork mode defaults to 'general' role (the role parameter is ignored for forks), except the special 'advisor' role which is honored even as a fork, and defaults send_result_to_user to false. Use for tasks that benefit from the full conversational context."
|
|
27
27
|
},
|
|
28
28
|
"send_result_to_user": {
|
|
29
29
|
"type": "boolean",
|
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
"researcher",
|
|
37
37
|
"coder",
|
|
38
38
|
"planner",
|
|
39
|
-
"investigator"
|
|
39
|
+
"investigator",
|
|
40
|
+
"advisor"
|
|
40
41
|
],
|
|
41
|
-
"description": "Agent specialization that controls tool access. 'researcher': read-only (web, files, memory). 'coder': file and bash access. 'planner': read-only analysis. 'investigator': root-cause analysis — debugging, log forensics, tracing behavior across many files; uses read-only search/read tools (code_search, file_read, file_list) with no shell access and returns a compact root-cause report. 'general': full access (default).
|
|
42
|
+
"description": "Agent specialization that controls tool access. 'researcher': read-only (web, files, memory). 'coder': file and bash access. 'planner': read-only analysis. 'investigator': root-cause analysis — debugging, log forensics, tracing behavior across many files; uses read-only search/read tools (code_search, file_read, file_list) with no shell access and returns a compact root-cause report. 'advisor': synchronous, read-only \"consult a stronger advisor\" — inherits full context, has no tools, and blocks until it returns focused strategic guidance. 'general': full access (default). Non-advisor roles are ignored when fork: true (forks default to general); the advisor role is honored even as a fork."
|
|
42
43
|
},
|
|
43
44
|
"inference_profile": {
|
|
44
45
|
"type": "string",
|
|
@@ -69,12 +69,6 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
|
|
|
69
69
|
meetConsentMonitor: { profile: "cost-optimized" },
|
|
70
70
|
meetChatOpportunity: { profile: "cost-optimized" },
|
|
71
71
|
inference: { profile: "cost-optimized" },
|
|
72
|
-
// The advisor consults the strongest managed profile (`frontier`, Opus),
|
|
73
|
-
// which seeding writes into `llm.advisorProfile` on boot and floats above this
|
|
74
|
-
// layer. This static fallback — used only when no `advisorProfile` resolves —
|
|
75
|
-
// stays on the always-reserved `quality-optimized` so it can never resolve to
|
|
76
|
-
// a user-owned profile that happens to be named `frontier`.
|
|
77
|
-
advisor: { profile: "quality-optimized" },
|
|
78
72
|
// Vision captioning for the image-fallback plugin. No pinned profile — the
|
|
79
73
|
// plugin resolves a vision-capable profile itself via `doesSupportVision` and
|
|
80
74
|
// passes it as an `overrideProfile`, so the call-site default is a fallback
|
|
@@ -497,9 +497,6 @@ function profileConfigFragment(profile: ProfileEntry): Mergeable {
|
|
|
497
497
|
// lower-precedence (e.g. active) profile into one that merely inherited it.
|
|
498
498
|
// `RetryProvider` resolves it from the applied profile, not the merge.
|
|
499
499
|
logitBias: _logitBias,
|
|
500
|
-
// Per-profile advisor toggle is profile identity, not inheritable model
|
|
501
|
-
// config — strip it so it can't leak into the merged `LLMConfigBase`.
|
|
502
|
-
advisorEnabled: _advisorEnabled,
|
|
503
500
|
// `temperature`/`top_p` are provider-coupled: only the winning profile
|
|
504
501
|
// contributes them (tracked via `samplingRef`, applied post-merge), so a
|
|
505
502
|
// shadowed profile's sampling can never reach a different provider through
|
|
@@ -300,13 +300,6 @@ const CATALOG_RECORD: CatalogRecord = {
|
|
|
300
300
|
description: "General-purpose LLM inference call site for skill use.",
|
|
301
301
|
domain: "skills",
|
|
302
302
|
},
|
|
303
|
-
advisor: {
|
|
304
|
-
id: "advisor",
|
|
305
|
-
displayName: "Advisor",
|
|
306
|
-
description:
|
|
307
|
-
"Stronger reviewer model consulted mid-task to shape or pressure-test the plan.",
|
|
308
|
-
domain: "skills",
|
|
309
|
-
},
|
|
310
303
|
vision: {
|
|
311
304
|
id: "vision",
|
|
312
305
|
displayName: "Vision",
|
|
@@ -5,10 +5,7 @@ export const HeartbeatConfigSchema = z
|
|
|
5
5
|
.object({
|
|
6
6
|
enabled: z
|
|
7
7
|
.boolean({ error: "heartbeat.enabled must be a boolean" })
|
|
8
|
-
|
|
9
|
-
// the default was true keep their behavior via workspace migration
|
|
10
|
-
// 102-preserve-heartbeat-enabled-for-existing-workspaces.
|
|
11
|
-
.default(false)
|
|
8
|
+
.default(true)
|
|
12
9
|
.describe("Whether periodic heartbeat checks are enabled"),
|
|
13
10
|
intervalMs: z
|
|
14
11
|
.number({ error: "heartbeat.intervalMs must be a number" })
|
|
@@ -64,7 +61,7 @@ export const HeartbeatConfigSchema = z
|
|
|
64
61
|
.int("heartbeat.maxDailyRuns must be an integer")
|
|
65
62
|
.positive("heartbeat.maxDailyRuns must be a positive integer")
|
|
66
63
|
.nullable()
|
|
67
|
-
.default(
|
|
64
|
+
.default(10)
|
|
68
65
|
.describe(
|
|
69
66
|
"Maximum heartbeats that can run per calendar day. Resets at midnight local time. Set to null for unlimited.",
|
|
70
67
|
),
|
|
@@ -79,7 +79,6 @@ export const LLMCallSiteEnum = z.enum([
|
|
|
79
79
|
"meetConsentMonitor",
|
|
80
80
|
"meetChatOpportunity",
|
|
81
81
|
"inference",
|
|
82
|
-
"advisor",
|
|
83
82
|
"vision",
|
|
84
83
|
"trustRuleSuggestion",
|
|
85
84
|
"homeGreeting",
|
|
@@ -442,13 +441,6 @@ export const ProfileEntry = LLMConfigFragment.extend({
|
|
|
442
441
|
* #30362 even though the schema didn't accept it until now.
|
|
443
442
|
*/
|
|
444
443
|
status: ProfileStatusSchema.nullable().optional(),
|
|
445
|
-
/**
|
|
446
|
-
* Whether the advisor is active while this profile is the chat profile.
|
|
447
|
-
* Absent/null means enabled (default on); only an explicit `false` disables
|
|
448
|
-
* it. `.nullable()` matches `status`/`label` so the PUT route's "send null
|
|
449
|
-
* to clear" sentinel resets it back to the default-on state.
|
|
450
|
-
*/
|
|
451
|
-
advisorEnabled: z.boolean().nullable().optional(),
|
|
452
444
|
/**
|
|
453
445
|
* When present, this profile is a "mix": it carries no model config and
|
|
454
446
|
* instead references a weighted list of standard profiles. The resolver
|
|
@@ -492,10 +484,9 @@ export const LLMSchema = z
|
|
|
492
484
|
// schema level, so `LLMSchema.parse({})` yields an empty map.
|
|
493
485
|
callSites: z.partialRecord(LLMCallSiteEnum, LLMCallSiteConfig).default({}),
|
|
494
486
|
activeProfile: z.string().min(1).optional(),
|
|
495
|
-
// The profile the advisor consults
|
|
496
|
-
// excluded from the chat-profile pickers so
|
|
497
|
-
// assistant's chat model.
|
|
498
|
-
// default (`quality-optimized`).
|
|
487
|
+
// The profile the advisor role consults when spawned as a subagent (chosen
|
|
488
|
+
// under Models & Services). It is excluded from the chat-profile pickers so
|
|
489
|
+
// it can't be selected as the assistant's chat model.
|
|
499
490
|
advisorProfile: z.string().min(1).optional(),
|
|
500
491
|
// TTL bounds for inference profile sessions. `defaultTtlSeconds` is read by
|
|
501
492
|
// the CLI to apply when `--ttl` is omitted; the daemon handler itself only
|
|
@@ -84,7 +84,7 @@ export const MemoryWorkerConfigSchema = z
|
|
|
84
84
|
.boolean({ error: "memory.worker.enabled must be a boolean" })
|
|
85
85
|
.default(false)
|
|
86
86
|
.describe(
|
|
87
|
-
"Whether the memory jobs worker runs as a separate OS process
|
|
87
|
+
"Whether the memory jobs worker runs as a separate OS process instead of the assistant's synchronous in-process runner. The assistant's worker supervisor re-reads this flag on every poll: while it is set, the in-process runner stands down (the out-of-process worker, spawned at startup when set, owns the queue); while it is unset, the in-process runner drains the queue. `assistant memory worker start`/`stop` flip the flag (and spawn/stop the worker process) to switch modes at runtime without a restart.",
|
|
88
88
|
),
|
|
89
89
|
})
|
|
90
90
|
.describe("Memory jobs worker process configuration");
|
|
@@ -84,9 +84,6 @@ const MANAGED_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
|
84
84
|
effort: "high",
|
|
85
85
|
thinking: { enabled: true, streamThinking: true },
|
|
86
86
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
87
|
-
// This is the advisor's own (strongest) profile: when it's also the chat
|
|
88
|
-
// profile there's nothing stronger to consult, so the advisor defaults off.
|
|
89
|
-
advisorEnabled: false,
|
|
90
87
|
},
|
|
91
88
|
// Served by DeepSeek V4 Flash on Fireworks via managed platform inference: a
|
|
92
89
|
// fast, low-cost open model. `model` is pinned explicitly rather than
|
|
@@ -224,11 +221,9 @@ export type SeedInferenceProfilesOptions = {
|
|
|
224
221
|
* `cost-optimized`): reconciled from the code templates on every boot —
|
|
225
222
|
* on-platform and off-platform alike — so Vellum can push model/config
|
|
226
223
|
* updates to customers in a release without a workspace migration. The
|
|
227
|
-
* templates own all profile content; `label`, `status`, `
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* allowlist; `advisorEnabled` is edited via the generic config path but is
|
|
231
|
-
* still preserved here so it survives reboots.
|
|
224
|
+
* templates own all profile content; `label`, `status`, and `topP` are user
|
|
225
|
+
* overrides that survive reseeds and are editable through the managed PUT
|
|
226
|
+
* route allowlist.
|
|
232
227
|
* Platform overlays (`preserveProfileNames`) take precedence for the boot
|
|
233
228
|
* they are supplied.
|
|
234
229
|
*
|
|
@@ -283,14 +278,11 @@ export function seedInferenceProfiles(
|
|
|
283
278
|
// as usual.
|
|
284
279
|
//
|
|
285
280
|
// A whitelist of user-editable fields survives the reconcile: `label`
|
|
286
|
-
// (display rename), `status` (active/disabled toggle), `
|
|
287
|
-
// (
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
// `MANAGED_PROFILE_EDITABLE_KEYS`, deliberately excludes `advisorEnabled`,
|
|
292
|
-
// which is set through the generic config path). We honor every one of
|
|
293
|
-
// these overrides across reseeds or they'd silently revert on every boot.
|
|
281
|
+
// (display rename), `status` (active/disabled toggle), and `topP`
|
|
282
|
+
// (sampling override) — the only fields a user may override. The managed
|
|
283
|
+
// PUT route `/v1/config/llm/profiles/:name` lets users patch these on
|
|
284
|
+
// managed profiles without duplicating. We honor every one of these
|
|
285
|
+
// overrides across reseeds or they'd silently revert on every boot.
|
|
294
286
|
// Carry by key-presence rather than truthiness so an explicit `null` (user
|
|
295
287
|
// cleared the field) survives too.
|
|
296
288
|
//
|
|
@@ -362,12 +354,6 @@ export function seedInferenceProfiles(
|
|
|
362
354
|
: previous.label;
|
|
363
355
|
}
|
|
364
356
|
if ("status" in previous) next.status = previous.status;
|
|
365
|
-
// The per-profile advisor toggle is a user override — preserve it across
|
|
366
|
-
// reseeds so a user's choice survives reboots (the template value only
|
|
367
|
-
// seeds the initial default, e.g. off for quality-optimized).
|
|
368
|
-
if ("advisorEnabled" in previous) {
|
|
369
|
-
next.advisorEnabled = previous.advisorEnabled;
|
|
370
|
-
}
|
|
371
357
|
// `topP` is user-editable on managed profiles (see the managed-profile
|
|
372
358
|
// editable allowlist on the PUT route) — preserve a user override across
|
|
373
359
|
// reseeds, including an explicit `null` clear, or it would silently revert
|
|
@@ -440,19 +426,32 @@ export function seedInferenceProfiles(
|
|
|
440
426
|
}
|
|
441
427
|
}
|
|
442
428
|
|
|
443
|
-
// Advisor profile: default to the strongest
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
429
|
+
// Advisor profile: BYOK hatches default to the strongest personal profile
|
|
430
|
+
// backed by the entered provider key. Managed-profile hatches and registered
|
|
431
|
+
// platform installs default to the strongest active managed profile.
|
|
432
|
+
const requestedAdvisorProfile = readString(llm.advisorProfile);
|
|
433
|
+
const requestedAdvisorEntry =
|
|
434
|
+
requestedAdvisorProfile !== undefined
|
|
435
|
+
? readObject(profiles[requestedAdvisorProfile])
|
|
436
|
+
: null;
|
|
437
|
+
const requestedAdvisorIsDisabledManaged =
|
|
438
|
+
requestedAdvisorEntry?.source === "managed" &&
|
|
439
|
+
requestedAdvisorEntry.status === "disabled";
|
|
440
|
+
const preferPersonalAdvisor =
|
|
441
|
+
userConnectionName !== undefined &&
|
|
442
|
+
hatchSelectedManagedConnection === undefined;
|
|
443
|
+
if (
|
|
444
|
+
requestedAdvisorProfile === undefined ||
|
|
445
|
+
requestedAdvisorIsDisabledManaged
|
|
446
|
+
) {
|
|
447
|
+
const defaultAdvisorProfile = selectDefaultAdvisorProfile(
|
|
448
|
+
profiles,
|
|
449
|
+
preferPersonalAdvisor,
|
|
450
|
+
);
|
|
451
|
+
if (defaultAdvisorProfile) {
|
|
452
|
+
llm.advisorProfile = defaultAdvisorProfile;
|
|
453
|
+
} else if (requestedAdvisorIsDisabledManaged) {
|
|
454
|
+
delete llm.advisorProfile;
|
|
456
455
|
}
|
|
457
456
|
}
|
|
458
457
|
|
|
@@ -592,6 +591,48 @@ function readString(value: unknown): string | undefined {
|
|
|
592
591
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
593
592
|
}
|
|
594
593
|
|
|
594
|
+
function selectDefaultAdvisorProfile(
|
|
595
|
+
profiles: Record<string, Record<string, unknown>>,
|
|
596
|
+
preferPersonalProfile: boolean,
|
|
597
|
+
): string | undefined {
|
|
598
|
+
const personal = firstActiveProfile(profiles, [
|
|
599
|
+
"custom-quality-optimized",
|
|
600
|
+
"custom-balanced",
|
|
601
|
+
"custom-cost-optimized",
|
|
602
|
+
]);
|
|
603
|
+
const managed = firstActiveManagedProfile(profiles, [
|
|
604
|
+
"frontier",
|
|
605
|
+
"quality-optimized",
|
|
606
|
+
"balanced",
|
|
607
|
+
"cost-optimized",
|
|
608
|
+
]);
|
|
609
|
+
return preferPersonalProfile ? (personal ?? managed) : (managed ?? personal);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function firstActiveProfile(
|
|
613
|
+
profiles: Record<string, Record<string, unknown>>,
|
|
614
|
+
names: string[],
|
|
615
|
+
): string | undefined {
|
|
616
|
+
for (const name of names) {
|
|
617
|
+
const profile = readObject(profiles[name]);
|
|
618
|
+
if (profile && profile.status !== "disabled") return name;
|
|
619
|
+
}
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function firstActiveManagedProfile(
|
|
624
|
+
profiles: Record<string, Record<string, unknown>>,
|
|
625
|
+
names: string[],
|
|
626
|
+
): string | undefined {
|
|
627
|
+
for (const name of names) {
|
|
628
|
+
const profile = readObject(profiles[name]);
|
|
629
|
+
if (profile?.source === "managed" && profile.status !== "disabled") {
|
|
630
|
+
return name;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
|
|
595
636
|
function getHatchSelectedManagedConnection(
|
|
596
637
|
llm: Record<string, unknown>,
|
|
597
638
|
profiles: Record<string, Record<string, unknown>>,
|
|
@@ -117,9 +117,6 @@ function enableProfile(
|
|
|
117
117
|
// Preserve user-owned overrides across reconciles.
|
|
118
118
|
if ("label" in previous) next.label = previous.label;
|
|
119
119
|
if ("status" in previous) next.status = previous.status;
|
|
120
|
-
if ("advisorEnabled" in previous) {
|
|
121
|
-
next.advisorEnabled = previous.advisorEnabled;
|
|
122
|
-
}
|
|
123
120
|
if ("topP" in previous) next.topP = previous.topP;
|
|
124
121
|
}
|
|
125
122
|
|