@vellumai/assistant 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -1
  3. package/docker-init-apt-root.sh +79 -6
  4. package/openapi.yaml +336 -21
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  7. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  8. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  9. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  10. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  11. package/src/__tests__/context-token-estimator.test.ts +30 -65
  12. package/src/__tests__/conversation-agent-loop.test.ts +57 -1
  13. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  14. package/src/__tests__/conversation-runtime-assembly.test.ts +26 -4
  15. package/src/__tests__/date-context.test.ts +45 -0
  16. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  17. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  18. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  19. package/src/__tests__/heartbeat-service.test.ts +24 -164
  20. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  21. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  22. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  23. package/src/__tests__/injector-background-turn.test.ts +153 -0
  24. package/src/__tests__/injector-chain.test.ts +5 -0
  25. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  26. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  27. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  28. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  29. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  30. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  31. package/src/__tests__/llm-resolver.test.ts +255 -2
  32. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  33. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  34. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  35. package/src/__tests__/notification-deep-link.test.ts +15 -0
  36. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  37. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  38. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  39. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  40. package/src/__tests__/openai-provider.test.ts +218 -3
  41. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  42. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  43. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  44. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  45. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  46. package/src/__tests__/plugin-types.test.ts +2 -2
  47. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  48. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  49. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  50. package/src/__tests__/system-prompt.test.ts +6 -73
  51. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  52. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  53. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  54. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  55. package/src/a2a/__tests__/task-store.test.ts +246 -0
  56. package/src/a2a/agent-card.ts +58 -0
  57. package/src/a2a/feature-gate.ts +8 -0
  58. package/src/a2a/protocol-constants.ts +21 -0
  59. package/src/a2a/protocol-errors.ts +50 -0
  60. package/src/a2a/protocol-types.ts +162 -0
  61. package/src/a2a/task-store.ts +168 -0
  62. package/src/agent/loop.ts +167 -18
  63. package/src/channels/config.ts +9 -0
  64. package/src/channels/types.ts +14 -0
  65. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  66. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  67. package/src/cli/commands/notifications.ts +65 -35
  68. package/src/cli/commands/plugins.ts +67 -0
  69. package/src/cli/commands/schedules.ts +297 -5
  70. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  71. package/src/cli/lib/install-from-github.ts +8 -9
  72. package/src/cli/lib/search-plugins.ts +163 -0
  73. package/src/cli/program.ts +14 -0
  74. package/src/config/assistant-feature-flags.ts +24 -54
  75. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  76. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  77. package/src/config/call-site-defaults.ts +105 -0
  78. package/src/config/feature-flag-registry.json +21 -29
  79. package/src/config/llm-resolver.ts +52 -1
  80. package/src/config/schema.ts +2 -0
  81. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  82. package/src/config/schemas/channels.ts +9 -0
  83. package/src/config/schemas/conversations.ts +10 -0
  84. package/src/config/schemas/heartbeat.ts +14 -0
  85. package/src/config/schemas/llm.ts +1 -3
  86. package/src/config/schemas/memory-retrospective.ts +1 -1
  87. package/src/config/schemas/memory-v2.ts +4 -4
  88. package/src/config/schemas/memory.ts +3 -1
  89. package/src/config/seed-inference-profiles.ts +99 -29
  90. package/src/context/compactor.ts +72 -12
  91. package/src/context/token-estimator.ts +32 -34
  92. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  93. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  94. package/src/daemon/conversation-agent-loop.ts +29 -2
  95. package/src/daemon/conversation-runtime-assembly.ts +9 -0
  96. package/src/daemon/conversation.ts +0 -7
  97. package/src/daemon/date-context.ts +40 -0
  98. package/src/daemon/guardian-action-generators.ts +1 -125
  99. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  100. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  101. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  102. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  103. package/src/daemon/handlers/config-a2a.ts +289 -0
  104. package/src/daemon/handlers/conversations.ts +1 -0
  105. package/src/daemon/host-app-control-proxy.ts +69 -18
  106. package/src/daemon/host-proxy-preactivation.ts +85 -18
  107. package/src/daemon/lifecycle.ts +49 -61
  108. package/src/daemon/memory-v2-startup.ts +49 -13
  109. package/src/daemon/message-types/notifications.ts +21 -0
  110. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  111. package/src/daemon/pkb-reminder-builder.ts +4 -19
  112. package/src/daemon/process-message.ts +3 -0
  113. package/src/daemon/skill-memory-refresh.ts +5 -1
  114. package/src/daemon/wake-target-adapter.ts +2 -0
  115. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  116. package/src/export/transcript-formatter.ts +54 -20
  117. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -0
  118. package/src/heartbeat/heartbeat-service.ts +34 -191
  119. package/src/home/__tests__/feed-types.test.ts +40 -0
  120. package/src/home/feed-types.ts +14 -2
  121. package/src/ipc/cli-client.ts +147 -45
  122. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  123. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  124. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  125. package/src/memory/conversation-queries.ts +87 -1
  126. package/src/memory/conversation-title-service.ts +26 -4
  127. package/src/memory/db-init.ts +6 -0
  128. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  129. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  130. package/src/memory/graph/tools.ts +6 -37
  131. package/src/memory/invite-store.ts +53 -0
  132. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  133. package/src/memory/llm-request-log-store.ts +92 -1
  134. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  135. package/src/memory/memory-retrospective-job.ts +33 -6
  136. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  137. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  138. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  139. package/src/memory/migrations/index.ts +3 -0
  140. package/src/memory/migrations/registry.ts +8 -0
  141. package/src/memory/schema/a2a.ts +15 -0
  142. package/src/memory/schema/index.ts +1 -0
  143. package/src/memory/schema/inference.ts +2 -0
  144. package/src/memory/schema/infrastructure.ts +1 -0
  145. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  146. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  147. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  148. package/src/memory/v2/__tests__/injection.test.ts +190 -3
  149. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  150. package/src/memory/v2/activation-store.ts +14 -16
  151. package/src/memory/v2/cli-command-content.ts +19 -0
  152. package/src/memory/v2/cli-command-store.ts +304 -0
  153. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  154. package/src/memory/v2/injection.ts +49 -20
  155. package/src/memory/v2/page-index.ts +38 -13
  156. package/src/memory/v2/static-context.ts +4 -4
  157. package/src/memory/v2/types.ts +23 -0
  158. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  159. package/src/messaging/providers/a2a/deliver.ts +156 -0
  160. package/src/messaging/providers/gmail/client.ts +9 -2
  161. package/src/messaging/providers/index.ts +11 -2
  162. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  163. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  164. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  165. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  166. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  167. package/src/notifications/adapters/macos.ts +12 -2
  168. package/src/notifications/broadcaster.ts +29 -4
  169. package/src/notifications/copy-composer.ts +17 -64
  170. package/src/notifications/decision-engine.ts +111 -44
  171. package/src/notifications/deterministic-checks.ts +96 -0
  172. package/src/notifications/emit-signal.ts +1 -0
  173. package/src/notifications/home-feed-side-effect.ts +85 -6
  174. package/src/notifications/signal.ts +0 -4
  175. package/src/notifications/types.ts +8 -0
  176. package/src/oauth/platform-connection.test.ts +43 -3
  177. package/src/oauth/platform-connection.ts +13 -4
  178. package/src/plugins/defaults/injectors.ts +38 -19
  179. package/src/plugins/external-plugin-loader.ts +82 -10
  180. package/src/plugins/types.ts +16 -7
  181. package/src/prompts/__tests__/system-prompt.test.ts +6 -51
  182. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  183. package/src/prompts/system-prompt.ts +0 -8
  184. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  185. package/src/prompts/templates/system-sections.ts +0 -9
  186. package/src/providers/__tests__/inference.test.ts +2 -0
  187. package/src/providers/call-site-routing.ts +24 -6
  188. package/src/providers/connection-resolution.ts +63 -13
  189. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  190. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  191. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  192. package/src/providers/inference/adapter-factory.ts +9 -20
  193. package/src/providers/inference/auth.ts +12 -0
  194. package/src/providers/inference/backfill.ts +14 -1
  195. package/src/providers/inference/connections.ts +85 -5
  196. package/src/providers/inference/resolve-auth.ts +2 -0
  197. package/src/providers/model-catalog.ts +199 -244
  198. package/src/providers/model-intents.ts +3 -3
  199. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  200. package/src/providers/openai/chat-completions-provider.ts +159 -6
  201. package/src/providers/openrouter/client.ts +42 -4
  202. package/src/providers/platform-proxy/constants.ts +3 -4
  203. package/src/providers/provider-catalog-visibility.ts +3 -1
  204. package/src/providers/provider-send-message.ts +27 -12
  205. package/src/providers/registry.ts +30 -1
  206. package/src/runtime/agent-wake.ts +61 -1
  207. package/src/runtime/auth/route-policy.ts +13 -0
  208. package/src/runtime/http-server.ts +7 -16
  209. package/src/runtime/http-types.ts +0 -47
  210. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  211. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +66 -4
  212. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  213. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  214. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  215. package/src/runtime/routes/consolidation-routes.ts +100 -0
  216. package/src/runtime/routes/conversation-query-routes.ts +70 -11
  217. package/src/runtime/routes/conversation-routes.ts +7 -0
  218. package/src/runtime/routes/index.ts +2 -0
  219. package/src/runtime/routes/inference-provider-connection-routes.ts +134 -1
  220. package/src/runtime/routes/integrations/a2a.ts +235 -0
  221. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  222. package/src/runtime/routes/subagents-routes.ts +41 -0
  223. package/src/subagent/manager.ts +2 -0
  224. package/src/tools/memory/register.ts +1 -9
  225. package/src/tools/registry.ts +2 -2
  226. package/src/tools/types.ts +37 -2
  227. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  228. package/src/workspace/migrations/registry.ts +2 -0
  229. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  230. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  231. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -9,14 +9,6 @@
