@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.
@@ -44,7 +44,6 @@ export {
44
44
  export {
45
45
  getLockfileData,
46
46
  renameLockfileAssistantIfPresent,
47
- stampLockfileAssistantOnboardedIfPresent,
48
47
  upsertLockfileAssistant,
49
48
  upsertRendererLockfileAssistant,
50
49
  replacePlatformAssistants,
@@ -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
- const entry = assistants[idx]!;
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] = { ...entry, ...patch };
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
- // `onboardedAt` is recorded by the client and unknown to the platform, so
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.11.8-dev.202609012313.7a1acce",
3
+ "version": "0.11.8-staging.2",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {