@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.
- package/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +4 -4
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +135 -59
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
- package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/config-loader-backfill.test.ts +103 -6
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +3 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -13
- package/src/__tests__/dynamic-page-surface.test.ts +101 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/invite-redemption-service.test.ts +4 -4
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +0 -22
- package/src/__tests__/llm-resolver.test.ts +49 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +11 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/skill-execute-input.test.ts +46 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/subagent-tools.test.ts +116 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +6 -6
- package/src/__tests__/voice-invite-redemption.test.ts +2 -2
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/agent/loop.ts +25 -5
- package/src/api/README.md +6 -6
- package/src/api/responses/conversation-message.ts +2 -4
- package/src/api/responses/home.ts +0 -4
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/plugins.ts +143 -2
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +228 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/upgrade-plugin.ts +270 -10
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +0 -1
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +12 -2
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/loader.ts +4 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/seed-inference-profiles.ts +25 -20
- package/src/contacts/contact-store.ts +87 -96
- package/src/contacts/contacts-write.ts +5 -21
- package/src/context/compactor.ts +2 -2
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop.ts +15 -4
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-tool-setup.ts +11 -3
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/handlers/config-channels.ts +20 -16
- package/src/daemon/handlers/config-slack-channel.ts +2 -3
- package/src/daemon/lifecycle.ts +0 -7
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/home/relationship-state.ts +2 -4
- package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/db-init.ts +7 -17
- package/src/memory/embedding-backend.ts +23 -0
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +52 -0
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-job.ts +19 -227
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/schema/contacts.ts +0 -4
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +5 -32
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/broadcaster.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +4 -3
- package/src/notifications/notification-utils.ts +17 -19
- package/src/notifications/types.ts +7 -0
- package/src/oauth/AGENTS.md +5 -24
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +6 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
- package/src/prompts/persona-resolver.ts +2 -2
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/actor-trust-resolver.ts +12 -44
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/channel-approval-types.ts +18 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-verification-service.ts +4 -3
- package/src/runtime/invite-redemption-service.ts +3 -3
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +171 -5
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/workflow-routes.test.ts +4 -43
- package/src/runtime/routes/workflow-routes.ts +0 -28
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/schedule/inference-profile.ts +2 -14
- package/src/subagent/manager.ts +6 -0
- package/src/subagent/types.ts +6 -0
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/schedule/create.ts +3 -9
- package/src/tools/schedule/update.ts +2 -10
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +34 -0
- package/src/tools/subagent/spawn.ts +34 -9
- package/src/tools/tool-approval-handler.ts +1 -3
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/util/platform.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/run-manager.test.ts +0 -25
- package/src/workflows/run-manager.ts +2 -24
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
775
|
+
address: decidedByExternalUserId,
|
|
776
776
|
})
|
|
777
777
|
: null;
|
|
778
778
|
const decidedByDisplayName =
|