@vellumai/assistant 0.9.0 → 0.9.1-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.
Files changed (222) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +4 -4
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
  12. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  13. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  14. package/openapi.yaml +135 -59
  15. package/package.json +2 -1
  16. package/scripts/sync-llm-catalog.ts +6 -15
  17. package/scripts/sync-web-search-catalog.ts +3 -11
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
  19. package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
  20. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  21. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  22. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  23. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  24. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  25. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  26. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  27. package/src/__tests__/btw-routes.test.ts +6 -8
  28. package/src/__tests__/checker.test.ts +0 -3
  29. package/src/__tests__/config-loader-backfill.test.ts +103 -6
  30. package/src/__tests__/config-watcher.test.ts +0 -18
  31. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  32. package/src/__tests__/credential-broker.test.ts +449 -1
  33. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  34. package/src/__tests__/credential-prompt-route.test.ts +3 -4
  35. package/src/__tests__/credential-routes.test.ts +360 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -13
  37. package/src/__tests__/dynamic-page-surface.test.ts +101 -1
  38. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  39. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  40. package/src/__tests__/identity-routes.test.ts +0 -189
  41. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  42. package/src/__tests__/invite-redemption-service.test.ts +4 -4
  43. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  44. package/src/__tests__/llm-catalog-parity.test.ts +0 -22
  45. package/src/__tests__/llm-resolver.test.ts +49 -24
  46. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  47. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  48. package/src/__tests__/persona-resolver.test.ts +11 -14
  49. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  50. package/src/__tests__/registry.test.ts +2 -7
  51. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  52. package/src/__tests__/schedule-routes.test.ts +0 -30
  53. package/src/__tests__/schedule-tools.test.ts +2 -18
  54. package/src/__tests__/skill-execute-input.test.ts +46 -1
  55. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  56. package/src/__tests__/subagent-tools.test.ts +116 -0
  57. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  58. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  59. package/src/__tests__/token-manager.test.ts +519 -0
  60. package/src/__tests__/tool-executor.test.ts +0 -79
  61. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  62. package/src/__tests__/trusted-contact-verification.test.ts +6 -6
  63. package/src/__tests__/voice-invite-redemption.test.ts +2 -2
  64. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  65. package/src/__tests__/workspace-greetings.test.ts +152 -0
  66. package/src/agent/loop.ts +25 -5
  67. package/src/api/README.md +6 -6
  68. package/src/api/responses/conversation-message.ts +2 -4
  69. package/src/api/responses/home.ts +0 -4
  70. package/src/approvals/guardian-request-resolvers.ts +2 -2
  71. package/src/calls/relay-access-wait.ts +1 -1
  72. package/src/calls/voice-session-bridge.ts +2 -2
  73. package/src/cli/commands/plugins.ts +143 -2
  74. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  75. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
  76. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
  77. package/src/cli/lib/diff-plugin.ts +346 -0
  78. package/src/cli/lib/install-from-github.ts +105 -17
  79. package/src/cli/lib/merge-plugin-tree.ts +228 -0
  80. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  81. package/src/cli/lib/upgrade-plugin.ts +270 -10
  82. package/src/cli/program.ts +0 -2
  83. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  84. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  85. package/src/config/bundled-skills/workflows/SKILL.md +0 -1
  86. package/src/config/bundled-tool-registry.ts +2 -7
  87. package/src/config/call-site-defaults.ts +12 -2
  88. package/src/config/feature-flag-registry.json +1 -17
  89. package/src/config/inference-profile-validation.ts +26 -0
  90. package/src/config/loader.ts +4 -0
  91. package/src/config/profile-order.ts +28 -0
  92. package/src/config/schemas/elevenlabs.ts +0 -1
  93. package/src/config/schemas/platform.ts +0 -8
  94. package/src/config/seed-inference-profiles.ts +25 -20
  95. package/src/contacts/contact-store.ts +87 -96
  96. package/src/contacts/contacts-write.ts +5 -21
  97. package/src/context/compactor.ts +2 -2
  98. package/src/credential-execution/process-manager.ts +55 -14
  99. package/src/credential-execution/prompted-credential.ts +2 -3
  100. package/src/daemon/config-watcher.ts +0 -4
  101. package/src/daemon/conversation-agent-loop.ts +15 -4
  102. package/src/daemon/conversation-slash.ts +2 -23
  103. package/src/daemon/conversation-tool-setup.ts +11 -3
  104. package/src/daemon/conversation.ts +2 -0
  105. package/src/daemon/handlers/config-channels.ts +20 -16
  106. package/src/daemon/handlers/config-slack-channel.ts +2 -3
  107. package/src/daemon/lifecycle.ts +0 -7
  108. package/src/daemon/message-types/conversations.ts +3 -3
  109. package/src/daemon/message-types/sync.ts +0 -1
  110. package/src/daemon/orphan-reaper.test.ts +0 -19
  111. package/src/daemon/orphan-reaper.ts +2 -24
  112. package/src/daemon/server.ts +0 -10
  113. package/src/home/relationship-state.ts +2 -4
  114. package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
  115. package/src/memory/bookmark-crud.ts +1 -2
  116. package/src/memory/db-init.ts +7 -17
  117. package/src/memory/embedding-backend.ts +23 -0
  118. package/src/memory/embedding-billing-breaker.ts +96 -0
  119. package/src/memory/jobs-store.ts +25 -13
  120. package/src/memory/jobs-worker.ts +52 -0
  121. package/src/memory/memory-retrospective-constants.ts +4 -4
  122. package/src/memory/memory-retrospective-job.ts +19 -227
  123. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
  124. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
  125. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  126. package/src/memory/migrations/index.ts +1 -0
  127. package/src/memory/migrations/run-migrations.ts +41 -0
  128. package/src/memory/migrations/validate-migration-state.ts +1 -1
  129. package/src/memory/schema/contacts.ts +0 -4
  130. package/src/messaging/providers/slack/adapter.ts +1 -1
  131. package/src/notifications/adapters/shared.ts +29 -0
  132. package/src/notifications/adapters/slack.ts +5 -32
  133. package/src/notifications/adapters/telegram.ts +2 -20
  134. package/src/notifications/broadcaster.ts +10 -1
  135. package/src/notifications/home-feed-side-effect.ts +4 -3
  136. package/src/notifications/notification-utils.ts +17 -19
  137. package/src/notifications/types.ts +7 -0
  138. package/src/oauth/AGENTS.md +5 -24
  139. package/src/plugin-api/constants.ts +1 -1
  140. package/src/plugin-api/index.ts +6 -1
  141. package/src/plugin-api/model-profiles.ts +33 -0
  142. package/src/plugin-api/types.ts +50 -2
  143. package/src/plugins/defaults/index.ts +25 -0
  144. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
  145. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
  146. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  147. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  148. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  149. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  150. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
  151. package/src/prompts/persona-resolver.ts +2 -2
  152. package/src/runtime/AGENTS.md +0 -1
  153. package/src/runtime/actor-trust-resolver.ts +12 -44
  154. package/src/runtime/btw-sidechain.ts +3 -6
  155. package/src/runtime/channel-approval-types.ts +18 -45
  156. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  157. package/src/runtime/channel-verification-service.ts +4 -3
  158. package/src/runtime/invite-redemption-service.ts +3 -3
  159. package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
  160. package/src/runtime/routes/app-routes.ts +1 -1
  161. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  162. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  163. package/src/runtime/routes/btw-routes.ts +1 -27
  164. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  165. package/src/runtime/routes/conversation-routes.ts +2 -2
  166. package/src/runtime/routes/credential-routes.ts +40 -16
  167. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  168. package/src/runtime/routes/identity-routes.ts +1 -296
  169. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
  170. package/src/runtime/routes/plugins-routes.ts +171 -5
  171. package/src/runtime/routes/schedule-routes.ts +0 -22
  172. package/src/runtime/routes/workflow-routes.test.ts +4 -43
  173. package/src/runtime/routes/workflow-routes.ts +0 -28
  174. package/src/runtime/routes/workspace-greetings.ts +55 -0
  175. package/src/runtime/sync/resource-sync-events.ts +1 -11
  176. package/src/schedule/inference-profile.ts +2 -14
  177. package/src/subagent/manager.ts +6 -0
  178. package/src/subagent/types.ts +6 -0
  179. package/src/tools/AGENTS.md +3 -3
  180. package/src/tools/browser/browser-execution.ts +1 -1
  181. package/src/tools/network/web-search-error.ts +1 -1
  182. package/src/tools/permission-checker.ts +1 -1
  183. package/src/tools/schedule/create.ts +3 -9
  184. package/src/tools/schedule/update.ts +2 -10
  185. package/src/tools/side-effects.ts +2 -17
  186. package/src/tools/skills/execute.ts +34 -0
  187. package/src/tools/subagent/spawn.ts +34 -9
  188. package/src/tools/tool-approval-handler.ts +1 -3
  189. package/src/tools/tool-manifest.ts +0 -2
  190. package/src/tools/ui-surface/definitions.ts +39 -1
  191. package/src/tools/workflows/run-workflow.test.ts +8 -18
  192. package/src/util/platform.ts +2 -2
  193. package/src/workflows/capabilities.ts +2 -3
  194. package/src/workflows/run-manager.test.ts +0 -25
  195. package/src/workflows/run-manager.ts +2 -24
  196. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  197. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  198. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  199. package/src/__tests__/credential-vault.test.ts +0 -1706
  200. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  201. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  202. package/src/cli/commands/__tests__/task.test.ts +0 -914
  203. package/src/cli/commands/task.ts +0 -771
  204. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  205. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  206. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  207. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  208. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  209. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  210. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  211. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  212. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  213. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  214. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  215. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  216. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  217. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  218. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  219. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  220. package/src/memory/preloaded-apps.ts +0 -116
  221. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  222. package/src/tools/credentials/vault.ts +0 -712
