@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
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Route handlers for A2A integration config endpoints.
3
+ *
4
+ * GET /v1/integrations/a2a/config — get current A2A config status
5
+ * POST /v1/integrations/a2a/config — enable A2A channel
6
+ * DELETE /v1/integrations/a2a/config — disable A2A channel
7
+ * POST /v1/integrations/a2a/invite — create a shareable A2A invite token
8
+ * POST /v1/integrations/a2a/invite/complete — sender-side invite completion
9
+ * POST /v1/integrations/a2a/invite/redeem — receiver-side invite redemption
10
+ */
11
+
12
+ import { isA2AEnabled } from "../../../a2a/feature-gate.js";
13
+ import { getConfig } from "../../../config/loader.js";
14
+ import {
15
+ clearA2AConfig,
16
+ completeA2AInvite,
17
+ createA2AInvite,
18
+ getA2AConfig,
19
+ redeemA2AInvite,
20
+ setA2AConfig,
21
+ } from "../../../daemon/handlers/config-a2a.js";
22
+ import { BadRequestError } from "../errors.js";
23
+ import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ function assertA2AFlag(): void {
30
+ if (!isA2AEnabled(getConfig())) {
31
+ throw new BadRequestError("A2A channel is not available");
32
+ }
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Handlers
37
+ // ---------------------------------------------------------------------------
38
+
39
+ function handleGetA2AConfig() {
40
+ assertA2AFlag();
41
+ return getA2AConfig();
42
+ }
43
+
44
+ function handleSetA2AConfig() {
45
+ assertA2AFlag();
46
+ const result = setA2AConfig();
47
+ if (!result.success) {
48
+ throw new BadRequestError(result.error ?? "Failed to enable A2A");
49
+ }
50
+ return result;
51
+ }
52
+
53
+ function handleClearA2AConfig() {
54
+ assertA2AFlag();
55
+ return clearA2AConfig();
56
+ }
57
+
58
+ function handleCreateA2AInvite({ body = {} }: RouteHandlerArgs) {
59
+ assertA2AFlag();
60
+ const { expiresInHours } = body as { expiresInHours?: unknown };
61
+ if (expiresInHours !== undefined) {
62
+ if (
63
+ typeof expiresInHours !== "number" ||
64
+ !Number.isFinite(expiresInHours) ||
65
+ expiresInHours <= 0
66
+ ) {
67
+ throw new BadRequestError(
68
+ "expiresInHours must be a positive finite number",
69
+ );
70
+ }
71
+ }
72
+ const result = createA2AInvite({
73
+ expiresInHours: expiresInHours as number | undefined,
74
+ });
75
+ if (!result.success) {
76
+ throw new BadRequestError(result.error ?? "Failed to create A2A invite");
77
+ }
78
+ return result;
79
+ }
80
+
81
+ function handleCompleteA2AInvite({ body = {} }: RouteHandlerArgs) {
82
+ const { token, senderAssistantId, acceptor } = body as {
83
+ token?: unknown;
84
+ senderAssistantId?: unknown;
85
+ acceptor?: {
86
+ assistantId?: unknown;
87
+ displayName?: unknown;
88
+ gatewayUrl?: unknown;
89
+ };
90
+ };
91
+
92
+ if (typeof token !== "string" || !token) {
93
+ throw new BadRequestError(
94
+ "token is required and must be a non-empty string",
95
+ );
96
+ }
97
+ if (typeof senderAssistantId !== "string" || !senderAssistantId) {
98
+ throw new BadRequestError(
99
+ "senderAssistantId is required and must be a non-empty string",
100
+ );
101
+ }
102
+ if (
103
+ !acceptor ||
104
+ typeof acceptor.assistantId !== "string" ||
105
+ !acceptor.assistantId ||
106
+ typeof acceptor.displayName !== "string" ||
107
+ !acceptor.displayName ||
108
+ typeof acceptor.gatewayUrl !== "string" ||
109
+ !acceptor.gatewayUrl
110
+ ) {
111
+ throw new BadRequestError(
112
+ "acceptor must include non-empty assistantId, displayName, and gatewayUrl",
113
+ );
114
+ }
115
+
116
+ const result = completeA2AInvite({
117
+ token,
118
+ senderAssistantId,
119
+ acceptor: {
120
+ assistantId: acceptor.assistantId,
121
+ displayName: acceptor.displayName,
122
+ gatewayUrl: acceptor.gatewayUrl,
123
+ },
124
+ });
125
+ if (!result.success) {
126
+ throw new BadRequestError(result.error ?? "Failed to complete A2A invite");
127
+ }
128
+ return result;
129
+ }
130
+
131
+ function handleRedeemA2AInvite({ body = {} }: RouteHandlerArgs) {
132
+ const { sender } = body as {
133
+ sender?: {
134
+ assistantId?: unknown;
135
+ displayName?: unknown;
136
+ gatewayUrl?: unknown;
137
+ };
138
+ };
139
+
140
+ if (
141
+ !sender ||
142
+ typeof sender.assistantId !== "string" ||
143
+ !sender.assistantId ||
144
+ typeof sender.displayName !== "string" ||
145
+ !sender.displayName ||
146
+ typeof sender.gatewayUrl !== "string" ||
147
+ !sender.gatewayUrl
148
+ ) {
149
+ throw new BadRequestError(
150
+ "sender must include non-empty assistantId, displayName, and gatewayUrl",
151
+ );
152
+ }
153
+
154
+ const result = redeemA2AInvite({
155
+ sender: {
156
+ assistantId: sender.assistantId,
157
+ displayName: sender.displayName,
158
+ gatewayUrl: sender.gatewayUrl,
159
+ },
160
+ });
161
+ if (!result.success) {
162
+ throw new BadRequestError(result.error ?? "Failed to redeem A2A invite");
163
+ }
164
+ return result;
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Route definitions
169
+ // ---------------------------------------------------------------------------
170
+
171
+ export const ROUTES: RouteDefinition[] = [
172
+ {
173
+ operationId: "integrations_a2a_config_get",
174
+ endpoint: "integrations/a2a/config",
175
+ method: "GET",
176
+ summary: "Get A2A config",
177
+ description: "Check current A2A channel configuration status.",
178
+ tags: ["integrations"],
179
+ requirePolicyEnforcement: true,
180
+ handler: () => handleGetA2AConfig(),
181
+ },
182
+ {
183
+ operationId: "integrations_a2a_config_post",
184
+ endpoint: "integrations/a2a/config",
185
+ method: "POST",
186
+ summary: "Enable A2A channel",
187
+ description: "Enable the A2A channel for inter-assistant communication.",
188
+ tags: ["integrations"],
189
+ requirePolicyEnforcement: true,
190
+ handler: () => handleSetA2AConfig(),
191
+ },
192
+ {
193
+ operationId: "integrations_a2a_config_delete",
194
+ endpoint: "integrations/a2a/config",
195
+ method: "DELETE",
196
+ summary: "Disable A2A channel",
197
+ description: "Disable the A2A channel.",
198
+ tags: ["integrations"],
199
+ requirePolicyEnforcement: true,
200
+ handler: () => handleClearA2AConfig(),
201
+ },
202
+ {
203
+ operationId: "integrations_a2a_invite_post",
204
+ endpoint: "integrations/a2a/invite",
205
+ method: "POST",
206
+ summary: "Create A2A invite",
207
+ description:
208
+ "Create a shareable A2A invite token for link-based contact creation.",
209
+ tags: ["integrations"],
210
+ requirePolicyEnforcement: true,
211
+ handler: handleCreateA2AInvite,
212
+ },
213
+ {
214
+ operationId: "integrations_a2a_invite_complete_post",
215
+ endpoint: "integrations/a2a/invite/complete",
216
+ method: "POST",
217
+ summary: "Complete A2A invite (sender side)",
218
+ description:
219
+ "Called by the platform to finalize the sender side of a link-based A2A connection.",
220
+ tags: ["integrations"],
221
+ requirePolicyEnforcement: true,
222
+ handler: handleCompleteA2AInvite,
223
+ },
224
+ {
225
+ operationId: "integrations_a2a_invite_redeem_post",
226
+ endpoint: "integrations/a2a/invite/redeem",
227
+ method: "POST",
228
+ summary: "Redeem A2A invite (receiver side)",
229
+ description:
230
+ "Called by the platform to create a trusted contact on the receiver side of a link-based A2A connection.",
231
+ tags: ["integrations"],
232
+ requirePolicyEnforcement: true,
233
+ handler: handleRedeemA2AInvite,
234
+ },
235
+ ];
@@ -1,10 +1,20 @@
1
+ import { resolveDefaultProfileKey } from "../../config/llm-resolver.js";
2
+ import { loadConfig } from "../../config/loader.js";
1
3
  import { CALL_SITE_CATALOG, CALL_SITE_DOMAINS } from "../../config/schemas/call-site-catalog.js";
4
+ import type { LLMCallSite } from "../../config/schemas/llm.js";
2
5
  import type { RouteDefinition } from "./types.js";
3
6
 
4
7
  async function handleGetCallSites() {
8
+ const { llm } = loadConfig();
5
9
  return {
6
10
  domains: CALL_SITE_DOMAINS,
7
- callSites: CALL_SITE_CATALOG,
11
+ callSites: CALL_SITE_CATALOG.map((entry) => ({
12
+ ...entry,
13
+ defaultProfile: resolveDefaultProfileKey(
14
+ entry.id as LLMCallSite,
15
+ llm,
16
+ ),
17
+ })),
8
18
  };
9
19
  }
10
20
 
@@ -188,6 +188,47 @@ function getSubagentDetail(
188
188
  // ---------------------------------------------------------------------------
189
189
 
190
190
  export const ROUTES: RouteDefinition[] = [
191
+ {
192
+ operationId: "reconcileSubagents",
193
+ endpoint: "subagents/reconcile",
194
+ method: "GET",
195
+ policyKey: "subagents",
196
+ summary: "Reconcile subagent live status",
197
+ description:
198
+ "Returns the live in-memory status of all subagents known to the daemon for a given parent conversation. Subagents not in the response are orphaned.",
199
+ tags: ["subagents"],
200
+ queryParams: [
201
+ {
202
+ name: "parentConversationId",
203
+ schema: { type: "string" },
204
+ description: "Parent conversation ID",
205
+ },
206
+ ],
207
+ responseBody: z.object({
208
+ subagents: z.record(
209
+ z.string(),
210
+ z.object({
211
+ status: z.string(),
212
+ }),
213
+ ),
214
+ }),
215
+ handler: ({ queryParams }) => {
216
+ const parentConversationId = queryParams?.parentConversationId;
217
+ if (!parentConversationId) {
218
+ throw new BadRequestError(
219
+ "parentConversationId query parameter is required",
220
+ );
221
+ }
222
+ const manager = getSubagentManager();
223
+ const children = manager.getChildrenOf(parentConversationId);
224
+ const subagents: Record<string, { status: string }> = {};
225
+ for (const child of children) {
226
+ subagents[child.config.id] = { status: child.status };
227
+ }
228
+ return { subagents };
229
+ },
230
+ },
231
+
191
232
  {
192
233
  operationId: "getSubagentDetail",
193
234
  endpoint: "subagents/:id",
@@ -903,6 +903,8 @@ export class SubagentManager {
903
903
  subagentId: info.subagentId,
904
904
  label: info.label,
905
905
  status: "running" as const,
906
+ conversationId: managed.state.conversationId,
907
+ objective: managed.state.config.objective,
906
908
  },
907
909
  },
908
910
  );
@@ -6,7 +6,6 @@ import {
6
6
  type RememberInput,
7
7
  } from "../../memory/graph/tool-handlers.js";
8
8
  import {
9
- getRememberDescription,
10
9
  graphRecallDefinition,
11
10
  graphRememberDefinition,
12
11
  } from "../../memory/graph/tools.js";
@@ -19,19 +18,12 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
19
18
 
20
19
  class RememberTool implements Tool {
21
20
  name = "remember";
22
- // Surfaced in registry listings. The flag-aware description used during
23
- // actual tool dispatch is computed lazily in `getDefinition()` below so it
24
- // tracks config changes across daemon lifetime without needing a tool
25
- // re-registration pass.
26
21
  description = graphRememberDefinition.description;
27
22
  category = "memory";
28
23
  defaultRiskLevel = RiskLevel.Low;
29
24
 
30
25
  getDefinition(): ToolDefinition {
31
- return {
32
- ...graphRememberDefinition,
33
- description: getRememberDescription(getConfig()),
34
- };
26
+ return graphRememberDefinition;
35
27
  }
36
28
 
37
29
  async execute(
@@ -10,7 +10,7 @@ import { hostFileWriteTool } from "./host-filesystem/write.js";
10
10
  import { hostShellTool } from "./host-terminal/host-shell.js";
11
11
  import { toProviderSafeToolName } from "./provider-tool-name.js";
12
12
  import { registerSystemTools } from "./system/register.js";
13
- import type { PluginTool, Tool } from "./types.js";
13
+ import type { LoadedPluginTool, Tool } from "./types.js";
14
14
  import { allUiSurfaceTools } from "./ui-surface/definitions.js";
15
15
  import { registerUiSurfaceTools } from "./ui-surface/registry.js";
16
16
 
@@ -193,7 +193,7 @@ export function registerSkillTools(newTools: Tool[]): Tool[] {
193
193
  */
194
194
  export function registerPluginTools(
195
195
  pluginName: string,
196
- newTools: PluginTool[],
196
+ newTools: LoadedPluginTool[],
197
197
  ): Tool[] {
198
198
  const stamped: Tool[] = newTools.map((pluginTool) => {
199
199
  const { input_schema, ...rest } = pluginTool;
@@ -376,16 +376,51 @@ export interface Tool {
376
376
 
377
377
  /**
378
378
  * Plugin-facing tool shape. The narrow surface plugin authors implement;
379
- * differs from {@link Tool} in three ways:
379
+ * differs from {@link Tool} in four ways:
380
380
  * - Plugins declare `input_schema` as a top-level field instead of
381
381
  * implementing `getDefinition()`. The registration boundary synthesizes
382
382
  * `getDefinition()` from `{name, description, input_schema}` before the
383
383
  * tool enters the internal registry.
384
+ * - `name` is derived from the tool file's basename by the external plugin
385
+ * loader.
384
386
  * - `category` is registry-owned and stamped to `"plugin"` when the tool is
385
387
  * registered.
386
388
  * - All ownership stamps (`origin`, `ownerPluginId`, etc.) are set
387
389
  * authoritatively by the bootstrap; plugin authors leave them blank.
390
+ *
391
+ * Every author-visible field is optional. The loader fills the four
392
+ * normally-required slots (`description`, `defaultRiskLevel`,
393
+ * `input_schema`, `execute`) with documented defaults when a plugin omits
394
+ * them — see `applyPluginToolDefaults` in `external-plugin-loader.ts`.
395
+ * A nameless, body-less `export default {}` is a valid (if useless) tool;
396
+ * misconfigured tools surface at call time rather than blocking plugin
397
+ * load.
398
+ */
399
+ export type PluginTool = Omit<
400
+ Tool,
401
+ "category" | "getDefinition" | "name" | "description" | "defaultRiskLevel"
402
+ > & {
403
+ description?: string;
404
+ defaultRiskLevel?: RiskLevel;
405
+ input_schema?: object;
406
+ execute?: (
407
+ input: Record<string, unknown>,
408
+ context: ToolContext,
409
+ ) => Promise<ToolExecutionResult>;
410
+ };
411
+
412
+ /**
413
+ * Plugin tool after the external loader has derived its registry name and
414
+ * filled defaults for any author-omitted fields. All four normally-required
415
+ * slots are guaranteed present.
388
416
  */
389
- export type PluginTool = Omit<Tool, "category" | "getDefinition"> & {
417
+ export type LoadedPluginTool = PluginTool & {
418
+ name: string;
419
+ description: string;
420
+ defaultRiskLevel: RiskLevel;
390
421
  input_schema: object;
422
+ execute: (
423
+ input: Record<string, unknown>,
424
+ context: ToolContext,
425
+ ) => Promise<ToolExecutionResult>;
391
426
  };
@@ -0,0 +1,91 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ // Upgrade callSites.memoryRouter from the 077-seeded
7
+ // {model: "claude-sonnet-4-6", contextWindow: {maxInputTokens: 1_000_000}}
8
+ // shape to {profile: "balanced"} so the router rides the workspace's active
9
+ // inference profile (with thinking enabled, higher effort, etc.) instead of a
10
+ // bare model pin.
11
+ //
12
+ // Two skip conditions guard against runtime regressions:
13
+ //
14
+ // 1. BYOK / non-Anthropic workspaces. `balanced` resolves to the managed
15
+ // Anthropic connection (see seedInferenceProfiles), which off-platform
16
+ // installs explicitly disable. Forcing `balanced` there would make
17
+ // getConfiguredProvider("memoryRouter") return null and silently
18
+ // disable memory injection. Detect this by inspecting llm.default.provider
19
+ // — same heuristic migration 077 used to gate its seed.
20
+ //
21
+ // 2. User-customized memoryRouter config. If the existing entry isn't the
22
+ // exact 077-seeded shape (and isn't already {profile: "balanced"}), the
23
+ // user — or a platform overlay — chose those values deliberately. Match
24
+ // 077's pattern of preserving any prior config.
25
+ export const memoryRouterBalancedProfileMigration: WorkspaceMigration = {
26
+ id: "087-memory-router-balanced-profile",
27
+ description:
28
+ "Set callSites.memoryRouter to { profile: 'balanced' }, dropping the seeded model and contextWindow override",
29
+ run(workspaceDir: string): void {
30
+ if (process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH) return;
31
+
32
+ const configPath = join(workspaceDir, "config.json");
33
+ const configExisted = existsSync(configPath);
34
+
35
+ let config: Record<string, unknown> = {};
36
+ if (configExisted) {
37
+ try {
38
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
39
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
40
+ config = raw as Record<string, unknown>;
41
+ } catch {
42
+ return;
43
+ }
44
+ }
45
+
46
+ const llm = readObject(config.llm) ?? {};
47
+
48
+ const explicitProvider = readString(readObject(llm.default)?.provider);
49
+ if (explicitProvider !== undefined && explicitProvider !== "anthropic") {
50
+ return;
51
+ }
52
+
53
+ const callSites = readObject(llm.callSites) ?? {};
54
+ const existing = readObject(callSites.memoryRouter);
55
+
56
+ if (existing !== null && !isSeededBy077(existing)) {
57
+ return;
58
+ }
59
+
60
+ callSites.memoryRouter = { profile: "balanced" };
61
+ llm.callSites = callSites;
62
+ config.llm = llm;
63
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
64
+ },
65
+ down(_workspaceDir: string): void {
66
+ // Forward-only.
67
+ },
68
+ };
69
+
70
+ // True when the entry looks exactly like what migration 077 wrote: a model pin
71
+ // of claude-sonnet-4-6 plus the 1M-token context window, and nothing else.
72
+ function isSeededBy077(entry: Record<string, unknown>): boolean {
73
+ const keys = Object.keys(entry);
74
+ if (keys.length !== 2) return false;
75
+ if (entry.model !== "claude-sonnet-4-6") return false;
76
+ const contextWindow = readObject(entry.contextWindow);
77
+ if (contextWindow === null) return false;
78
+ const cwKeys = Object.keys(contextWindow);
79
+ return cwKeys.length === 1 && contextWindow.maxInputTokens === 1_000_000;
80
+ }
81
+
82
+ function readObject(value: unknown): Record<string, unknown> | null {
83
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
84
+ return null;
85
+ }
86
+ return value as Record<string, unknown>;
87
+ }
88
+
89
+ function readString(value: unknown): string | undefined {
90
+ return typeof value === "string" && value.length > 0 ? value : undefined;
91
+ }
@@ -84,6 +84,7 @@ import { systemPromptPrefixToFileMigration } from "./083-system-prompt-prefix-to
84
84
  import { removeLegacySkillsIndexMigration } from "./084-remove-legacy-skills-index.js";
85
85
  import { memoryV2Bm25BReembedDisabledV2PagesMigration } from "./085-memory-v2-bm25-b-reembed-disabled-v2-pages.js";
86
86
  import { revertStaleGeminiMisRewritesMigration } from "./086-revert-stale-gemini-mis-rewrites.js";
87
+ import { memoryRouterBalancedProfileMigration } from "./087-memory-router-balanced-profile.js";
87
88
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
88
89
  import type { WorkspaceMigration } from "./types.js";
89
90
 
@@ -179,4 +180,5 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
179
180
  removeLegacySkillsIndexMigration,
180
181
  memoryV2Bm25BReembedDisabledV2PagesMigration,
181
182
  revertStaleGeminiMisRewritesMigration,
183
+ memoryRouterBalancedProfileMigration,
182
184
  ];