@frockbot/plugin-settings 0.1.4 → 0.2.1

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.1.4",
3
+ "version": "0.2.1",
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.1.4",
25
- "@frockbot/client-core": "0.1.4",
26
- "@frockbot/client-ui": "0.1.4",
27
- "@frockbot/configuration-core": "0.1.4",
28
- "@frockbot/connection-core": "0.1.4",
29
- "@frockbot/kernel-composition": "0.1.4",
30
- "@frockbot/plugin-auth": "0.1.4",
31
- "@frockbot/plugin-shell": "0.1.4",
24
+ "@frockbot/catalog-core": "0.2.1",
25
+ "@frockbot/client-core": "0.2.1",
26
+ "@frockbot/client-ui": "0.2.1",
27
+ "@frockbot/configuration-core": "0.2.1",
28
+ "@frockbot/connection-core": "0.2.1",
29
+ "@frockbot/kernel-composition": "0.2.1",
30
+ "@frockbot/plugin-auth": "0.2.1",
31
+ "@frockbot/plugin-shell": "0.2.1",
32
32
  "cordis": "4.0.0-rc.8",
33
33
  "vue": "3.5.41"
34
34
  },
package/src/user.test.ts CHANGED
@@ -82,6 +82,75 @@ async function installOllama(
82
82
  }
83
83
 
84
84
  describe("User settings backend Contribution", () => {
85
+ test("reads old settings purely and writes the migrated shape on the next command", async () => {
86
+ const storage = new MemoryStorage();
87
+ // Literal durable shape from eb0283edcce5daea976a21a9f6a6414bedc6e2bc,
88
+ // the first parent of PR #134's merge commit.
89
+ const historical = {
90
+ schemaVersion: 1,
91
+ revision: 7,
92
+ profile: { name: "Existing User" },
93
+ packages: [],
94
+ connections: [
95
+ {
96
+ connectionId: "ollama-1",
97
+ packageId: "provider-ollama-cloud",
98
+ connectionTypeId: "ollama-cloud-account",
99
+ displayName: "Work",
100
+ state: "ready",
101
+ safeMetadata: {
102
+ dependentAssignments: [
103
+ {
104
+ botId: "primary",
105
+ generation: "assignment-1",
106
+ packageId: "provider-ollama-cloud",
107
+ capabilityId: "ollama-cloud-models",
108
+ claimOrder: 0,
109
+ status: "acknowledged",
110
+ },
111
+ ],
112
+ },
113
+ },
114
+ ],
115
+ newBotModelTemplate: {
116
+ connectionId: "ollama-1",
117
+ providerModelId: "glm-5.3-flash:cloud",
118
+ },
119
+ newBotModelTemplateSource: "user",
120
+ };
121
+ storage.values.set("user-configuration", structuredClone(historical));
122
+ const settings = contribution(storage);
123
+
124
+ await expect(settings.readSnapshot()).resolves.toMatchObject({
125
+ revision: 7,
126
+ connections: [{ safeMetadata: {} }],
127
+ });
128
+ expect(await storage.get<unknown>("user-configuration")).toEqual(
129
+ historical,
130
+ );
131
+
132
+ await settings.executeConfiguration({
133
+ schemaVersion: 1,
134
+ userId: "user-1",
135
+ command: {
136
+ schemaVersion: 1,
137
+ type: "user/update-profile",
138
+ commandId: "migrate-user-settings",
139
+ expectedRevision: 7,
140
+ profile: { name: "Migrated User" },
141
+ },
142
+ });
143
+ const written =
144
+ await storage.get<Record<string, unknown>>("user-configuration");
145
+ expect(written).toMatchObject({
146
+ revision: 8,
147
+ profile: { name: "Migrated User" },
148
+ connections: [{ safeMetadata: {} }],
149
+ });
150
+ expect(written).not.toHaveProperty("newBotModelTemplate");
151
+ expect(written).not.toHaveProperty("newBotModelTemplateSource");
152
+ });
153
+
85
154
  test("bootstraps first-party Packages once and preserves a later uninstall", async () => {
86
155
  const storage = new MemoryStorage();
87
156
  const settings = createUserSettingsBackendContribution({
@@ -657,6 +726,9 @@ class FakeCatalog implements UserPackageCatalogHost {
657
726
  version: entry.version,
658
727
  manifestHash: entry.manifestHash,
659
728
  kind: entry.kind,
729
+ ...(entry.bundle === undefined
730
+ ? {}
731
+ : { contentHash: entry.bundle.contentHash }),
660
732
  })),
661
733
  },
662
734
  });
@@ -752,6 +824,89 @@ describe("remote Package Catalog", () => {
752
824
  });
753
825
  });
754
826
 
