@vellumai/cli 0.11.8-dev.202609012313.7a1acce → 0.11.8-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.
|
@@ -179,12 +179,6 @@ export const LockfileAssistantSchema = z.object({
|
|
|
179
179
|
runtimeUrl: z.string().optional(),
|
|
180
180
|
species: z.string().optional(),
|
|
181
181
|
hatchedAt: z.string().optional(),
|
|
182
|
-
/**
|
|
183
|
-
* When this assistant finished first-run onboarding. Absent for entries that
|
|
184
|
-
* predate the record, which fall back to hatch age (see the web client's
|
|
185
|
-
* `onboarded-assistant.ts`).
|
|
186
|
-
*/
|
|
187
|
-
onboardedAt: z.string().optional(),
|
|
188
182
|
/** Owning org for platform assistants; absent for local ones. */
|
|
189
183
|
organizationId: z.string().optional(),
|
|
190
184
|
/** Platform assistant UUID for a self-hosted local assistant registration. */
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
getLockfileData,
|
|
8
8
|
isPairedLockfileEntry,
|
|
9
9
|
renameLockfileAssistantIfPresent,
|
|
10
|
-
stampLockfileAssistantOnboardedIfPresent,
|
|
11
10
|
replacePlatformAssistants,
|
|
12
11
|
upsertLockfileAssistant,
|
|
13
12
|
upsertRendererLockfileAssistant,
|
|
@@ -281,87 +280,6 @@ describe("upsertLockfileAssistant", () => {
|
|
|
281
280
|
});
|
|
282
281
|
});
|
|
283
282
|
|
|
284
|
-
describe("stampLockfileAssistantOnboardedIfPresent", () => {
|
|
285
|
-
const entry = {
|
|
286
|
-
assistantId: "asst_1",
|
|
287
|
-
cloud: "local",
|
|
288
|
-
name: "Credence",
|
|
289
|
-
signingKey: "sk-on-disk-secret",
|
|
290
|
-
};
|
|
291
|
-
const AT = "2026-08-31T00:00:00.000Z";
|
|
292
|
-
|
|
293
|
-
test("stamps the entry preserving its other fields and activeAssistant", () => {
|
|
294
|
-
writeOnDisk({ activeAssistant: "asst_other", assistants: [entry] });
|
|
295
|
-
|
|
296
|
-
const result = stampLockfileAssistantOnboardedIfPresent(
|
|
297
|
-
[lockfilePath],
|
|
298
|
-
"asst_1",
|
|
299
|
-
AT,
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
expect(result.ok).toBe(true);
|
|
303
|
-
const onDisk = readOnDisk();
|
|
304
|
-
expect(onDisk.activeAssistant).toBe("asst_other");
|
|
305
|
-
expect(onDisk.assistants).toEqual([{ ...entry, onboardedAt: AT }]);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Update-only: a completion racing a CLI retire must not resurrect the row.
|
|
309
|
-
test("refuses a missing entry without writing the file", () => {
|
|
310
|
-
const result = stampLockfileAssistantOnboardedIfPresent(
|
|
311
|
-
[lockfilePath],
|
|
312
|
-
"asst_gone",
|
|
313
|
-
AT,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
expect(result).toEqual({
|
|
317
|
-
ok: false,
|
|
318
|
-
status: 404,
|
|
319
|
-
error: "No lockfile entry for this assistant",
|
|
320
|
-
});
|
|
321
|
-
expect(fs.existsSync(lockfilePath)).toBe(false);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
test("keeps an existing stamp: the first completion is the real one", () => {
|
|
325
|
-
const stamped = { ...entry, onboardedAt: "2026-08-01T00:00:00.000Z" };
|
|
326
|
-
writeOnDisk({ activeAssistant: null, assistants: [stamped] });
|
|
327
|
-
|
|
328
|
-
const result = stampLockfileAssistantOnboardedIfPresent(
|
|
329
|
-
[lockfilePath],
|
|
330
|
-
"asst_1",
|
|
331
|
-
AT,
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
expect(result.ok).toBe(true);
|
|
335
|
-
expect(readOnDisk().assistants).toEqual([stamped]);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("refuses a corrupt on-disk file without clobbering it", () => {
|
|
339
|
-
fs.writeFileSync(lockfilePath, "{ not json");
|
|
340
|
-
|
|
341
|
-
const result = stampLockfileAssistantOnboardedIfPresent(
|
|
342
|
-
[lockfilePath],
|
|
343
|
-
"asst_1",
|
|
344
|
-
AT,
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
expect(result.ok).toBe(false);
|
|
348
|
-
if (!result.ok) {
|
|
349
|
-
expect(result.status).toBe(409);
|
|
350
|
-
}
|
|
351
|
-
expect(fs.readFileSync(lockfilePath, "utf-8")).toBe("{ not json");
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
test("rejects a missing id or timestamp", () => {
|
|
355
|
-
expect(
|
|
356
|
-
stampLockfileAssistantOnboardedIfPresent([lockfilePath], "", AT).ok,
|
|
357
|
-
).toBe(false);
|
|
358
|
-
expect(
|
|
359
|
-
stampLockfileAssistantOnboardedIfPresent([lockfilePath], "asst_1", "").ok,
|
|
360
|
-
).toBe(false);
|
|
361
|
-
expect(fs.existsSync(lockfilePath)).toBe(false);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
283
|
describe("renameLockfileAssistantIfPresent", () => {
|
|
366
284
|
const entry = {
|
|
367
285
|
assistantId: "asst_1",
|
|
@@ -678,89 +596,6 @@ describe("replacePlatformAssistants", () => {
|
|
|
678
596
|
});
|
|
679
597
|
});
|
|
680
598
|
|
|
681
|
-
// `onboardedAt` is recorded by the client and unknown to the platform, so the
|
|
682
|
-
// rows a sync builds from the API never carry it. Dropping it here would let
|
|
683
|
-
// a routine session refresh erase the completion record.
|
|
684
|
-
test("carries an existing onboardedAt onto the replacement row", () => {
|
|
685
|
-
writeOnDisk({
|
|
686
|
-
activeAssistant: "asst_p",
|
|
687
|
-
assistants: [
|
|
688
|
-
{
|
|
689
|
-
assistantId: "asst_p",
|
|
690
|
-
cloud: "vellum",
|
|
691
|
-
runtimeUrl: "http://old",
|
|
692
|
-
onboardedAt: "2026-08-01T00:00:00.000Z",
|
|
693
|
-
},
|
|
694
|
-
],
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
const result = replacePlatformAssistants(
|
|
698
|
-
[lockfilePath],
|
|
699
|
-
[{ assistantId: "asst_p", cloud: "vellum", runtimeUrl: "http://new" }],
|
|
700
|
-
);
|
|
701
|
-
|
|
702
|
-
expect(result.ok).toBe(true);
|
|
703
|
-
expect(readOnDisk().assistants).toEqual([
|
|
704
|
-
{
|
|
705
|
-
assistantId: "asst_p",
|
|
706
|
-
cloud: "vellum",
|
|
707
|
-
runtimeUrl: "http://new",
|
|
708
|
-
onboardedAt: "2026-08-01T00:00:00.000Z",
|
|
709
|
-
},
|
|
710
|
-
]);
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
test("an incoming onboardedAt wins over the stored one", () => {
|
|
714
|
-
writeOnDisk({
|
|
715
|
-
activeAssistant: null,
|
|
716
|
-
assistants: [
|
|
717
|
-
{
|
|
718
|
-
assistantId: "asst_p",
|
|
719
|
-
cloud: "vellum",
|
|
720
|
-
onboardedAt: "2026-08-01T00:00:00.000Z",
|
|
721
|
-
},
|
|
722
|
-
],
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
replacePlatformAssistants(
|
|
726
|
-
[lockfilePath],
|
|
727
|
-
[
|
|
728
|
-
{
|
|
729
|
-
assistantId: "asst_p",
|
|
730
|
-
cloud: "vellum",
|
|
731
|
-
onboardedAt: "2026-08-31T00:00:00.000Z",
|
|
732
|
-
},
|
|
733
|
-
],
|
|
734
|
-
);
|
|
735
|
-
|
|
736
|
-
expect(
|
|
737
|
-
(readOnDisk().assistants as Array<Record<string, unknown>>)[0]
|
|
738
|
-
?.onboardedAt,
|
|
739
|
-
).toBe("2026-08-31T00:00:00.000Z");
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
test("a retired-then-recreated id does not inherit a stale stamp from another entry", () => {
|
|
743
|
-
writeOnDisk({
|
|
744
|
-
activeAssistant: null,
|
|
745
|
-
assistants: [
|
|
746
|
-
{
|
|
747
|
-
assistantId: "asst_a",
|
|
748
|
-
cloud: "vellum",
|
|
749
|
-
onboardedAt: "2026-08-01T00:00:00.000Z",
|
|
750
|
-
},
|
|
751
|
-
],
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
replacePlatformAssistants(
|
|
755
|
-
[lockfilePath],
|
|
756
|
-
[{ assistantId: "asst_b", cloud: "vellum" }],
|
|
757
|
-
);
|
|
758
|
-
|
|
759
|
-
expect(readOnDisk().assistants).toEqual([
|
|
760
|
-
{ assistantId: "asst_b", cloud: "vellum" },
|
|
761
|
-
]);
|
|
762
|
-
});
|
|
763
|
-
|
|
764
599
|
test("replaces platform assistants while keeping local ones and unknown fields", () => {
|
|
765
600
|
writeOnDisk({
|
|
766
601
|
schemaVersion: 99,
|
|
@@ -181,49 +181,13 @@ export function renameLockfileAssistantIfPresent(
|
|
|
181
181
|
lockfilePaths: string[],
|
|
182
182
|
assistantId: string,
|
|
183
183
|
name: string,
|
|
184
|
-
): WriteResult {
|
|
185
|
-
if (typeof name !== "string" || name === "") {
|
|
186
|
-
return { ok: false, status: 400, error: "Missing name" };
|
|
187
|
-
}
|
|
188
|
-
return patchLockfileAssistantIfPresent(lockfilePaths, assistantId, { name });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Record when an assistant finished first-run onboarding. Update-only, on the
|
|
193
|
-
* same terms as {@link renameLockfileAssistantIfPresent}. Keeps an existing
|
|
194
|
-
* stamp: the first completion is the real one, and a replayed funnel must not
|
|
195
|
-
* move it.
|
|
196
|
-
*/
|
|
197
|
-
export function stampLockfileAssistantOnboardedIfPresent(
|
|
198
|
-
lockfilePaths: string[],
|
|
199
|
-
assistantId: string,
|
|
200
|
-
onboardedAt: string,
|
|
201
|
-
): WriteResult {
|
|
202
|
-
if (typeof onboardedAt !== "string" || onboardedAt === "") {
|
|
203
|
-
return { ok: false, status: 400, error: "Missing onboardedAt" };
|
|
204
|
-
}
|
|
205
|
-
return patchLockfileAssistantIfPresent(
|
|
206
|
-
lockfilePaths,
|
|
207
|
-
assistantId,
|
|
208
|
-
{ onboardedAt },
|
|
209
|
-
(entry) => typeof entry.onboardedAt === "string" && entry.onboardedAt !== "",
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* The shared update-only write: read the on-disk registry under the lock,
|
|
215
|
-
* refuse a missing entry, and merge `patch` into it. `skip` short-circuits to
|
|
216
|
-
* a successful no-op for a field whose current value should win.
|
|
217
|
-
*/
|
|
218
|
-
function patchLockfileAssistantIfPresent(
|
|
219
|
-
lockfilePaths: string[],
|
|
220
|
-
assistantId: string,
|
|
221
|
-
patch: Record<string, unknown>,
|
|
222
|
-
skip?: (entry: Record<string, unknown>) => boolean,
|
|
223
184
|
): WriteResult {
|
|
224
185
|
if (typeof assistantId !== "string" || assistantId === "") {
|
|
225
186
|
return { ok: false, status: 400, error: "Missing assistantId" };
|
|
226
187
|
}
|
|
188
|
+
if (typeof name !== "string" || name === "") {
|
|
189
|
+
return { ok: false, status: 400, error: "Missing name" };
|
|
190
|
+
}
|
|
227
191
|
|
|
228
192
|
const locked = withLockfileLock(lockfilePaths, (): WriteResult => {
|
|
229
193
|
const read = readRawLockfileStrict(lockfilePaths);
|
|
@@ -242,14 +206,10 @@ function patchLockfileAssistantIfPresent(
|
|
|
242
206
|
error: "No lockfile entry for this assistant",
|
|
243
207
|
};
|
|
244
208
|
}
|
|
245
|
-
|
|
246
|
-
const converged =
|
|
247
|
-
skip?.(entry) ??
|
|
248
|
-
Object.entries(patch).every(([key, value]) => entry[key] === value);
|
|
249
|
-
if (converged) {
|
|
209
|
+
if (assistants[idx]!.name === name) {
|
|
250
210
|
return { ok: true, lockfile: toWireLockfile(lockfile) };
|
|
251
211
|
}
|
|
252
|
-
assistants[idx] = { ...
|
|
212
|
+
assistants[idx] = { ...assistants[idx], name };
|
|
253
213
|
lockfile.assistants = assistants;
|
|
254
214
|
return writeRawLockfile(lockfilePaths, lockfile);
|
|
255
215
|
});
|
|
@@ -394,24 +354,7 @@ export function replacePlatformAssistants(
|
|
|
394
354
|
if (syncedIds.has(a.assistantId)) return false;
|
|
395
355
|
return organizationId != null && a.organizationId !== organizationId;
|
|
396
356
|
});
|
|
397
|
-
|
|
398
|
-
// the replacement rows never carry it. Without this it is erased by every
|
|
399
|
-
// routine session sync.
|
|
400
|
-
const priorOnboardedAt = new Map<string, unknown>();
|
|
401
|
-
for (const a of existing as Array<Record<string, unknown>>) {
|
|
402
|
-
if (typeof a?.assistantId === "string" && a.onboardedAt != null) {
|
|
403
|
-
priorOnboardedAt.set(a.assistantId, a.onboardedAt);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
lockfile.assistants = [
|
|
407
|
-
...preserved,
|
|
408
|
-
...platformAssistants.map((a) => {
|
|
409
|
-
const prior = priorOnboardedAt.get(a.assistantId as string);
|
|
410
|
-
return a.onboardedAt == null && prior != null
|
|
411
|
-
? { ...a, onboardedAt: prior }
|
|
412
|
-
: a;
|
|
413
|
-
}),
|
|
414
|
-
];
|
|
357
|
+
lockfile.assistants = [...preserved, ...platformAssistants];
|
|
415
358
|
|
|
416
359
|
const active = lockfile.activeAssistant as string | null;
|
|
417
360
|
if (active) {
|