9
9
  "description": "Automatically trigger conversation analysis on the same cadence as memory extraction (batch threshold, idle debounce, end-of-conversation). The analysis agent has full tool access and writes back to memory and skills without user approval.",
10
10
  "defaultEnabled": false
11
11
  },
12
- {
13
- "id": "memory-retrospective",
14
- "scope": "assistant",
15
- "key": "memory-retrospective",
16
- "label": "Memory retrospective pass",
17
- "description": "Run a focused, memory-only retrospective during active conversations and at conversation lifecycle. The retrospective agent re-reads the messages added since the last successful run, dedupes against memory/archive/, and calls `remember` for what wasn't captured in the moment. Enabling this also relaxes the in-conversation pressure to call `remember` (lighter PKB reminder + relaxed tool description) so the assistant can stay present and trust the retrospective as the backstop.",
18
- "defaultEnabled": false
19
- },
20
12
  {
21
13
  "id": "user-hosted-enabled",
22
14
  "scope": "client",
@@ -33,6 +25,14 @@
33
25
  "description": "When enabled, the Local hosting option uses Docker under the hood for sandboxed execution, hiding the separate Docker card",
34
26
  "defaultEnabled": false
35
27
  },
28
+ {
29
+ "id": "a2a-channel",
30
+ "scope": "assistant",
31
+ "key": "a2a-channel",
32
+ "label": "A2A Channel",
33
+ "description": "Enable the A2A (Agent-to-Agent) channel for inter-assistant communication via the open A2A protocol",
34
+ "defaultEnabled": false
35
+ },
36
36
  {
37
37
  "id": "email-channel",
38
38
  "scope": "assistant",
@@ -145,6 +145,14 @@
145
145
  "description": "Show a speaker button on assistant messages to generate and play the message as audio via Fish Audio TTS",
146
146
  "defaultEnabled": false
147
147
  },
