@frockbot/plugin-settings 0.2.2 → 0.2.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-settings",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,14 +21,14 @@
21
21
  "typecheck": "vue-tsc --noEmit -p tsconfig.json"
22
22
  },
23
23
  "dependencies": {
24
- "@frockbot/catalog-core": "0.2.2",
25
- "@frockbot/client-core": "0.2.2",
26
- "@frockbot/client-ui": "0.2.2",
27
- "@frockbot/configuration-core": "0.2.2",
28
- "@frockbot/connection-core": "0.2.2",
29
- "@frockbot/kernel-composition": "0.2.2",
30
- "@frockbot/plugin-auth": "0.2.2",
31
- "@frockbot/plugin-shell": "0.2.2",
24
+ "@frockbot/catalog-core": "0.2.4",
25
+ "@frockbot/client-core": "0.2.4",
26
+ "@frockbot/client-ui": "0.2.4",
27
+ "@frockbot/configuration-core": "0.2.4",
28
+ "@frockbot/connection-core": "0.2.4",
29
+ "@frockbot/kernel-composition": "0.2.4",
30
+ "@frockbot/plugin-auth": "0.2.4",
31
+ "@frockbot/plugin-shell": "0.2.4",
32
32
  "cordis": "4.0.0-rc.8",
33
33
  "vue": "3.5.41"
34
34
  },
package/src/user.test.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  } from "@frockbot/configuration-core";
7
7
  import {
8
8
  createUserSettingsBackendContribution,
9
+ type AvailableUserPackage,
9
10
  type UserPackageCatalogHost,
10
11
  type UserSettingsStorage,
11
12
  } from "./user.js";
@@ -246,6 +247,166 @@ describe("User settings backend Contribution", () => {
246
247
  ).toEqual(first);
247
248
  });
248
249
 
