@vellumai/cli 0.8.10-dev.202606110059.319a8d3 → 0.8.10-dev.202606110240.ef9212e
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/node_modules/@vellumai/local-mode/src/lockfile.test.ts +169 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.ts +9 -4
- package/package.json +1 -1
- package/src/__tests__/upgrade-replay-env.test.ts +16 -0
- package/src/commands/client.ts +1 -0
- package/src/lib/docker.ts +3 -1
- package/src/lib/upgrade-lifecycle.ts +6 -3
|
@@ -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
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
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
|
@@ -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" },
|
package/src/commands/client.ts
CHANGED
|
@@ -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:
|
|
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
|
|
223
|
-
* (captured
|
|
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
|
|
241
|
+
extraAssistantEnv,
|
|
239
242
|
extraGatewayEnv,
|
|
240
243
|
};
|
|
241
244
|
}
|