148
+ {
149
+ "id": "openai-compatible-endpoints",
150
+ "scope": "assistant",
151
+ "key": "openai-compatible-endpoints",
152
+ "label": "OpenAI-Compatible Endpoints",
153
+ "description": "Enable user-configured OpenAI-compatible inference endpoints with custom base URLs and model identifiers",
154
+ "defaultEnabled": false
155
+ },
148
156
  {
149
157
  "id": "backward-releases",
150
158
  "scope": "assistant",
@@ -281,14 +289,6 @@
281
289
  "description": "Enable the app-control skill (per-app screenshot + raw input bypassing AX tree)",
282
290
  "defaultEnabled": false
283
291
  },
284
- {
285
- "id": "species-migration",
286
- "scope": "assistant",
287
- "key": "species-migration",
288
- "label": "Species Migration",
289
- "description": "Enable the Species Migration skill for migrating from OpenClaw, Hermes, Manus, and other assistant species into Vellum.",
290
- "defaultEnabled": false
291
- },
292
292
  {
293
293
  "id": "analyze-conversation",
294
294
  "scope": "assistant",
@@ -322,19 +322,11 @@
322
322
  "defaultEnabled": false
323
323
  },
324
324
  {
325
- "id": "provider-deepseek",
326
- "scope": "assistant",
327
- "key": "provider-deepseek",
328
- "label": "DeepSeek Provider",
329
- "description": "Enable the DeepSeek direct API provider and its models (V4 Pro, V4 Flash) in the provider picker and model selection UI",
330
- "defaultEnabled": false
331
- },
332
- {
333
- "id": "provider-minimax",
334
- "scope": "assistant",
335
- "key": "provider-minimax",
336
- "label": "MiniMax Provider",
337
- "description": "Enable the MiniMax direct API provider and its models (M2.7, M2.5, M2.1, M2, and highspeed variants) in the provider picker and model selection UI",
325
+ "id": "velvet-theme",
326
+ "scope": "client",
327
+ "key": "velvet-theme",
328
+ "label": "Velvet Theme",
329
+ "description": "Show the Velvet theme option in the macOS appearance settings. Velvet is a dark-mode variant with red/pink accent colors.",
338
330
  "defaultEnabled": false
339
331
  }
