@vellumai/cli 0.8.10-dev.202606110112.319a8d3 → 0.8.10-dev.202606110317.792ac3c

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.
@@ -207,6 +207,175 @@ describe("replacePlatformAssistants", () => {
207
207
  expect(ids).toEqual(["asst_local", "asst_new_platform"]);
208
208
  });
209
209
 
210
+ test("a sync scoped to one org preserves another org's platform entries", () => {
211
+ writeOnDisk({
212
+ activeAssistant: null,
213
+ assistants: [
214
+ {
215
+ assistantId: "asst_org_a",
216
+ cloud: "vellum",
217
+ organizationId: "org_a",
218
+ runtimeUrl: "http://a",
219
+ },
220
+ {
221
+ assistantId: "asst_org_b_old",
222
+ cloud: "vellum",
223
+ organizationId: "org_b",
224
+ runtimeUrl: "http://bo",
225
+ },
226
+ ],
227
+ });
228
+
229
+ replacePlatformAssistants(
230
+ [lockfilePath],
231
+ [
232
+ {
233
+ assistantId: "asst_org_b_new",
234
+ cloud: "vellum",
235
+ organizationId: "org_b",
236
+ runtimeUrl: "http://bn",
237
+ },
238
+ ],
239
+ "org_b",
240
+ );
241
+
242
+ const ids = (readOnDisk().assistants as Array<Record<string, unknown>>).map(
243
+ (a) => a.assistantId,
244
+ );
245
+ // Org A survives; Org B's stale entry is replaced by the new one.
246
+ expect(ids).toEqual(["asst_org_a", "asst_org_b_new"]);
247
+ });
248
+
249
+ test("de-duplicates a legacy no-org entry that shares an id with the new list", () => {
250
+ writeOnDisk({
251
+ activeAssistant: null,
252
+ assistants: [
253
+ // Legacy platform entry with no organizationId, same id as the sync.
254
+ { assistantId: "asst_dup", cloud: "vellum", runtimeUrl: "http://old" },
255
+ ],
256
+ });
257
+
258
+ replacePlatformAssistants(
259
+ [lockfilePath],
260
+ [
261
+ {
262
+ assistantId: "asst_dup",
263
+ cloud: "vellum",
264
+ organizationId: "org_a",
265
+ runtimeUrl: "http://new",
266
+ },
267
+ ],
268
+ "org_a",
269
+ );
270
+
271
+ const assistants = readOnDisk().assistants as Array<Record<string, unknown>>;
272
+ expect(assistants).toHaveLength(1);
273
+ expect(assistants[0]).toMatchObject({
274
+ assistantId: "asst_dup",
275
+ organizationId: "org_a",
276
+ runtimeUrl: "http://new",
277
+ });
278
+ });
279
+
280
+ test("full-replaces all platform entries when no org is given (legacy)", () => {
281
+ writeOnDisk({
282
+ activeAssistant: null,
283
+ assistants: [
284
+ {
285
+ assistantId: "asst_org_a",
286
+ cloud: "vellum",
287
+ organizationId: "org_a",
288
+ runtimeUrl: "http://a",
289
+ },
290
+ {
291
+ assistantId: "asst_org_b",
292
+ cloud: "vellum",
293
+ organizationId: "org_b",
294
+ runtimeUrl: "http://b",
295
+ },
296
+ ],
297
+ });
298
+
299
+ replacePlatformAssistants(
300
+ [lockfilePath],
301
+ [
302
+ {
303
+ assistantId: "asst_new",
304
+ cloud: "vellum",
305
+ runtimeUrl: "http://np",
306
+ },
307
+ ],
308
+ );
309
+
310
+ const ids = (readOnDisk().assistants as Array<Record<string, unknown>>).map(
311
+ (a) => a.assistantId,
312
+ );
313
+ expect(ids).toEqual(["asst_new"]);
314
+ });
315
+
316
+ test("local entries always survive an org-scoped sync", () => {
317
+ writeOnDisk({
318
+ activeAssistant: null,
319
+ assistants: [
320
+ { assistantId: "asst_local", cloud: "local", runtimeUrl: "http://l" },
321
+ {
322
+ assistantId: "asst_org_a",
323
+ cloud: "vellum",
324
+ organizationId: "org_a",
325
+ runtimeUrl: "http://a",
326
+ },
327
+ ],
328
+ });
329
+
330
+ replacePlatformAssistants(
331
+ [lockfilePath],
332
+ [
333
+ {
334
+ assistantId: "asst_org_a_new",
335
+ cloud: "vellum",
336
+ organizationId: "org_a",
337
+ runtimeUrl: "http://an",
338
+ },
339
+ ],
340
+ "org_a",
341
+ );
342
+
343
+ const ids = (readOnDisk().assistants as Array<Record<string, unknown>>).map(
344
+ (a) => a.assistantId,
345
+ );
346
+ expect(ids).toEqual(["asst_local", "asst_org_a_new"]);
347
+ });
348
+
349
+ test("keeps activeAssistant when it still resolves after an org-scoped sync", () => {
350
+ writeOnDisk({
351
+ activeAssistant: "asst_org_b_old",
352
+ assistants: [
353
+ {
354
+ assistantId: "asst_org_b_old",
355
+ cloud: "vellum",
356
+ organizationId: "org_b",
357
+ runtimeUrl: "http://bo",
358
+ },
359
+ ],
360
+ });
361
+
362
+ replacePlatformAssistants(
363
+ [lockfilePath],
364
+ [
365
+ {
366
+ assistantId: "asst_org_a",
367
+ cloud: "vellum",
368
+ organizationId: "org_a",
369
+ runtimeUrl: "http://a",
370
+ },
371
+ ],
372
+ "org_a",
373
+ );
374
+
375
+ // Org B's entry (and the active id pointing at it) survives the org-A sync.
376
+ expect(readOnDisk().activeAssistant).toBe("asst_org_b_old");
377
+ });
378
+
210
379
  test("clears activeAssistant when the active id no longer exists", () => {
211
380
  writeOnDisk({
212
381
  activeAssistant: "asst_old_platform",
@@ -106,6 +106,7 @@ export function isActiveAssistant(
106
106
  export function replacePlatformAssistants(
107
107
  lockfilePaths: string[],
108
108
  platformAssistants: Array<Record<string, unknown>>,
109
+ organizationId?: string,
109
110
  ): WriteResult {
110
111
  let lockfile: Record<string, unknown> = { assistants: [], activeAssistant: null };
111
112
  for (const candidate of lockfilePaths) {
@@ -118,10 +119,14 @@ export function replacePlatformAssistants(
118
119
  }
119
120
 
120
121
  const existing = Array.isArray(lockfile.assistants) ? lockfile.assistants : [];
121
- const local = existing.filter(
122
- (a: Record<string, unknown>) => a?.cloud !== "vellum",
123
- );
124
- lockfile.assistants = [...local, ...platformAssistants];
122
+ const syncedIds = new Set(platformAssistants.map((a) => a.assistantId));
123
+ // Org-scoped sync preserves other orgs' platform entries; no org full-replaces.
124
+ const preserved = existing.filter((a: Record<string, unknown>) => {
125
+ if (a?.cloud !== "vellum") return true;
126
+ if (syncedIds.has(a.assistantId)) return false;
127
+ return organizationId != null && a.organizationId !== organizationId;
128
+ });
129
+ lockfile.assistants = [...preserved, ...platformAssistants];
125
130
 
126
131
  const active = lockfile.activeAssistant as string | null;
127
132
  if (active) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.8.10-dev.202606110112.319a8d3",
3
+ "version": "0.8.10-dev.202606110317.792ac3c",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -130,6 +130,22 @@ describe("buildReplayState", () => {
130
130
  expect(state.extraGatewayEnv.VELLUM_DEVICE_ID).toBe("existing");
131
131
  });
132
132
 
133
+ test("backfills VELLUM_DEVICE_ID on assistant replay env when absent", () => {
134
+ const state = buildReplayState({}, {});
135
+ expect(state.extraAssistantEnv.VELLUM_DEVICE_ID).toBe("host-device-id");
136
+ });
137
+
138
+ test("assistant backfill inherits captured gateway VELLUM_DEVICE_ID", () => {
139
+ const state = buildReplayState({}, { VELLUM_DEVICE_ID: "gw-captured" });
140
+ expect(state.extraGatewayEnv.VELLUM_DEVICE_ID).toBe("gw-captured");
141
+ expect(state.extraAssistantEnv.VELLUM_DEVICE_ID).toBe("gw-captured");
142
+ });
143
+
144
+ test("captured assistant VELLUM_DEVICE_ID wins over host-derived id", () => {
145
+ const state = buildReplayState({ VELLUM_DEVICE_ID: "existing" }, {});
146
+ expect(state.extraAssistantEnv.VELLUM_DEVICE_ID).toBe("existing");
147
+ });
148
+
133
149
  test("plucks secrets from the captured envs", () => {
134
150
  const state = buildReplayState(
135
151
  { CES_SERVICE_TOKEN: "ces-token", ACTOR_TOKEN_SIGNING_KEY: "sign-key" },
@@ -458,6 +458,7 @@ async function handleLocalEndpoints(
458
458
  result = replacePlatformAssistants(
459
459
  lockfilePaths,
460
460
  body.platformAssistants as Array<Record<string, unknown>>,
461
+ body.organizationId as string | undefined,
461
462
  );
462
463
  } else {
463
464
  result = upsertLockfileAssistant(
package/src/lib/docker.ts CHANGED
@@ -1332,9 +1332,11 @@ export async function hatchDocker(
1332
1332
  extraAssistantEnv.VELLUM_DISABLE_PLATFORM =
1333
1333
  flagEnvVars.VELLUM_DISABLE_PLATFORM;
1334
1334
  }
1335
+ const hostDeviceId = getOrCreateHostDeviceId();
1336
+ extraAssistantEnv.VELLUM_DEVICE_ID = hostDeviceId;
1335
1337
  const extraGatewayEnv = {
1336
1338
  ...flagEnvVars,
1337
- VELLUM_DEVICE_ID: getOrCreateHostDeviceId(),
1339
+ VELLUM_DEVICE_ID: hostDeviceId,
1338
1340
  };
1339
1341
  await startContainers(
1340
1342
  {
@@ -219,8 +219,8 @@ interface ReplayState {
219
219
  * already-captured assistant/gateway envs. GUARDIAN_BOOTSTRAP_SECRET is only
220
220
  * set on the gateway; CES_SERVICE_TOKEN and ACTOR_TOKEN_SIGNING_KEY fall back
221
221
  * to fresh values for instances that predate them. VELLUM_DEVICE_ID is
222
- * backfilled from the host for gateways hatched before device-id injection
223
- * (captured value wins it was itself host-derived).
222
+ * backfilled on the gateway from the host, and the assistant inherits the
223
+ * gateway's value (captured values win) so the pair always matches.
224
224
  */
225
225
  export function buildReplayState(
226
226
  capturedEnv: Record<string, string>,
@@ -229,13 +229,16 @@ export function buildReplayState(
229
229
  const extraGatewayEnv = buildReplayEnv(gatewayEnv, "gateway");
230
230
  extraGatewayEnv.VELLUM_DEVICE_ID ??= getOrCreateHostDeviceId();
231
231
 
232
+ const extraAssistantEnv = buildReplayEnv(capturedEnv, "assistant");
233
+ extraAssistantEnv.VELLUM_DEVICE_ID ??= extraGatewayEnv.VELLUM_DEVICE_ID;
234
+
232
235
  return {
233
236
  bootstrapSecret: gatewayEnv["GUARDIAN_BOOTSTRAP_SECRET"],
234
237
  cesServiceToken:
235
238
  capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex"),
236
239
  signingKey:
237
240
  capturedEnv["ACTOR_TOKEN_SIGNING_KEY"] || randomBytes(32).toString("hex"),
238
- extraAssistantEnv: buildReplayEnv(capturedEnv, "assistant"),
241
+ extraAssistantEnv,
239
242
  extraGatewayEnv,
240
243
  };
241
244
  }