@vellumai/cli 0.10.4-dev.202607022227.14c7770 → 0.10.4-staging.2

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.
@@ -14,18 +14,9 @@ export {
14
14
  resolveDevCliInvocation,
15
15
  } from "./util";
16
16
  export type { CliInvocation } from "./util";
17
- export {
18
- resolveLocalConfigFromEnv,
19
- resolveLockfilePaths,
20
- resolveConfigDir,
21
- guardianTokenPath,
22
- } from "./config";
17
+ export { resolveLocalConfigFromEnv, resolveLockfilePaths, resolveConfigDir, guardianTokenPath } from "./config";
23
18
  export type { LocalEndpointConfig } from "./config";
24
- export {
25
- defaultEnvironmentFilePath,
26
- readDefaultEnvironment,
27
- resolveEnvironmentName,
28
- } from "./environment";
19
+ export { defaultEnvironmentFilePath, readDefaultEnvironment, resolveEnvironmentName } from "./environment";
29
20
  export {
30
21
  getLockfileData,
31
22
  upsertLockfileAssistant,
@@ -43,7 +34,7 @@ export type {
43
34
  export { runHatch } from "./hatch";
44
35
  export type { HatchResult } from "./hatch";
45
36
  export { runRetire } from "./retire";
46
- export type { RetireOptions, RetireResult } from "./retire";
37
+ export type { RetireResult } from "./retire";
47
38
  export { runSleep } from "./sleep";
48
39
  export type { SleepResult } from "./sleep";
49
40
  export { runWake } from "./wake";
@@ -21,9 +21,6 @@ describe("parseLockfile", () => {
21
21
  species: "vellum",
22
22
  hatchedAt: "2026-01-01T00:00:00.000Z",
23
23
  organizationId: "org_1",
24
- platformAssistantId: "platform-assistant-1",
25
- platformBaseUrl: "https://platform.example.com",
26
- platformOrganizationId: "org_1",
27
24
  resources: { gatewayPort: 7777, daemonPort: 7778 },
28
25
  },
29
26
  ],
@@ -277,19 +274,13 @@ describe("parseLockfile", () => {
277
274
  expect(parseLockfile(raw).assistants).toEqual([
278
275
  { assistantId: "gcp_1", cloud: "gcp", runtimeUrl: "https://a" },
279
276
  { assistantId: "ssh_1", cloud: "custom", runtimeUrl: "https://b" },
280
- {
281
- assistantId: "local_1",
282
- cloud: "local",
283
- runtimeUrl: "http://localhost:7830",
284
- },
277
+ { assistantId: "local_1", cloud: "local", runtimeUrl: "http://localhost:7830" },
285
278
  ]);
286
279
  });
287
280
 
288
281
  test("prefers an explicit cloud over legacy remote markers", () => {
289
282
  const raw = {
290
- assistants: [
291
- { assistantId: "a", cloud: "vellum", project: "stale-proj" },
292
- ],
283
+ assistants: [{ assistantId: "a", cloud: "vellum", project: "stale-proj" }],
293
284
  activeAssistant: null,
294
285
  };
295
286
  expect(parseLockfile(raw).assistants).toEqual([
@@ -109,12 +109,6 @@ export const LockfileAssistantSchema = z.object({
109
109
  hatchedAt: z.string().optional(),
110
110
  /** Owning org for platform assistants; absent for local ones. */
111
111
  organizationId: z.string().optional(),
112
- /** Platform assistant UUID for a self-hosted local assistant registration. */
113
- platformAssistantId: z.string().optional(),
114
- /** Platform base URL used for a self-hosted local assistant registration. */
115
- platformBaseUrl: z.string().optional(),
116
- /** Platform organization UUID used for a self-hosted local assistant registration. */
117
- platformOrganizationId: z.string().optional(),
118
112
  resources: LocalAssistantResourcesSchema.optional(),
119
113
  });
120
114
 
@@ -171,32 +171,6 @@ describe("upsertLockfileAssistant", () => {
171
171
  name: "Renamed",
172
172
  });
173
173
  });
174
-
175
- test("preserves activeAssistant when no active id is provided", () => {
176
- writeOnDisk({
177
- activeAssistant: "asst_active",
178
- assistants: [
179
- {
180
- assistantId: "asst_active",
181
- cloud: "local",
182
- runtimeUrl: "http://active",
183
- },
184
- { assistantId: "asst_1", cloud: "local", runtimeUrl: "http://a" },
185
- ],
186
- });
187
-
188
- const result = upsertLockfileAssistant(
189
- [lockfilePath],
190
- { assistantId: "asst_1", name: "Renamed" },
191
- undefined,
192
- );
193
-
194
- expect(result.ok).toBe(true);
195
- expect(readOnDisk().activeAssistant).toBe("asst_active");
196
- if (result.ok) {
197
- expect(result.lockfile.activeAssistant).toBe("asst_active");
198
- }
199
- });
200
174
  });
201
175
 
202
176
  describe("replacePlatformAssistants", () => {
@@ -296,9 +270,7 @@ describe("replacePlatformAssistants", () => {
296
270
  "org_a",
297
271
  );
298
272
 
299
- const assistants = readOnDisk().assistants as Array<
300
- Record<string, unknown>
301
- >;
273
+ const assistants = readOnDisk().assistants as Array<Record<string, unknown>>;
302
274
  expect(assistants).toHaveLength(1);
303
275
  expect(assistants[0]).toMatchObject({
304
276
  assistantId: "asst_dup",
@@ -5,32 +5,18 @@ import type { CliInvocation } from "./util";
5
5
  const RETIRE_TIMEOUT_MS = 60_000;
6
6
 
7
7
  export type RetireResult =
8
- { ok: true } | { ok: false; status: number; error: string };
9
-
10
- export interface RetireOptions {
11
- platformToken?: string;
12
- }
8
+ | { ok: true }
9
+ | { ok: false; status: number; error: string };
13
10
 
14
11
  export function runRetire(
15
12
  invocation: CliInvocation,
16
13
  assistantId: string,
17
- options: RetireOptions = {},
18
14
  ): Promise<RetireResult> {
19
15
  return new Promise((resolve) => {
20
16
  const child = spawn(
21
17
  invocation.command,
22
18
  [...invocation.baseArgs, "retire", assistantId, "--yes"],
23
- {
24
- ...(options.platformToken
25
- ? {
26
- env: {
27
- ...process.env,
28
- VELLUM_PLATFORM_TOKEN: options.platformToken,
29
- },
30
- }
31
- : {}),
32
- stdio: ["ignore", "pipe", "pipe"],
33
- },
19
+ { stdio: ["ignore", "pipe", "pipe"] },
34
20
  );
35
21
 
36
22
  let stdout = "";
@@ -46,11 +32,7 @@ export function runRetire(
46
32
 
47
33
  const timeout = setTimeout(() => {
48
34
  child.kill("SIGTERM");
49
- finish({
50
- ok: false,
51
- status: 500,
52
- error: "Retire timed out after 60 seconds",
53
- });
35
+ finish({ ok: false, status: 500, error: "Retire timed out after 60 seconds" });
54
36
  }, RETIRE_TIMEOUT_MS);
55
37
 
56
38
  child.stdout.on("data", (data: Buffer) => {
@@ -70,11 +52,7 @@ export function runRetire(
70
52
  });
71
53
 
72
54
  child.on("error", (err) => {
73
- finish({
74
- ok: false,
75
- status: 500,
76
- error: `Failed to spawn CLI: ${err.message}`,
77
- });
55
+ finish({ ok: false, status: 500, error: `Failed to spawn CLI: ${err.message}` });
78
56
  });
79
57
  });
80
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.10.4-dev.202607022227.14c7770",
3
+ "version": "0.10.4-staging.2",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,7 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import {
4
- fetchLatestVersion,
5
4
  fetchReleases,
6
5
  resolveImageRefsDetailed,
7
6
  } from "../lib/platform-releases.js";
@@ -115,45 +114,4 @@ describe("resolveImageRefsDetailed", () => {
115
114
  const result = await resolveImageRefsDetailed("0.7.0");
116
115
  expect(result.status).toBe("dockerhub-fallback");
117
116
  });
118
-
119
- test("requests the preview channel when channel=preview", async () => {
120
- const fetchMock = mockFetchJson([RELEASE]);
121
- await resolveImageRefsDetailed("v0.7.0", undefined, "preview");
122
- const url = String(fetchMock.mock.calls[0][0]);
123
- expect(url).toContain("channel=preview");
124
- expect(url).not.toContain("stable=true");
125
- });
126
-
127
- test("keeps the stable filter when channel=stable (default)", async () => {
128
- const fetchMock = mockFetchJson([RELEASE]);
129
- await resolveImageRefsDetailed("v0.7.0", undefined, "stable");
130
- const url = String(fetchMock.mock.calls[0][0]);
131
- expect(url).toContain("stable=true");
132
- expect(url).not.toContain("channel=");
133
- });
134
- });
135
-
136
- describe("fetchLatestVersion", () => {
137
- test("defaults to the stable channel", async () => {
138
- const fetchMock = mockFetchJson([RELEASE]);
139
- const version = await fetchLatestVersion();
140
- expect(version).toBe("0.7.0");
141
- const url = String(fetchMock.mock.calls[0][0]);
142
- expect(url).toContain("stable=true");
143
- expect(url).not.toContain("channel=");
144
- });
145
-
146
- test("requests the preview channel when asked", async () => {
147
- const preview = { ...RELEASE, version: "0.8.0-preview.3" };
148
- const fetchMock = mockFetchJson([preview]);
149
- const version = await fetchLatestVersion("preview");
150
- expect(version).toBe("0.8.0-preview.3");
151
- const url = String(fetchMock.mock.calls[0][0]);
152
- expect(url).toContain("channel=preview");
153
- });
154
-
155
- test("returns null when the channel has no releases", async () => {
156
- mockFetchJson([]);
157
- expect(await fetchLatestVersion("preview")).toBeNull();
158
- });
159
117
  });
@@ -20,74 +20,30 @@ import { join } from "node:path";
20
20
 
21
21
  import type { AssistantEntry } from "../lib/assistant-config.js";
22
22
  import { loadAllAssistants } from "../lib/assistant-config.js";
23
- import * as loopbackFetchModule from "../lib/loopback-fetch.js";
24
- import * as platformClientModule from "../lib/platform-client.js";
25
23
  import * as retireLocalModule from "../lib/retire-local.js";
26
24
 
27
25
  const testDir = mkdtempSync(join(tmpdir(), "cli-retire-test-"));
28
26
  const originalArgv = [...process.argv];
29
27
  const originalExit = process.exit;
30
28
  const originalLockfileDir = process.env.VELLUM_LOCKFILE_DIR;
31
- const originalPlatformToken = process.env.VELLUM_PLATFORM_TOKEN;
32
29
  const originalStdinIsTTY = process.stdin.isTTY;
33
30
  const originalStdoutIsTTY = process.stdout.isTTY;
34
31
  const originalStdinIsRaw = process.stdin.isRaw;
35
32
  const originalSetRawMode = process.stdin.setRawMode;
36
33
  const originalStdoutWrite = process.stdout.write;
37
34
  const realRetireLocalModule = { ...retireLocalModule };
38
- const realPlatformClientModule = { ...platformClientModule };
39
- const realLoopbackFetchModule = { ...loopbackFetchModule };
40
35
 
41
36
  const retireLocalMock = mock(async () => {});
42
- const readPlatformTokenMock = mock((): string | null => null);
43
- const authHeadersMock = mock(async () => ({
44
- "Content-Type": "application/json",
45
- "X-Session-Token": "session-token",
46
- }));
47
- const authHeadersForKnownOrganizationMock = mock(
48
- (token: string, organizationId: string) => ({
49
- "Content-Type": "application/json",
50
- "X-Session-Token": token,
51
- "Vellum-Organization-Id": organizationId,
52
- }),
53
- );
54
- const getPlatformUrlMock = mock(() => "https://platform.example.com");
55
- const readGatewayCredentialMock = mock(
56
- async (
57
- _gatewayUrl: string,
58
- _name: string,
59
- _bearerToken?: string,
60
- ): Promise<{ value: string | null; unreachable: boolean }> => ({
61
- value: null,
62
- unreachable: false,
63
- }),
64
- );
65
- const loopbackSafeFetchMock = mock(
66
- async (): Promise<Response> => new Response(null, { status: 204 }),
67
- );
68
37
 
69
38
  mock.module("../lib/retire-local.js", () => ({
70
39
  ...realRetireLocalModule,
71
40
  retireLocal: retireLocalMock,
72
41
  }));
73
42
 
74
- mock.module("../lib/platform-client.js", () => ({
75
- authHeaders: authHeadersMock,
76
- authHeadersForKnownOrganization: authHeadersForKnownOrganizationMock,
77
- getPlatformUrl: getPlatformUrlMock,
78
- readGatewayCredential: readGatewayCredentialMock,
79
- readPlatformToken: readPlatformTokenMock,
80
- }));
81
-
82
- mock.module("../lib/loopback-fetch.js", () => ({
83
- loopbackSafeFetch: loopbackSafeFetchMock,
84
- }));
85
-
86
43
  import { retire } from "../commands/retire.js";
87
44
 
88
45
  let consoleLogSpy: ReturnType<typeof spyOn>;
89
46
  let consoleErrorSpy: ReturnType<typeof spyOn>;
90
- let consoleWarnSpy: ReturnType<typeof spyOn>;
91
47
 
92
48
  function makeEntry(
93
49
  assistantId: string,
@@ -167,7 +123,6 @@ function restoreTerminal(): void {
167
123
  describe("vellum retire", () => {
168
124
  beforeEach(() => {
169
125
  process.env.VELLUM_LOCKFILE_DIR = testDir;
170
- delete process.env.VELLUM_PLATFORM_TOKEN;
171
126
  rmSync(join(testDir, ".vellum.lock.json"), { force: true });
172
127
  process.argv = ["bun", "vellum", "retire"];
173
128
  process.exit = ((code?: number) => {
@@ -175,36 +130,9 @@ describe("vellum retire", () => {
175
130
  }) as typeof process.exit;
176
131
  retireLocalMock.mockReset();
177
132
  retireLocalMock.mockResolvedValue(undefined);
178
- readPlatformTokenMock.mockReset();
179
- readPlatformTokenMock.mockReturnValue(null);
180
- authHeadersMock.mockReset();
181
- authHeadersMock.mockResolvedValue({
182
- "Content-Type": "application/json",
183
- "X-Session-Token": "session-token",
184
- });
185
- authHeadersForKnownOrganizationMock.mockReset();
186
- authHeadersForKnownOrganizationMock.mockImplementation(
187
- (token: string, organizationId: string) => ({
188
- "Content-Type": "application/json",
189
- "X-Session-Token": token,
190
- "Vellum-Organization-Id": organizationId,
191
- }),
192
- );
193
- getPlatformUrlMock.mockReset();
194
- getPlatformUrlMock.mockReturnValue("https://platform.example.com");
195
- readGatewayCredentialMock.mockReset();
196
- readGatewayCredentialMock.mockResolvedValue({
197
- value: null,
198
- unreachable: false,
199
- });
200
- loopbackSafeFetchMock.mockReset();
201
- loopbackSafeFetchMock.mockResolvedValue(
202
- new Response(null, { status: 204 }),
203
- );
204
133
  setTerminalMode(false);
205
134
  consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
206
135
  consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
207
- consoleWarnSpy = spyOn(console, "warn").mockImplementation(() => {});
208
136
  });
209
137
 
210
138
  afterEach(() => {
@@ -213,23 +141,15 @@ describe("vellum retire", () => {
213
141
  restoreTerminal();
214
142
  consoleLogSpy.mockRestore();
215
143
  consoleErrorSpy.mockRestore();
216
- consoleWarnSpy.mockRestore();
217
144
  });
218
145
 
219
146
  afterAll(() => {
220
147
  mock.module("../lib/retire-local.js", () => realRetireLocalModule);
221
- mock.module("../lib/platform-client.js", () => realPlatformClientModule);
222
- mock.module("../lib/loopback-fetch.js", () => realLoopbackFetchModule);
223
148
  if (originalLockfileDir === undefined) {
224
149
  delete process.env.VELLUM_LOCKFILE_DIR;
225
150
  } else {
226
151
  process.env.VELLUM_LOCKFILE_DIR = originalLockfileDir;
227
152
  }
228
- if (originalPlatformToken === undefined) {
229
- delete process.env.VELLUM_PLATFORM_TOKEN;
230
- } else {
231
- process.env.VELLUM_PLATFORM_TOKEN = originalPlatformToken;
232
- }
233
153
  rmSync(testDir, { recursive: true, force: true });
234
154
  });
235
155
 
@@ -250,279 +170,6 @@ describe("vellum retire", () => {
250
170
  );
251
171
  });
252
172
 
253
- test("local retire unregisters the self-hosted platform record first", async () => {
254
- const entry = makeEntry("assistant-1", { name: "Example Assistant" });
255
- writeLockfile([entry]);
256
- readPlatformTokenMock.mockReturnValue("session-token");
257
- getPlatformUrlMock.mockReturnValue("https://platform.example.test/api");
258
- readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
259
- if (name === "vellum:platform_assistant_id") {
260
- return { value: "platform-assistant-1", unreachable: false };
261
- }
262
- if (name === "vellum:platform_base_url") {
263
- return { value: "https://platform.example.test", unreachable: false };
264
- }
265
- if (name === "vellum:platform_organization_id") {
266
- return { value: "org-123", unreachable: false };
267
- }
268
- return { value: null, unreachable: false };
269
- });
270
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
271
-
272
- await retire();
273
-
274
- expect(readGatewayCredentialMock).toHaveBeenCalledWith(
275
- entry.localUrl ?? entry.runtimeUrl,
276
- "vellum:platform_assistant_id",
277
- entry.bearerToken,
278
- );
279
- expect(authHeadersMock).not.toHaveBeenCalled();
280
- expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
281
- "https://platform.example.test/api/v1/assistants/platform-assistant-1/retire/",
282
- {
283
- method: "DELETE",
284
- headers: {
285
- "Content-Type": "application/json",
286
- "X-Session-Token": "session-token",
287
- "Vellum-Organization-Id": "org-123",
288
- },
289
- },
290
- );
291
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
292
- expect(loadAllAssistants()).toEqual([]);
293
- });
294
-
295
- test("local retire reads platform registration through the loopback gateway URL", async () => {
296
- const localGatewayUrl = "http://127.0.0.1:7831";
297
- const entry = makeEntry("assistant-1", {
298
- name: "Example Assistant",
299
- runtimeUrl: "http://assistant-1.local:7831",
300
- localUrl: localGatewayUrl,
301
- });
302
- writeLockfile([entry]);
303
- readPlatformTokenMock.mockReturnValue("session-token");
304
- getPlatformUrlMock.mockReturnValue("https://platform.example.test");
305
- readGatewayCredentialMock.mockImplementation(async (gatewayUrl, name) => {
306
- expect(gatewayUrl).toBe(localGatewayUrl);
307
- if (name === "vellum:platform_assistant_id") {
308
- return { value: "platform-assistant-1", unreachable: false };
309
- }
310
- if (name === "vellum:platform_base_url") {
311
- return { value: "https://platform.example.test", unreachable: false };
312
- }
313
- if (name === "vellum:platform_organization_id") {
314
- return { value: "org-123", unreachable: false };
315
- }
316
- return { value: null, unreachable: false };
317
- });
318
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
319
-
320
- await retire();
321
-
322
- expect(readGatewayCredentialMock).toHaveBeenCalledWith(
323
- localGatewayUrl,
324
- "vellum:platform_assistant_id",
325
- entry.bearerToken,
326
- );
327
- expect(readGatewayCredentialMock).toHaveBeenCalledWith(
328
- localGatewayUrl,
329
- "vellum:platform_base_url",
330
- entry.bearerToken,
331
- );
332
- expect(readGatewayCredentialMock).toHaveBeenCalledWith(
333
- localGatewayUrl,
334
- "vellum:platform_organization_id",
335
- entry.bearerToken,
336
- );
337
- expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
338
- "https://platform.example.test/v1/assistants/platform-assistant-1/retire/",
339
- expect.any(Object),
340
- );
341
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
342
- });
343
-
344
- test("local retire uses saved platform registration when local credentials are unreachable", async () => {
345
- const entry = makeEntry("assistant-1", {
346
- name: "Example Assistant",
347
- platformAssistantId: "platform-assistant-1",
348
- platformBaseUrl: "https://platform.example.test",
349
- platformOrganizationId: "org-123",
350
- });
351
- writeLockfile([entry]);
352
- readPlatformTokenMock.mockReturnValue("session-token");
353
- getPlatformUrlMock.mockReturnValue("https://platform.example.test");
354
- readGatewayCredentialMock.mockResolvedValue({
355
- value: null,
356
- unreachable: true,
357
- });
358
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
359
-
360
- await retire();
361
-
362
- expect(readGatewayCredentialMock).toHaveBeenCalledWith(
363
- entry.localUrl ?? entry.runtimeUrl,
364
- "vellum:platform_assistant_id",
365
- entry.bearerToken,
366
- );
367
- expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
368
- "https://platform.example.test/v1/assistants/platform-assistant-1/retire/",
369
- {
370
- method: "DELETE",
371
- headers: {
372
- "Content-Type": "application/json",
373
- "X-Session-Token": "session-token",
374
- "Vellum-Organization-Id": "org-123",
375
- },
376
- },
377
- );
378
- expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
379
- "using saved platform registration",
380
- );
381
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
382
- expect(loadAllAssistants()).toEqual([]);
383
- });
384
-
385
- test("local retire continues when local credentials are unreachable and no saved platform registration exists", async () => {
386
- const entry = makeEntry("assistant-1", { name: "Example Assistant" });
387
- writeLockfile([entry]);
388
- readPlatformTokenMock.mockReturnValue("session-token");
389
- readGatewayCredentialMock.mockResolvedValue({
390
- value: null,
391
- unreachable: true,
392
- });
393
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
394
-
395
- await retire();
396
-
397
- expect(loopbackSafeFetchMock).not.toHaveBeenCalled();
398
- expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
399
- "no saved platform registration is available",
400
- );
401
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
402
- expect(loadAllAssistants()).toEqual([]);
403
- });
404
-
405
- test("local retire skips platform unregister when stored platform URL origin mismatches", async () => {
406
- const entry = makeEntry("assistant-1", { name: "Example Assistant" });
407
- writeLockfile([entry]);
408
- readPlatformTokenMock.mockReturnValue("session-token");
409
- getPlatformUrlMock.mockReturnValue("https://platform.example.com");
410
- readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
411
- if (name === "vellum:platform_assistant_id") {
412
- return { value: "platform-assistant-1", unreachable: false };
413
- }
414
- if (name === "vellum:platform_base_url") {
415
- return { value: "https://malicious.example.test", unreachable: false };
416
- }
417
- if (name === "vellum:platform_organization_id") {
418
- return { value: "org-123", unreachable: false };
419
- }
420
- return { value: null, unreachable: false };
421
- });
422
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
423
-
424
- await retire();
425
-
426
- expect(loopbackSafeFetchMock).not.toHaveBeenCalled();
427
- expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
428
- "Stored platform URL does not match the configured platform URL",
429
- );
430
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
431
- });
432
-
433
- test("local retire skips platform unregister when stored organization ID is missing", async () => {
434
- const entry = makeEntry("assistant-1", { name: "Example Assistant" });
435
- writeLockfile([entry]);
436
- readPlatformTokenMock.mockReturnValue("session-token");
437
- getPlatformUrlMock.mockReturnValue("https://platform.example.test");
438
- readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
439
- if (name === "vellum:platform_assistant_id") {
440
- return { value: "platform-assistant-1", unreachable: false };
441
- }
442
- if (name === "vellum:platform_base_url") {
443
- return { value: "https://platform.example.test", unreachable: false };
444
- }
445
- return { value: null, unreachable: false };
446
- });
447
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
448
-
449
- await retire();
450
-
451
- expect(loopbackSafeFetchMock).not.toHaveBeenCalled();
452
- expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
453
- "Could not read platform organization ID",
454
- );
455
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
456
- expect(loadAllAssistants()).toEqual([]);
457
- });
458
-
459
- test("local retire uses injected platform token from the app host", async () => {
460
- const entry = makeEntry("assistant-1", { name: "Example Assistant" });
461
- writeLockfile([entry]);
462
- process.env.VELLUM_PLATFORM_TOKEN = "host-session-token";
463
- readPlatformTokenMock.mockReturnValue(null);
464
- getPlatformUrlMock.mockReturnValue("https://platform.example.test");
465
- readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
466
- if (name === "vellum:platform_assistant_id") {
467
- return { value: "platform-assistant-1", unreachable: false };
468
- }
469
- if (name === "vellum:platform_base_url") {
470
- return { value: "https://platform.example.test", unreachable: false };
471
- }
472
- if (name === "vellum:platform_organization_id") {
473
- return { value: "org-123", unreachable: false };
474
- }
475
- return { value: null, unreachable: false };
476
- });
477
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
478
-
479
- await retire();
480
-
481
- expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
482
- "https://platform.example.test/v1/assistants/platform-assistant-1/retire/",
483
- {
484
- method: "DELETE",
485
- headers: {
486
- "Content-Type": "application/json",
487
- "X-Session-Token": "host-session-token",
488
- "Vellum-Organization-Id": "org-123",
489
- },
490
- },
491
- );
492
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
493
- });
494
-
495
- test("local retire continues when self-hosted platform unregister fails", async () => {
496
- const entry = makeEntry("assistant-1", { name: "Example Assistant" });
497
- writeLockfile([entry]);
498
- readPlatformTokenMock.mockReturnValue("session-token");
499
- getPlatformUrlMock.mockReturnValue("https://platform.example.test");
500
- readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
501
- if (name === "vellum:platform_assistant_id") {
502
- return { value: "platform-assistant-1", unreachable: false };
503
- }
504
- if (name === "vellum:platform_base_url") {
505
- return { value: "https://platform.example.test", unreachable: false };
506
- }
507
- if (name === "vellum:platform_organization_id") {
508
- return { value: "org-123", unreachable: false };
509
- }
510
- return { value: null, unreachable: false };
511
- });
512
- loopbackSafeFetchMock.mockResolvedValue(
513
- new Response("platform unavailable", { status: 503 }),
514
- );
515
- process.argv = ["bun", "vellum", "retire", "assistant-1", "--yes"];
516
-
517
- await retire();
518
-
519
- expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
520
- expect(loadAllAssistants()).toEqual([]);
521
- expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
522
- "Platform self-hosted unregister failed (503): platform unavailable",
523
- );
524
- });
525
-
526
173
  test("non-interactive retire without --yes fails before deleting", async () => {
527
174
  const entry = makeEntry("assistant-1", { name: "Example Assistant" });
528
175
  writeLockfile([entry]);
@@ -2266,14 +2266,6 @@ describe("platform credential injection", () => {
2266
2266
  userId: "user-1",
2267
2267
  webhookSecret: "webhook-secret-123",
2268
2268
  });
2269
- expect(saveAssistantEntryMock).toHaveBeenCalledWith(
2270
- expect.objectContaining({
2271
- assistantId: "my-local",
2272
- platformAssistantId: "platform-assistant-1",
2273
- platformBaseUrl: "https://platform.vellum.ai",
2274
- platformOrganizationId: "org-1",
2275
- }),
2276
- );
2277
2269
  } finally {
2278
2270
  restoreFetch();
2279
2271
  }