250
+ test("repairs a pre-default-enablement account before Package enablement", async () => {
251
+ const storage = new MemoryStorage();
252
+ const availablePackages: AvailableUserPackage[] = [
253
+ {
254
+ packageId: "custom-models",
255
+ version: "0.0.1",
256
+ installByDefault: true,
257
+ defaultEnablement: "disabled",
258
+ dependencies: {
259
+ settings: ">=0.0.1",
260
+ shell: ">=0.0.1",
261
+ "ui-theme": ">=0.0.1",
262
+ },
263
+ },
264
+ {
265
+ packageId: "provider-ollama-cloud",
266
+ version: "0.0.1",
267
+ installByDefault: true,
268
+ defaultEnablement: "disabled",
269
+ dependencies: {
270
+ "custom-models": ">=0.0.1",
271
+ credentials: ">=0.0.1",
272
+ settings: ">=0.0.1",
273
+ web: ">=0.0.1",
274
+ },
275
+ },
276
+ { packageId: "credentials", version: "0.0.1", installByDefault: true },
277
+ { packageId: "settings", version: "0.0.1", installByDefault: true },
278
+ { packageId: "shell", version: "0.0.1", installByDefault: true },
279
+ { packageId: "ui-theme", version: "0.0.1", installByDefault: true },
280
+ { packageId: "web", version: "0.0.1", installByDefault: true },
281
+ ];
282
+ await storage.put("user-id", "legacy-user");
283
+ await storage.put("user-default-packages-bootstrap:v1", {
284
+ schemaVersion: 1,
285
+ });
286
+ await storage.put("user-configuration", {
287
+ schemaVersion: 1,
288
+ revision: 12,
289
+ profile: { name: "Legacy User" },
290
+ packages: [
291
+ {
292
+ packageId: "provider-workers-ai",
293
+ version: "0.0.1",
294
+ state: "installed",
295
+ },
296
+ {
297
+ packageId: "provider-ollama-cloud",
298
+ version: "0.0.1",
299
+ state: "installed",
300
+ },
301
+ ],
302
+ connections: [
303
+ {
304
+ connectionId: "workers-ai-ambient",
305
+ packageId: "provider-workers-ai",
306
+ connectionTypeId: "workers-ai-account",
307
+ displayName: "Cloudflare Workers AI",
308
+ state: "ready",
309
+ generation: "workers-ai-ambient-v1",
310
+ providerType: "workers-ai",
311
+ safeMetadata: { catalog: "static" },
312
+ },
313
+ {
314
+ connectionId: "ollama-legacy",
315
+ packageId: "provider-ollama-cloud",
316
+ connectionTypeId: "ollama-cloud-account",
317
+ displayName: "Ollama",
318
+ state: "ready",
319
+ generation: "ollama-generation-1",
320
+ providerType: "ollama-cloud",
321
+ safeMetadata: {
322
+ dependentAssignments: [
323
+ {
324
+ botId: "primary",
325
+ generation: "assignment-1",
326
+ packageId: "provider-ollama-cloud",
327
+ capabilityId: "ollama-cloud-models",
328
+ claimOrder: 1,
329
+ status: "acknowledged",
330
+ },
331
+ ],
332
+ },
333
+ },
334
+ ],
335
+ platformModel: {
336
+ connectionId: "workers-ai-ambient",
337
+ providerModelId: "@cf/deepseek-ai/deepseek-v4-flash-0731",
338
+ },
339
+ newBotModelTemplate: {
340
+ connectionId: "ollama-legacy",
341
+ providerModelId: "glm-5.3-flash:cloud",
342
+ },
343
+ newBotModelTemplateSource: "user",
344
+ });
345
+ const settings = createUserSettingsBackendContribution({
346
+ storage,
347
+ availablePackages,
348
+ });
349
+
350
+ const migrated = await settings.readConfiguration({
351
+ schemaVersion: 1,
352
+ userId: "legacy-user",
353
+ });
354
+ expect(migrated.packages).toEqual([
355
+ {
356
+ packageId: "provider-ollama-cloud",
357
+ version: "0.0.1",
358
+ state: "disabled",
359
+ },
360
+ {
361
+ packageId: "custom-models",
362
+ version: "0.0.1",
363
+ state: "disabled",
364
+ provenance: "first-party",
365
+ },
366
+ ...availablePackages.slice(2).map(({ packageId, version }) => ({
367
+ packageId,
368
+ version,
369
+ state: "installed" as const,
370
+ provenance: "first-party" as const,
371
+ })),
372
+ ]);
373
+ expect(migrated.connections).toEqual([
374
+ expect.objectContaining({
375
+ connectionId: "ollama-legacy",
376
+ safeMetadata: {},
377
+ }),
378
+ ]);
379
+ expect(migrated.platformModel).toBeUndefined();
380
+
381
+ const customModels = await settings.executeConfiguration({
382
+ schemaVersion: 1,
383
+ userId: "legacy-user",
384
+ command: {
385
+ schemaVersion: 1,
386
+ type: "user/set-package-enabled",
387
+ commandId: "enable-custom-models-after-migration",
388
+ expectedRevision: migrated.revision,
389
+ packageId: "custom-models",
390
+ enabled: true,
391
+ },
392
+ });
393
+ expect(customModels).toMatchObject({ status: "applied" });
394
+ await expect(
395
+ settings.executeConfiguration({
396
+ schemaVersion: 1,
397
+ userId: "legacy-user",
398
+ command: {
399
+ schemaVersion: 1,
400
+ type: "user/set-package-enabled",
401
+ commandId: "enable-ollama-after-migration",
402
+ expectedRevision: customModels.revision,
403
+ packageId: "provider-ollama-cloud",
404
+ enabled: true,
405
+ },
406
+ }),
407
+ ).resolves.toMatchObject({ status: "applied" });
408
+ });
409
+
249
410
  test("installs a Package disabled when explicitly requested", async () => {
250
411
  const settings = contribution();
251
412
  const receipt = await settings.executeConfiguration({
@@ -417,6 +578,32 @@ describe("User settings backend Contribution", () => {
417
578
  const storage = new MemoryStorage();
418
579
  const settings = contribution(storage);
419
580
 
581
+ await settings.executeConfigurationCommand(
582
+ "user-1",
583
+ {
584
+ schemaVersion: 1,
585
+ type: "user/install-package",
586
+ commandId: "bootstrap-platform-package",
587
+ expectedRevision: 0,
588
+ packageId: "flock",
589
+ version: "0.0.1",
590
+ },
591
+ storage,
592
+ );
593
+ await settings.createConnection(
594
+ "user-1",
595
+ {
596
+ connectionId: "flock-default",
597
+ packageId: "flock",
598
+ connectionTypeId: "flock-account",
599
+ displayName: "Flock",
600
+ state: "ready",
601
+ providerType: "flock",
602
+ safeMetadata: {},
603
+ },
604
+ storage,
605
+ );
606
+
420
607
  await expect(
421
608
  settings.executeConfigurationCommand(
422
609
  "user-1",
@@ -424,7 +611,7 @@ describe("User settings backend Contribution", () => {
424
611
  schemaVersion: 1,
425
612
  type: "user/set-platform-model",
426
613
  commandId: "bootstrap-platform-model",
427
- expectedRevision: 0,
614
+ expectedRevision: 2,
428
615
  model: {
429
616
  connectionId: "flock-default",
430
617
  providerModelId: "@flock/auto",
@@ -432,7 +619,7 @@ describe("User settings backend Contribution", () => {
432
619
  },
433
620
  storage,
434
621
  ),
435
- ).resolves.toMatchObject({ status: "applied", revision: 1 });
622
+ ).resolves.toMatchObject({ status: "applied", revision: 3 });
436
623
  expect((await settings.read("user-1")).platformModel).toEqual({
437
624
  connectionId: "flock-default",
438
625
  providerModelId: "@flock/auto",
package/src/user.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  type PackageSettingValueV1,
18
18
  type OperationReceiptV1,
19
19
  type PackageInstallationView,
20
+ type StoredUserSettingsPackageV1,
20
21
  type UserConfigurationCommandV1,
21
22
  type UserSettingsViewV1,
22
23
  } from "@frockbot/configuration-core";
@@ -33,6 +34,7 @@ import type { Plugin } from "cordis";
33
34
 
34
35
  const STATE_KEY = "user-configuration";
35
36
  const DEFAULT_PACKAGES_BOOTSTRAP_KEY = "user-default-packages-bootstrap:v1";
37
+ const DEFAULT_PACKAGES_BOOTSTRAP_VERSION = 2;
36
38
  /**
37
39
  * The pinned Catalog generation lives beside the settings view rather than in
38
40
  * it, so pinning on a read never bumps the settings revision a client is
@@ -397,6 +399,9 @@ function applyUserCommand(
397
399
  export class UserSettingsBackendContribution {
398
400
  private readonly availablePackages: ReadonlySet<string>;
399
401
 
402
+ /** Catalog-relative facts supplied to the raw stored-settings migration. */
403
+ private readonly storedSettingsPackages: readonly StoredUserSettingsPackageV1[];
404
+
400
405
  /** Declared Package dependencies, by Package id and version. */
401
406
  private readonly packageDependencies: ReadonlyMap<
402
407
  string,
@@ -406,6 +411,13 @@ export class UserSettingsBackendContribution {
406
411
  /** The immutable first-party installation rows written on first read. */
407
412
  private readonly defaultPackages: readonly PackageInstallationView[];
408
413
 
414
+ /**
415
+ * Packages introduced by the default-enablement rollout, plus their
416
+ * dependency closure. A v1 marker predates those rows, so this is the one
417
+ * bounded set that marker migration may add without replaying every default.
418
+ */
419
+ private readonly enablementRolloutPackages: readonly PackageInstallationView[];
420
+
409
421
  /** Declared User-level settings, by Package id and version. */
410
422
  private readonly packageSettingDefinitions: ReadonlyMap<
411
423
  string,
@@ -420,6 +432,11 @@ export class UserSettingsBackendContribution {
420
432
  >();
421
433
 
422
434
  constructor(private readonly host: UserSettingsBackendHost) {
435
+ this.storedSettingsPackages = host.availablePackages.map((pkg) => ({
436
+ packageId: pkg.packageId,
437
+ version: pkg.version,
438
+ ...(pkg.dependencies ? { dependencies: pkg.dependencies } : {}),
439
+ }));
423
440
  this.availablePackages = new Set(
424
441
  host.availablePackages.map(
425
442
  ({ packageId, version }) => `${packageId}\u0000${version}`,
@@ -452,6 +469,26 @@ export class UserSettingsBackendContribution {
452
469
  ]
453
470
  : [],
454
471
  );
472
+ const byPackageId = new Map(
473
+ host.availablePackages.map((pkg) => [pkg.packageId, pkg]),
474
+ );
475
+ const rolloutPackageIds = new Set(
476
+ host.availablePackages
477
+ .filter(
478
+ (pkg) => pkg.installByDefault && pkg.defaultEnablement !== undefined,
479
+ )
480
+ .map((pkg) => pkg.packageId),
481
+ );
482
+ for (const packageId of rolloutPackageIds) {
483
+ for (const dependencyId of Object.keys(
484
+ byPackageId.get(packageId)?.dependencies ?? {},
485
+ )) {
486
+ if (byPackageId.has(dependencyId)) rolloutPackageIds.add(dependencyId);
487
+ }
488
+ }
489
+ this.enablementRolloutPackages = this.defaultPackages.filter((pkg) =>
490
+ rolloutPackageIds.has(pkg.packageId),
491
+ );
455
492
  }
456
493
 
457
494
  /**
@@ -480,11 +517,49 @@ export class UserSettingsBackendContribution {
480
517
  typeof marker !== "object" ||
481
518
  Array.isArray(marker) ||
482
519
  Object.keys(marker).length !== 1 ||
483
- (marker as { schemaVersion?: unknown }).schemaVersion !== 1
520
+ ((marker as { schemaVersion?: unknown }).schemaVersion !== 1 &&
521
+ (marker as { schemaVersion?: unknown }).schemaVersion !==
522
+ DEFAULT_PACKAGES_BOOTSTRAP_VERSION)
484
523
  ) {
485
524
  throw new Error("Stored default Package bootstrap is invalid");
486
525
  }
487
- return this.readSnapshot(transaction);
526
+ if (
527
+ (marker as { schemaVersion: number }).schemaVersion ===
528
+ DEFAULT_PACKAGES_BOOTSTRAP_VERSION
529
+ ) {
530
+ return this.readSnapshot(transaction);
531
+ }
532
+ const stored = await transaction.get<unknown>(STATE_KEY);
533
+ const current =
534
+ stored === undefined
535
+ ? initialState()
536
+ : decodeUserSettingsViewV1(
537
+ migrateStoredUserSettingsV1(
538
+ stored,
539
+ this.storedSettingsPackages,
540
+ ),
541
+ );
542
+ const installedPackageIds = new Set(
543
+ current.packages.map((pkg) => pkg.packageId),
544
+ );
545
+ const additions = this.enablementRolloutPackages.filter(
546
+ (pkg) => !installedPackageIds.has(pkg.packageId),
547
+ );
548
+ const next = {
549
+ ...current,
550
+ revision: current.revision + 1,
551
+ packages: [
552
+ ...current.packages,
553
+ ...additions.map((pkg) => structuredClone(pkg)),
554
+ ],
555
+ } satisfies UserSettingsViewV1;
556
+ await transaction.put({
557
+ [STATE_KEY]: next,
558
+ [DEFAULT_PACKAGES_BOOTSTRAP_KEY]: {
559
+ schemaVersion: DEFAULT_PACKAGES_BOOTSTRAP_VERSION,
560
+ },
561
+ });
562
+ return structuredClone(next);
488
563
  }
489
564
  const current = await this.readSnapshot(transaction);
490
565
  const installedPackageIds = new Set(
@@ -503,7 +578,9 @@ export class UserSettingsBackendContribution {
503
578
  } satisfies UserSettingsViewV1;
504
579
  await transaction.put({
505
580
  [STATE_KEY]: next,
506
- [DEFAULT_PACKAGES_BOOTSTRAP_KEY]: { schemaVersion: 1 },
581
+ [DEFAULT_PACKAGES_BOOTSTRAP_KEY]: {
582
+ schemaVersion: DEFAULT_PACKAGES_BOOTSTRAP_VERSION,
583
+ },
507
584
  });
508
585
  return structuredClone(next);
509
586
  };