@@ -129,7 +129,7 @@ export function redeemInvite(params: {
129
129
  : undefined;
130
130
  const contactResult = findContactChannel({
131
131
  channelType: sourceChannel,
132
- externalUserId: canonicalUserId,
132
+ address: canonicalUserId,
133
133
  externalChatId: externalChatId,
134
134
  });
135
135
  const existingChannel = contactResult?.channel ?? null;
@@ -359,7 +359,7 @@ export function redeemVoiceInviteCode(params: {
359
359
  callerExternalUserId;
360
360
  const voiceContactResult = findContactChannel({
361
361
  channelType: "phone",
362
- externalUserId: canonicalCallerId,
362
+ address: canonicalCallerId,
363
363
  });
364
364
  const existingVoiceChannel = voiceContactResult?.channel ?? null;
365
365
  const voiceContact = voiceContactResult?.contact ?? null;
@@ -511,7 +511,7 @@ export function redeemInviteByCode(params: {
511
511
  : undefined;
512
512
  const contactResult = findContactChannel({
513
513
  channelType: sourceChannel,
514
- externalUserId: canonicalUserId,
514
+ address: canonicalUserId,
515
515
  externalChatId: externalChatId,
516
516
  });
517
517
  const existingChannel = contactResult?.channel ?? null;
@@ -34,6 +34,12 @@
34
34
 
35
35
  import { beforeEach, describe, expect, mock, test } from "bun:test";
36
36
 
37
+ import {
38
+ type DiffPluginDeps,
39
+ type DiffPluginOptions,
40
+ type PluginDiffResult,
41
+ PluginDiffUnavailableError,
42
+ } from "../../../cli/lib/diff-plugin.js";
37
43
  import {
38
44
  type InspectPluginDeps,
39
45
  type InspectPluginOptions,
@@ -66,8 +72,10 @@ import {
66
72
  type UninstallPluginResult,
67
73
  } from "../../../cli/lib/uninstall-plugin.js";
68
74
  import {
75
+ PluginMergeBaselineError,
69
76
  PluginNotUpgradableError,
70
77
  type PluginUpgradeResult,
78
+ PluginUpgradeStrategyUnsupportedError,
71
79
  type UpgradePluginDeps,
72
80
  type UpgradePluginOptions,
73
81
  } from "../../../cli/lib/upgrade-plugin.js";
@@ -173,10 +181,29 @@ const upgradeSpy = mock(
173
181
  );
174
182
 
175
183
  mock.module("../../../cli/lib/upgrade-plugin.js", () => ({
184
+ PluginMergeBaselineError,
176
185
  PluginNotUpgradableError,
186
+ PluginUpgradeStrategyUnsupportedError,
177
187
  upgradePlugin: upgradeSpy,
178
188
  }));
179
189
 
190
+ // Mock diffPlugin: the lib re-materializes the install commit and computes the
191
+ // per-file unified diff (covered by diff-plugin.test.ts); the route forwards
192
+ // the name and maps the lib's error taxonomy to HTTP status codes.
193
+ const diffSpy = mock(
194
+ async (
195
+ _opts: DiffPluginOptions,
196
+ _deps: DiffPluginDeps,
197
+ ): Promise<PluginDiffResult> => {
198
+ throw new Error("diffSpy default impl not configured");
199
+ },
200
+ );
201
+
202
+ mock.module("../../../cli/lib/diff-plugin.js", () => ({
203
+ PluginDiffUnavailableError,
204
+ diffPlugin: diffSpy,
205
+ }));
206
+
180
207
  import {
181
208
  BadRequestError,
182
209
  ConflictError,
@@ -200,6 +227,7 @@ const getHandler = findHandler("plugins_get");
200
227
  const installHandler = findHandler("plugins_install");
201
228
  const inspectHandler = findHandler("plugins_inspect");
202
229
  const upgradeHandler = findHandler("plugins_upgrade");
230
+ const diffHandler = findHandler("plugins_diff");
203
231
 
204
232
  function invoke(args: RouteHandlerArgs = {}): {
205
233
  plugins: Array<Record<string, unknown>>;
@@ -1080,6 +1108,7 @@ function upgradeResult(
1080
1108
  target: overrides.target ?? "/workspace/.vellum/plugins/level-up",
1081
1109
  fileCount: overrides.fileCount === undefined ? 12 : overrides.fileCount,
1082
1110
  dryRun: overrides.dryRun ?? false,
1111
+ strategy: overrides.strategy ?? "overwrite",
1083
1112
  provenanceWasUnknown: overrides.provenanceWasUnknown ?? false,
1084
1113
  };
1085
1114
  }
@@ -1094,6 +1123,7 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
1094
1123
  target: string;
1095
1124
  fileCount: number | null;
1096
1125
  dryRun: boolean;
1126
+ strategy: string;
1097
1127
  provenanceWasUnknown: boolean;
1098
1128
  }> {
1099
1129
  return (await upgradeHandler(args)) as {
@@ -1106,6 +1136,7 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
1106
1136
  target: string;
1107
1137
  fileCount: number | null;
1108
1138
  dryRun: boolean;
1139
+ strategy: string;
1109
1140
  provenanceWasUnknown: boolean;
1110
1141
  };
1111
1142
  }
@@ -1136,13 +1167,69 @@ describe("POST /v1/plugins/:name/upgrade", () => {
1136
1167
  target: "/workspace/.vellum/plugins/level-up",
1137
1168
  fileCount: 12,
1138
1169
  dryRun: false,
1170
+ strategy: "overwrite",
1139
1171
  provenanceWasUnknown: false,
1140
1172
  });
1141
- // AND the name + dryRun are forwarded to the lib
1173
+ // AND the name + dryRun are forwarded to the lib (strategy omitted)
1142
1174
  expect(upgradeSpy.mock.calls[0]?.[0]).toEqual({
1143
1175
  name: "level-up",
1144
1176
  dryRun: false,
1177
+ strategy: undefined,
1178
+ });
1179
+ });
1180
+
1181
+ test("forwards a requested strategy to the lib and projects it", async () => {
1182
+ // GIVEN upgradePlugin merges local edits forward via the `ours` strategy
1183
+ upgradeSpy.mockImplementation(async () =>
1184
+ upgradeResult({ strategy: "ours" }),
1185
+ );
1186
+
1187
+ // WHEN the handler runs with a strategy in the body
1188
+ const result = await invokeUpgrade({
1189
+ pathParams: { name: "level-up" },
1190
+ body: { strategy: "ours" },
1191
+ });
1192
+
1193
+ // THEN the strategy is forwarded to the lib and surfaced on the wire
1194
+ expect(result.strategy).toBe("ours");
1195
+ expect(upgradeSpy.mock.calls[0]?.[0]).toEqual({
1196
+ name: "level-up",
1197
+ dryRun: undefined,
1198
+ strategy: "ours",
1199
+ });
1200
+ });
1201
+
1202
+ test("PluginUpgradeStrategyUnsupportedError \u2192 BadRequestError (400)", async () => {
1203
+ // The `assistant` strategy is recognized but not yet implemented: a
1204
+ // well-formed request the server cannot honor.
1205
+ upgradeSpy.mockImplementation(async () => {
1206
+ throw new PluginUpgradeStrategyUnsupportedError("assistant");
1207
+ });
1208
+
1209
+ await expect(
1210
+ invokeUpgrade({
1211
+ pathParams: { name: "level-up" },
1212
+ body: { strategy: "assistant" },
1213
+ }),
1214
+ ).rejects.toBeInstanceOf(BadRequestError);
1215
+ });
1216
+
1217
+ test("PluginMergeBaselineError \u2192 ConflictError (409)", async () => {
1218
+ // A merge strategy whose install-time baseline can't be reconstructed: a
1219
+ // well-formed request that isn't actionable in the current state.
1220
+ upgradeSpy.mockImplementation(async () => {
1221
+ throw new PluginMergeBaselineError(
1222
+ "level-up",
1223
+ "the install-time baseline could not be faithfully reconstructed",
1224
+ );
1145
1225
  });
1226
+
1227
+ await expect(
1228
+ invokeUpgrade({
1229
+ pathParams: { name: "level-up" },
1230
+ body: { strategy: "ours" },
1231
+ }),
1232
+ ).rejects.toBeInstanceOf(ConflictError);
1146
1233
  });
1147
1234
 
1148
1235
  test("omits dryRun (passes undefined) when the body flag is absent", async () => {
@@ -1246,3 +1333,133 @@ describe("POST /v1/plugins/:name/upgrade", () => {
1246
1333
  expect((caught as Error).message).toContain("ECONNRESET");
1247
1334
  });
1248
1335
  });
1336
+
1337
+ function diffResult(
1338
+ overrides: Partial<PluginDiffResult> = {},
1339
+ ): PluginDiffResult {
1340
+ return {
1341
+ name: overrides.name ?? "level-up",
1342
+ target: overrides.target ?? "/workspace/.vellum/plugins/level-up",
1343
+ commit: overrides.commit ?? "60a392b0000000000000000000000000000000aa",
1344
+ committedAt: overrides.committedAt ?? "2026-06-01T12:34:56.000Z",
1345
+ clean: overrides.clean ?? false,
1346
+ files: overrides.files ?? [
1347
+ {
1348
+ path: "src/skill.ts",
1349
+ status: "modified",
1350
+ diff: "--- a/src/skill.ts\n+++ b/src/skill.ts\n@@ -1 +1 @@\n-old\n+new\n",
1351
+ binary: false,
1352
+ reconstructed: true,
1353
+ },
1354
+ ],
1355
+ };
1356
+ }
1357
+
1358
+ async function invokeDiff(
1359
+ args: RouteHandlerArgs = {},
1360
+ ): Promise<PluginDiffResult> {
1361
+ return (await diffHandler(args)) as PluginDiffResult;
1362
+ }
1363
+
1364
+ describe("POST /v1/plugins/:name/diff", () => {
1365
+ beforeEach(() => {
1366
+ diffSpy.mockReset();
1367
+ });
1368
+
1369
+ test("forwards the name to diffPlugin and returns the diff verbatim", async () => {
1370
+ // GIVEN diffPlugin reports a single modified file against the install commit
1371
+ const view = diffResult();
1372
+ diffSpy.mockImplementation(async () => view);
1373
+
1374
+ // WHEN the route handler is invoked with the path name
1375
+ const result = await invokeDiff({ pathParams: { name: "level-up" } });
1376
+
1377
+ // THEN the diff is returned unchanged
1378
+ expect(result).toEqual(view);
1379
+ // AND only the name is forwarded to the lib (ref is never caller-supplied)
1380
+ expect(diffSpy.mock.calls[0]?.[0]).toEqual({ name: "level-up" });
1381
+ });
1382
+
1383
+ test("InvalidPluginNameError → BadRequestError (400)", async () => {
1384
+ diffSpy.mockImplementation(async () => {
1385
+ throw new InvalidPluginNameError("../escape");
1386
+ });
1387
+
1388
+ await expect(
1389
+ invokeDiff({ pathParams: { name: "../escape" } }),
1390
+ ).rejects.toBeInstanceOf(BadRequestError);
1391
+ });
1392
+
1393
+ test("PluginNotInstalledError → NotFoundError (404)", async () => {
1394
+ diffSpy.mockImplementation(async () => {
1395
+ throw new PluginNotInstalledError(
1396
+ "ghost",
1397
+ "/workspace/.vellum/plugins/ghost",
1398
+ );
1399
+ });
1400
+
1401
+ await expect(
1402
+ invokeDiff({ pathParams: { name: "ghost" } }),
1403
+ ).rejects.toBeInstanceOf(NotFoundError);
1404
+ });
1405
+
1406
+ test("PluginNotFoundError → NotFoundError (404)", async () => {
1407
+ // The recorded commit no longer resolves to a tree (source/commit gone).
1408
+ diffSpy.mockImplementation(async () => {
1409
+ throw new PluginNotFoundError(
1410
+ "level-up",
1411
+ "deadbeef",
1412
+ "vellum-ai/level-up",
1413
+ );
1414
+ });
1415
+
1416
+ await expect(
1417
+ invokeDiff({ pathParams: { name: "level-up" } }),
1418
+ ).rejects.toBeInstanceOf(NotFoundError);
1419
+ });
1420
+
1421
+ test("PluginDiffUnavailableError → ConflictError (409)", async () => {
1422
+ // The install recorded no commit, so there is no baseline to diff against:
1423
+ // a well-formed request that is not actionable in the current state.
1424
+ diffSpy.mockImplementation(async () => {
1425
+ throw new PluginDiffUnavailableError(
1426
+ "level-up",
1427
+ "no install commit was recorded",
1428
+ );
1429
+ });
1430
+
1431
+ await expect(
1432
+ invokeDiff({ pathParams: { name: "level-up" } }),
1433
+ ).rejects.toBeInstanceOf(ConflictError);
1434
+ });
1435
+
1436
+ test("PluginSourceUnavailableError → ServiceUnavailableError (503)", async () => {
1437
+ // Re-materializing the baseline can hit a rate-limited/down GitHub; that is
1438
+ // retryable, so surface 503 rather than a misleading 500.
1439
+ diffSpy.mockImplementation(async () => {
1440
+ throw new PluginSourceUnavailableError(
1441
+ "git clone failed for vellum-ai/level-up: HTTP 403",
1442
+ 503,
1443
+ );
1444
+ });
1445
+
1446
+ await expect(
1447
+ invokeDiff({ pathParams: { name: "level-up" } }),
1448
+ ).rejects.toBeInstanceOf(ServiceUnavailableError);
1449
+ });
1450
+
1451
+ test("unknown errors → InternalError with original message preserved", async () => {
1452
+ diffSpy.mockImplementation(async () => {
1453
+ throw new Error("ECONNRESET");
1454
+ });
1455
+
1456
+ let caught: unknown;
1457
+ try {
1458
+ await invokeDiff({ pathParams: { name: "level-up" } });
1459
+ } catch (err) {
1460
+ caught = err;
1461
+ }
1462
+ expect(caught).toBeInstanceOf(InternalError);
1463
+ expect((caught as Error).message).toContain("ECONNRESET");
1464
+ });
1465
+ });
@@ -45,7 +45,7 @@ function loadDesignSystemCss(): string {
45
45
  try {
46
46
  const cssPath = join(
47
47
  import.meta.dirname ?? __dirname,
48
- "../../../../clients/macos/vellum-assistant/Resources/vellum-design-system.css",
48
+ "assets/vellum-design-system.css",
49
49
  );
50
50
  designSystemCssCache = readFileSync(cssPath, "utf-8");
51
51
  } catch {
@@ -763,7 +763,7 @@ async function handleAccessRequestApproval(
763
763
  const requesterContactResult = approval.requesterExternalUserId
764
764
  ? findContactChannel({
765
765
  channelType: approval.channel,
766
- externalUserId: approval.requesterExternalUserId,
766
+ address: approval.requesterExternalUserId,
767
767
  })
768
768
  : null;
769
769
  const requesterDisplayName =
@@ -772,7 +772,7 @@ async function handleAccessRequestApproval(
772
772
  const decidedByContactResult = decidedByExternalUserId
773
773
  ? findContactChannel({
774
774
  channelType: approval.channel,
775
- externalUserId: decidedByExternalUserId,
775
+ address: decidedByExternalUserId,
776
776
  })
777
777
  : null;
778
778
  const decidedByDisplayName =