827
+ test("admits only the exact hash of a code-carrying Catalog entry", async () => {
828
+ const contentHash = "b".repeat(64);
829
+ const catalog = new FakeCatalog();
830
+ catalog.entries = [
831
+ catalogEntry({
832
+ packageId: "parcel-tracking",
833
+ catalogId: "parcel-tracking",
834
+ displayName: "Parcel tracking",
835
+ manifestHash: "e".repeat(64),
836
+ bundle: {
837
+ contentHash,
838
+ size: 512,
839
+ mediaType: "application/javascript",
840
+ bundlerVersion: "catalog-test@1",
841
+ manifest: {
842
+ schemaVersion: 3,
843
+ id: "parcel-tracking",
844
+ displayName: "Parcel tracking",
845
+ version: "0.0.1",
846
+ compatibility: { frockbot: "*" },
847
+ dependencies: {},
848
+ contributions: {
849
+ runtime: { entry: "./package.js", host: "bot-isolate" },
850
+ },
851
+ tools: [],
852
+ permissions: [],
853
+ },
854
+ },
855
+ }),
856
+ ];
857
+ const { settings, storage } = catalogContribution(
858
+ new MemoryStorage(),
859
+ catalog,
860
+ );
861
+ await settings.readConfiguration({ schemaVersion: 1, userId: "user-1" });
862
+
863
+ await expect(
864
+ settings.executeConfiguration({
865
+ schemaVersion: 1,
866
+ userId: "user-1",
867
+ command: {
868
+ schemaVersion: 1,
869
+ type: "user/install-package",
870
+ commandId: "install-parcel",
871
+ expectedRevision: 0,
872
+ packageId: "parcel-tracking",
873
+ version: "0.0.1",
874
+ catalogId: "parcel-tracking",
875
+ catalogGeneration: "gen-one",
876
+ contentHash,
877
+ },
878
+ }),
879
+ ).resolves.toMatchObject({ status: "applied" });
880
+ expect(
881
+ (await storage.get<UserSettingsViewV1>("user-configuration"))?.packages,
882
+ ).toEqual([
883
+ expect.objectContaining({ packageId: "parcel-tracking", contentHash }),
884
+ ]);
885
+
886
+ const mismatch = catalogContribution(new MemoryStorage(), catalog);
887
+ await mismatch.settings.readConfiguration({
888
+ schemaVersion: 1,
889
+ userId: "user-1",
890
+ });
891
+ await expect(
892
+ mismatch.settings.executeConfiguration({
893
+ schemaVersion: 1,
894
+ userId: "user-1",
895
+ command: {
896
+ schemaVersion: 1,
897
+ type: "user/install-package",
898
+ commandId: "install-wrong-hash",
899
+ expectedRevision: 0,
900
+ packageId: "parcel-tracking",
901
+ version: "0.0.1",
902
+ catalogId: "parcel-tracking",
903
+ catalogGeneration: "gen-one",
904
+ contentHash: "c".repeat(64),
905
+ },
906
+ }),
907
+ ).rejects.toThrow('requires bundle hash "bbbb');
908
+ });
909
+
755
910
  test("refuses an install off the pinned generation", async () => {
756
911
  const { catalog, settings } = catalogContribution();
757
912
  await settings.readConfiguration({ schemaVersion: 1, userId: "user-1" });
package/src/user.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  decodeUserConfigurationExecuteRpcV1,
10
10
  decodeUserConfigurationReadRpcV1,
11
11
  decodeUserSettingsViewV1,
12
+ migrateStoredUserSettingsV1,
12
13
  MAX_USER_CONNECTIONS_V1,
13
14
  USER_PROFILE_PLACEHOLDER_NAME_V1,
14
15
  type ConnectionView,
@@ -302,6 +303,9 @@ function applyUserCommand(
302
303
  : {
303
304
  catalogId: command.catalogId,
304
305
  catalogGeneration: command.catalogGeneration,
306
+ ...(command.contentHash === undefined
307
+ ? {}
308
+ : { contentHash: command.contentHash }),
305
309
  provenance: "catalog" as const,
306
310
  }),
307
311
  ...(Object.keys(values).length === 0 ? {} : { values }),
@@ -615,6 +619,7 @@ export class UserSettingsBackendContribution {
615
619
  version: string;
616
620
  catalogId: string;
617
621
  catalogGeneration: string;
622
+ contentHash?: string;
618
623
  }): Promise<CatalogEntryV1> {
619
624
  const catalog = this.host.catalog;
620
625
  if (!catalog) {
@@ -639,6 +644,17 @@ export class UserSettingsBackendContribution {
639
644
  `Catalog entry "${command.catalogId}" does not offer Package "${command.packageId}" at version "${command.version}"`,
640
645
  );
641
646
  }
647
+ if (entry.bundle) {
648
+ if (command.contentHash !== entry.bundle.contentHash) {
649
+ throw new Error(
650
+ `Catalog entry "${command.catalogId}" requires bundle hash "${entry.bundle.contentHash}"`,
651
+ );
652
+ }
653
+ } else if (command.contentHash !== undefined) {
654
+ throw new Error(
655
+ `Catalog entry "${command.catalogId}" does not carry a Package bundle`,
656
+ );
657
+ }
642
658
  return entry;
643
659
  }
644
660
 
@@ -656,6 +672,9 @@ export class UserSettingsBackendContribution {
656
672
  version: command.version,
657
673
  catalogId: command.catalogId,
658
674
  catalogGeneration: command.catalogGeneration,
675
+ ...(command.contentHash === undefined
676
+ ? {}
677
+ : { contentHash: command.contentHash }),
659
678
  })
660
679
  : undefined;
661
680
  return this.host.storage.transaction((storage) =>
@@ -731,7 +750,7 @@ export class UserSettingsBackendContribution {
731
750
  const current =
732
751
  storedSettings === undefined
733
752
  ? initialState()
734
- : decodeUserSettingsViewV1(storedSettings);
753
+ : decodeUserSettingsViewV1(migrateStoredUserSettingsV1(storedSettings));
735
754
  if (command.type === "user/set-package-enabled" && command.enabled) {
736
755
  const installed = current.packages.find(
737
756
  (pkg) => pkg.packageId === command.packageId,
@@ -800,7 +819,7 @@ export class UserSettingsBackendContribution {
800
819
  const stored = await storage.get<unknown>(STATE_KEY);
801
820
  return stored === undefined
802
821
  ? initialState()
803
- : decodeUserSettingsViewV1(stored);
822
+ : decodeUserSettingsViewV1(migrateStoredUserSettingsV1(stored));
804
823
  }
805
824
 
806
825
  async read(