340
332
  ]
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { getCatalogProviderForModel } from "../providers/model-catalog.js";
4
+ import { CALL_SITE_DEFAULTS } from "./call-site-defaults.js";
4
5
  import {
5
6
  type LLMCallSite,
6
7
  LLMConfigBase,
@@ -56,7 +57,9 @@ export function resolveCallSiteConfig(
56
57
  opts.overrideProfile != null
57
58
  ? llm.profiles?.[opts.overrideProfile]
58
59
  : undefined;
59
- const site = llm.callSites?.[callSite];
60
+ const site =
61
+ llm.callSites?.[callSite] ??
62
+ effectiveDefault(callSite, llm, opts.overrideProfile != null);
60
63
 
61
64
  if (callSite === "mainAgent") {
62
65
  appendCallSiteLayers(layers, callSite, llm, site);
@@ -77,6 +80,54 @@ export function resolveCallSiteConfig(
77
80
 
78
81
  type Mergeable = Record<string, unknown>;
79
82
 
83
+ /**
84
+ * Returns the resolved default profile key for a call site, accounting for
85
+ * the `custom-*` user-profile fallback when the managed profile is unavailable.
86
+ */
87
+ export function resolveDefaultProfileKey(
88
+ callSite: LLMCallSite,
89
+ llm: z.infer<typeof LLMSchema>,
90
+ ): string | undefined {
91
+ const dflt = CALL_SITE_DEFAULTS[callSite];
92
+ if (dflt?.profile == null) return undefined;
93
+ const target = llm.profiles?.[dflt.profile];
94
+ if (target != null && target.status !== "disabled") return dflt.profile;
95
+ const customKey = `custom-${dflt.profile}`;
96
+ const customTarget = llm.profiles?.[customKey];
97
+ if (customTarget != null && customTarget.status !== "disabled")
98
+ return customKey;
99
+ return undefined;
100
+ }
101
+
102
+ function effectiveDefault(
103
+ callSite: LLMCallSite,
104
+ llm: z.infer<typeof LLMSchema>,
105
+ hasOverrideProfile = false,
106
+ ): z.infer<typeof LLMSchema>["callSites"][LLMCallSite] | undefined {
107
+ const dflt = CALL_SITE_DEFAULTS[callSite];
108
+ if (dflt == null) return undefined;
109
+ const targetProfile =
110
+ dflt.profile != null ? llm.profiles?.[dflt.profile] : undefined;
111
+ const profileUnavailable =
112
+ dflt.profile != null &&
113
+ (targetProfile == null || targetProfile.status === "disabled");
114
+
115
+ if (profileUnavailable && !hasOverrideProfile) {
116
+ const customKey = `custom-${dflt.profile}`;
117
+ const customProfile = llm.profiles?.[customKey];
118
+ if (customProfile != null && customProfile.status !== "disabled") {
119
+ return { ...dflt, profile: customKey };
120
+ }
121
+ }
122
+
123
+ const stripProfile = hasOverrideProfile || profileUnavailable;
124
+ if (stripProfile) {
125
+ const { profile: _profile, ...rest } = dflt;
126
+ return Object.keys(rest).length > 0 ? rest : undefined;
127
+ }
128
+ return dflt;
129
+ }
130
+
80
131
  function withImpliedProviderForKnownModel(source: Mergeable): Mergeable {
81
132
  if (source.provider !== undefined) return source;
82
133
  const model = source.model;
@@ -20,6 +20,7 @@ import { AnalysisConfigSchema } from "./schemas/analysis.js";
20
20
  import { BackupConfigSchema } from "./schemas/backup.js";
21
21
  import { CallsConfigSchema } from "./schemas/calls.js";
22
22
  import {
23
+ A2AConfigSchema,
23
24
  SlackConfigSchema,
24
25
  TelegramConfigSchema,
25
26
  TwilioConfigSchema,
@@ -111,6 +112,7 @@ export const AssistantConfigSchema = z
111
112
  whatsapp: WhatsAppConfigSchema.default(WhatsAppConfigSchema.parse({})),
112
113
  telegram: TelegramConfigSchema.default(TelegramConfigSchema.parse({})),
113
114
  slack: SlackConfigSchema.default(SlackConfigSchema.parse({})),
115
+ a2a: A2AConfigSchema.default(A2AConfigSchema.parse({})),
114
116
  ingress: IngressConfigSchema,
115
117
  platform: PlatformConfigSchema.default(PlatformConfigSchema.parse({})),
116
118
  daemon: DaemonConfigSchema.default(DaemonConfigSchema.parse({})),
@@ -33,7 +33,7 @@ describe("MemoryV2ConfigSchema", () => {
33
33
  dtype: "q8",
34
34
  },
35
35
  router: {
36
- enabled: false,
36
+ enabled: true,
37
37
  max_page_ids: 25,
38
38
  router_prompt_path: null,
39
39
  },
@@ -161,9 +161,9 @@ describe("MemoryV2ConfigSchema", () => {
161
161
  expect(() => MemoryV2ConfigSchema.parse({ epsilon: 1.5 })).toThrow();
162
162
  });
163
163
 
164
- test("router defaults to disabled with max_page_ids=25", () => {
164
+ test("router defaults to enabled with max_page_ids=25", () => {
165
165
  const parsed = MemoryV2ConfigSchema.parse({});
166
- expect(parsed.router.enabled).toBe(false);
166
+ expect(parsed.router.enabled).toBe(true);
167
167
  expect(parsed.router.max_page_ids).toBe(25);
168
168
  });
169
169
 
@@ -99,6 +99,15 @@ export const TelegramConfigSchema = z
99
99
  })
100
100
  .describe("Telegram bot channel configuration");
101
101
 
102
+ export const A2AConfigSchema = z
103
+ .object({
104
+ enabled: z
105
+ .boolean({ error: "a2a.enabled must be a boolean" })
106
+ .default(false)
107
+ .describe("Whether the A2A channel is enabled"),
108
+ })
109
+ .describe("Agent-to-Agent protocol channel configuration");
110
+
102
111
  export const SlackConfigSchema = z
103
112
  .object({
104
113
  deliverAuthBypass: z
@@ -10,6 +10,16 @@ export const ConversationsConfigSchema = z
10
10
  .describe(
11
11
  "When true, skip the second-pass title regeneration that fires after the third user turn. The initial auto-generated title and manual renames are unaffected.",
12
12
  ),
13
+ backgroundInjection: z
14
+ .string({
15
+ error: "conversations.backgroundInjection must be a string",
16
+ })
17
+ .default(
18
+ "This is a background turn — your guardian isn't watching. If anything noteworthy comes up, send them a notification so they see it when they're back by invoking the `notifications` skill (`assistant notifications send --message \"...\"`)",
19
+ )
20
+ .describe(
21
+ "Inner text injected into the tail user message of non-interactive turns in background/scheduled conversations. The injector wraps this in <background_turn>...</background_turn> tags. Empty string disables the injection.",
22
+ ),
13
23
  })
14
24
  .describe("Conversation behavior configuration");
15
25
 
@@ -56,6 +56,20 @@ export const HeartbeatConfigSchema = z
56
56
  .describe(
57
57
  "Maximum heartbeats that can run consecutively without a guardian message. Counter resets when the guardian sends a message. Set to null for unlimited.",
58
58
  ),
59
+ disposition: z
60
+ .string({ error: "heartbeat.disposition must be a string" })
61
+ .default(
62
+ `This is your time to do something useful, interesting, or creative while your guardian is away.
63
+
64
+ Before checking on anything, ask yourself: is there something I want to work on, think about, or make progress on right now? A project, an idea, something I noticed, something I've been meaning to get to. If so, do it.
65
+
66
+ If you do something worth sharing — built something, noticed something, had an idea — send your guardian a notification so they see it when they're back.
67
+
68
+ If nothing needs attention and nothing stirs, that's fine. But make sure you actually considered it first rather than defaulting to "nothing to do."`,
69
+ )
70
+ .describe(
71
+ "Inner text injected into the heartbeat prompt. The service wraps this in <heartbeat-disposition>...</heartbeat-disposition> tags. Empty string disables the block.",
72
+ ),
59
73
  })
60
74
  .describe("Periodic heartbeat configuration for health monitoring")
61
75
  .superRefine((config, ctx) => {
@@ -20,9 +20,7 @@ const LLMProvider = z.enum([
20
20
  "ollama",
21
21
  "fireworks",
22
22
  "openrouter",
23
- "zai",
24
- "deepseek",
25
- "minimax",
23
+ "openai-compatible",
26
24
  ]);
27
25
  type LLMProvider = z.infer<typeof LLMProvider>;
28
26
 
@@ -40,7 +40,7 @@ export const MemoryRetrospectiveConfigSchema = z
40
40
  ),
41
41
  })
42
42
  .describe(
43
- "Controls the memory-retrospective background pass triggered by the `memory-retrospective` feature flag. Model selection lives under llm.callSites.memoryRetrospective.",
43
+ "Controls the memory-retrospective background pass. Model selection lives under llm.callSites.memoryRetrospective.",
44
44
  );
45
45
 
46
46
  export type MemoryRetrospectiveConfig = z.infer<
@@ -260,9 +260,9 @@ export const MemoryV2ConfigSchema = z
260
260
  .object({
261
261
  enabled: z
262
262
  .boolean()
263
- .default(false)
263
+ .default(true)
264
264
  .describe(
265
- "Whether to use the LLM router as the per-turn page-selection mechanism in place of spreading activation. Disabled by default — opt in once the router orchestration and dispatcher land.",
265
+ "Whether to use the LLM router as the per-turn page-selection mechanism in place of spreading activation. Enabled by default.",
266
266
  ),
267
267
  max_page_ids: z
268
268
  .number()
@@ -283,9 +283,9 @@ export const MemoryV2ConfigSchema = z
283
283
  "Optional path to a file whose contents replace the bundled router prompt. Absolute paths are used as-is, a leading `~/` is expanded to the home directory, otherwise the path is resolved under the workspace root. The loaded contents may include `{{ASSISTANT_NAME}}`, `{{USER_NAME}}`, and `{{PAGE_INDEX}}`, which are substituted at runtime. If the file is missing, unreadable, or empty, the bundled prompt is used and a warning is logged.",
284
284
  ),
285
285
  })
286
- .default({ enabled: false, max_page_ids: 25, router_prompt_path: null })
286
+ .default({ enabled: true, max_page_ids: 25, router_prompt_path: null })
287
287
  .describe(
288
- "LLM router configuration. When enabled, a single Sonnet router call replaces spreading activation for per-turn page selection.",
288
+ "LLM router configuration. When enabled, a single router LLM call replaces spreading activation for per-turn page selection.",
289
289
  ),
290
290
  })
291
291
  .describe(
@@ -23,7 +23,9 @@ export const MemoryConfigSchema = z
23
23
  enabled: z
24
24
  .boolean({ error: "memory.enabled must be a boolean" })
25
25
  .default(true)
26
- .describe("Whether the long-term memory system is enabled"),
26
+ .describe(
27
+ "Whether the long-term memory system is enabled — gates background memory jobs, embedding generation, and `<memory>` block injection into user messages",
28
+ ),
27
29
  embeddings: MemoryEmbeddingsConfigSchema.default(
28
30
  MemoryEmbeddingsConfigSchema.parse({}),
29
31
  ),
@@ -3,6 +3,8 @@ import {
3
3
  createConnection,
4
4
  disableManagedConnectionsForByokHatch,
5
5
  getConnection,
6
+ MANAGED_CONNECTION_NAMES,
7
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
6
8
  } from "../providers/inference/connections.js";
7
9
  import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
8
10
  import { resolveModelIntent } from "../providers/model-intents.js";
@@ -17,10 +19,6 @@ import {
17
19
 
18
20
  const log = getLogger("seed-inference-profiles");
19
21
 
20
- const MANAGED_CONNECTION_NAME = "anthropic-managed";
21
- const MANAGED_PROFILE_PROVIDER: NonNullable<ProfileEntry["provider"]> =
22
- "anthropic";
23
-
24
22
  /**
25
23
  * Template for a daemon-managed inference profile. The profile's model is
26
24
  * resolved at seed time from `PROVIDER_MODEL_INTENTS` so the catalog stays the
@@ -31,16 +29,20 @@ type ManagedProfileTemplate = Omit<
31
29
  "provider" | "model" | "provider_connection"
32
30
  > & {
33
31
  intent: ModelIntent;
32
+ provider: NonNullable<ProfileEntry["provider"]>;
33
+ connectionName: string;
34
34
  };
35
35
 
36
36
  /**
37
- * Managed Anthropic profiles. Overwritten on every daemon boot so Vellum can
38
- * push model/config updates to customers in new releases. Platform overlays
37
+ * Managed profiles. Overwritten on every daemon boot so Vellum can push
38
+ * model/config updates to customers in new releases. Platform overlays
39
39
  * (`preserveProfileNames`) take precedence when present.
40
40
  */
41
41
  const MANAGED_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
42
42
  balanced: {
43
43
  intent: "balanced",
44
+ provider: "anthropic",
45
+ connectionName: "anthropic-managed",
44
46
  source: "managed",
45
47
  label: "Balanced",
46
48
  description: "Good balance of quality, cost, and speed",
@@ -51,6 +53,8 @@ const MANAGED_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
51
53
  },
52
54
  "quality-optimized": {
53
55
  intent: "quality-optimized",
56
+ provider: "anthropic",
57
+ connectionName: "anthropic-managed",
54
58
  source: "managed",
55
59
  label: "Quality",
56
60
  description: "Best results with the most capable model",
@@ -61,6 +65,8 @@ const MANAGED_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
61
65
  },
62
66
  "cost-optimized": {
63
67
  intent: "latency-optimized",
68
+ provider: "anthropic",
69
+ connectionName: "anthropic-managed",
64
70
  source: "managed",
65
71
  label: "Speed",
66
72
  description: "Fastest responses at lower cost",
@@ -74,11 +80,15 @@ const MANAGED_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
74
80
  /**
75
81
  * User profile templates. Materialized at hatch time for off-platform
76
82
  * installations. Each points at the user's personal provider connection
77
- * (backed by their API key in CES).
83
+ * (backed by their API key in CES). The `provider` and `connectionName`
84
+ * fields are placeholders — they are overridden at hatch time with the
85
+ * user's chosen provider and personal connection name.
78
86
  */
79
87
  const USER_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
80
88
  "custom-balanced": {
81
89
  intent: "balanced",
90
+ provider: "anthropic",
91
+ connectionName: "",
82
92
  source: "user",
83
93
  label: "Balanced",
84
94
  description: "Good balance of quality, cost, and speed",
@@ -89,6 +99,8 @@ const USER_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
89
99
  },
90
100
  "custom-quality-optimized": {
91
101
  intent: "quality-optimized",
102
+ provider: "anthropic",
103
+ connectionName: "",
92
104
  source: "user",
93
105
  label: "Quality",
94
106
  description: "Best results with the most capable model",
@@ -99,6 +111,8 @@ const USER_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
99
111
  },
100
112
  "custom-cost-optimized": {
101
113
  intent: "latency-optimized",
114
+ provider: "anthropic",
115
+ connectionName: "",
102
116
  source: "user",
103
117
  label: "Speed",
104
118
  description: "Fastest responses at lower cost",
@@ -164,11 +178,12 @@ export function seedInferenceProfiles(
164
178
  // BYOK mode = off-platform installs. The user is bringing their own provider
165
179
  // API key; managed profile labels get a " (Managed)" suffix to disambiguate
166
180
  // from the personal "custom-*" profiles that share base labels. Managed
167
- // profile + connection status is initially "disabled" so the picker doesn't
168
- // offer an unusable platform-auth option on day one — but ONLY at hatch
169
- // time, and ONLY when the entry isn't already in the user's config (i.e.
170
- // first materialization). Post-hatch user toggles survive every subsequent
171
- // boot.
181
+ // profile + connection status is initially "disabled" for true BYOK hatches
182
+ // so the picker doesn't offer an unusable platform-auth option on day one.
183
+ // When the hatch overlay explicitly selects a managed profile, the matching
184
+ // managed connection stays active so the first post-onboarding message can
185
+ // use the user's chosen managed route. Post-hatch user toggles survive every
186
+ // subsequent boot.
172
187
  const isByokMode = !isPlatform;
173
188
 
174
189
  // 1. Managed profiles. Off-platform: overwrite on every boot so Vellum can
@@ -198,10 +213,18 @@ export function seedInferenceProfiles(
198
213
  // rewritten to the suffixed form. Any other previous label value
199
214
  // (user-set custom string, explicit null, already-suffixed) is
200
215
  // preserved as-is.
201
- // • status: "disabled" on fresh materialization at hatch only —
202
- // gated on (isHatch && !previous) so post-hatch boots and existing
203
- // installs are never auto-disabled. A user re-enable persists
204
- // across boots via the key-presence preservation below.
216
+ // • status: "disabled" on fresh materialization at BYOK hatch only —
217
+ // gated on (isHatch && !previous) and skipped for any managed
218
+ // connection explicitly selected by the hatch overlay. Post-hatch
219
+ // boots and existing installs are never auto-disabled. A user
220
+ // re-enable persists across boots via the key-presence preservation
221
+ // below.
222
+ const hatchSelectedManagedConnection = getHatchSelectedManagedConnection(
223
+ llm,
224
+ profiles,
225
+ options,
226
+ );
227
+
205
228
  for (const [name, template] of Object.entries(MANAGED_PROFILE_TEMPLATES)) {
206
229
  if (preservedProfileNames.has(name)) continue;
207
230
  if (isPlatform && readObject(profiles[name]) !== null) continue;
@@ -212,10 +235,15 @@ export function seedInferenceProfiles(
212
235
  : template;
213
236
  const next = materializeProfile(
214
237
  effectiveTemplate,
215
- MANAGED_PROFILE_PROVIDER,
216
- MANAGED_CONNECTION_NAME,
238
+ template.provider,
239
+ template.connectionName,
217
240
  ) as Record<string, unknown>;
218
- if (isByokMode && options.isHatch && !previous) {
241
+ if (
242
+ isByokMode &&
243
+ options.isHatch &&
244
+ !previous &&
245
+ template.connectionName !== hatchSelectedManagedConnection
246
+ ) {
219
247
  next.status = "disabled";
220
248
  }
221
249
  if (previous) {
@@ -238,17 +266,24 @@ export function seedInferenceProfiles(
238
266
  // 2. User profiles — only at hatch time for off-platform installations.
239
267
  let userConnectionName: string | undefined;
240
268
  if (options.isHatch && !isPlatform) {
241
- // BYOK hatch: disable the three canonical managed connections so the
242
- // picker doesn't surface unusable platform-auth options on day one.
243
- // Runs only here, only at hatch `seedCanonicalConnections` leaves
244
- // `status` alone on subsequent boots so a post-hatch user re-enable
245
- // persists.
269
+ // BYOK hatch: disable canonical managed connections so the picker doesn't
270
+ // surface unusable platform-auth options on day one. If the hatch overlay
271
+ // selected a managed profile, leave that connection active; the user has
272
+ // already chosen managed inference. Runs only here, only at hatch
273
+ // `seedCanonicalConnections` leaves `status` alone on subsequent boots so
274
+ // a post-hatch user re-enable persists.
246
275
  if (options.db) {
247
- disableManagedConnectionsForByokHatch(options.db);
276
+ disableManagedConnectionsForByokHatch(options.db, {
277
+ excludeConnection: hatchSelectedManagedConnection,
278
+ });
248
279
  }
249
280
 
250
281
  const hatchProvider = readString(readObject(llm.default)?.provider);
251
- if (hatchProvider && hatchProvider !== "ollama") {
282
+ if (
283
+ hatchProvider &&
284
+ hatchProvider !== "ollama" &&
285
+ !PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(hatchProvider)
286
+ ) {
252
287
  userConnectionName = `${hatchProvider}-personal`;
253
288
 
254
289
  if (options.db) {
@@ -269,8 +304,7 @@ export function seedInferenceProfiles(
269
304
  }
270
305
  }
271
306
 
272
- const provider =
273
- hatchProvider as NonNullable<ProfileEntry["provider"]>;
307
+ const provider = hatchProvider as NonNullable<ProfileEntry["provider"]>;
274
308
  for (const [name, template] of Object.entries(USER_PROFILE_TEMPLATES)) {
275
309
  if (preservedProfileNames.has(name)) continue;
276
310
  profiles[name] = materializeProfile(
@@ -342,7 +376,7 @@ function materializeProfile(
342
376
  provider: NonNullable<ProfileEntry["provider"]>,
343
377
  connectionName: string,
344
378
  ): ProfileEntry {
345
- const { intent, ...rest } = template;
379
+ const { intent, provider: _p, connectionName: _c, ...rest } = template;
346
380
  return {
347
381
  ...rest,
348
382
  provider,
@@ -361,6 +395,42 @@ function readString(value: unknown): string | undefined {
361
395
  return typeof value === "string" && value.length > 0 ? value : undefined;
362
396
  }
363
397
 
398
+ function getHatchSelectedManagedConnection(
399
+ llm: Record<string, unknown>,
400
+ profiles: Record<string, Record<string, unknown>>,
401
+ options: SeedInferenceProfilesOptions,
402
+ ): string | undefined {
403
+ if (!options.isHatch || options.preserveActiveProfile !== true) {
404
+ return undefined;
405
+ }
406
+
407
+ const activeProfile = readString(llm.activeProfile);
408
+ if (!activeProfile) return undefined;
409
+
410
+ const activeProfileEntry = readObject(profiles[activeProfile]);
411
+ if (
412
+ activeProfileEntry &&
413
+ Object.prototype.hasOwnProperty.call(
414
+ activeProfileEntry,
415
+ "provider_connection",
416
+ )
417
+ ) {
418
+ const explicitConnection = readString(
419
+ activeProfileEntry.provider_connection,
420
+ );
421
+ return explicitConnection &&
422
+ MANAGED_CONNECTION_NAMES.has(explicitConnection)
423
+ ? explicitConnection
424
+ : undefined;
425
+ }
426
+
427
+ const templateConnection =
428
+ MANAGED_PROFILE_TEMPLATES[activeProfile]?.connectionName;
429
+ return templateConnection && MANAGED_CONNECTION_NAMES.has(templateConnection)
430
+ ? templateConnection
431
+ : undefined;
432
+ }
433
+
364
434
  /**
365
435
  * Format the human-readable label seeded onto a personal provider connection
366
436
  * at hatch time, e.g. `"Anthropic (Personal)"`. The display name is sourced