@frockbot/plugin-settings 0.1.2 → 0.1.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/src/user.test.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import type { CatalogEntryV1, CatalogIndexV1 } from "@frockbot/catalog-core";
3
3
  import {
4
- capabilityAssignmentFailureV1,
5
- initializeBotSettingsV1,
6
- resolveBotExecutionPlanV1,
7
4
  USER_PROFILE_PLACEHOLDER_NAME_V1,
8
5
  type UserSettingsViewV1,
9
6
  } from "@frockbot/configuration-core";
@@ -150,6 +147,229 @@ describe("User settings backend Contribution", () => {
150
147
  ]);
151
148
  });
152
149
 
150
+ test("bootstraps a declared default-disabled Package without re-enabling it", async () => {
151
+ const settings = createUserSettingsBackendContribution({
152
+ storage: new MemoryStorage(),
153
+ availablePackages: [
154
+ {
155
+ packageId: "custom-models",
156
+ version: "0.0.1",
157
+ installByDefault: true,
158
+ defaultEnablement: "disabled",
159
+ },
160
+ ],
161
+ });
162
+
163
+ const first = await settings.readConfiguration({
164
+ schemaVersion: 1,
165
+ userId: "user-1",
166
+ });
167
+ expect(first.packages).toEqual([
168
+ {
169
+ packageId: "custom-models",
170
+ version: "0.0.1",
171
+ state: "disabled",
172
+ provenance: "first-party",
173
+ },
174
+ ]);
175
+ expect(
176
+ await settings.readConfiguration({ schemaVersion: 1, userId: "user-1" }),
177
+ ).toEqual(first);
178
+ });
179
+
180
+ test("installs a Package disabled when explicitly requested", async () => {
181
+ const settings = contribution();
182
+ const receipt = await settings.executeConfiguration({
183
+ schemaVersion: 1,
184
+ userId: "user-1",
185
+ command: {
186
+ schemaVersion: 1,
187
+ type: "user/install-package",
188
+ commandId: "install-disabled",
189
+ expectedRevision: 0,
190
+ packageId: "provider-ollama-cloud",
191
+ version: "0.0.1",
192
+ enabled: false,
193
+ },
194
+ });
195
+
196
+ expect(receipt).toMatchObject({ status: "applied", revision: 1 });
197
+ expect((await settings.read("user-1")).packages).toMatchObject([
198
+ { packageId: "provider-ollama-cloud", state: "disabled" },
199
+ ]);
200
+ });
201
+
202
+ test("does not clear an existing failed installation", async () => {
203
+ const storage = new MemoryStorage();
204
+ const settings = contribution(storage);
205
+ await storage.put("user-id", "user-1");
206
+ await storage.put("user-configuration", {
207
+ schemaVersion: 1,
208
+ revision: 0,
209
+ profile: { name: "User" },
210
+ packages: [
211
+ {
212
+ packageId: "provider-ollama-cloud",
213
+ version: "0.0.1",
214
+ state: "failed",
215
+ failure: "activation failed",
216
+ },
217
+ ],
218
+ connections: [],
219
+ } satisfies UserSettingsViewV1);
220
+
221
+ await settings.executeConfiguration({
222
+ schemaVersion: 1,
223
+ userId: "user-1",
224
+ command: {
225
+ schemaVersion: 1,
226
+ type: "user/install-package",
227
+ commandId: "retry-failed-disabled",
228
+ expectedRevision: 0,
229
+ packageId: "provider-ollama-cloud",
230
+ version: "0.0.1",
231
+ enabled: false,
232
+ },
233
+ });
234
+
235
+ expect((await settings.read("user-1")).packages).toMatchObject([
236
+ {
237
+ packageId: "provider-ollama-cloud",
238
+ state: "failed",
239
+ failure: "activation failed",
240
+ },
241
+ ]);
242
+ });
243
+
244
+ test("refuses enabling until every declared dependency is enabled", async () => {
245
+ const settings = createUserSettingsBackendContribution({
246
+ storage: new MemoryStorage(),
247
+ availablePackages: [
248
+ { packageId: "custom-models", version: "0.0.1" },
249
+ {
250
+ packageId: "provider-ollama-cloud",
251
+ version: "0.0.1",
252
+ dependencies: { "custom-models": ">=0.0.1" },
253
+ },
254
+ ],
255
+ });
256
+ await settings.executeConfiguration({
257
+ schemaVersion: 1,
258
+ userId: "user-1",
259
+ command: {
260
+ schemaVersion: 1,
261
+ type: "user/install-package",
262
+ commandId: "install-custom-models-disabled",
263
+ expectedRevision: 0,
264
+ packageId: "custom-models",
265
+ version: "0.0.1",
266
+ enabled: false,
267
+ },
268
+ });
269
+ const refusedInstall = await settings.executeConfiguration({
270
+ schemaVersion: 1,
271
+ userId: "user-1",
272
+ command: {
273
+ schemaVersion: 1,
274
+ type: "user/install-package",
275
+ commandId: "install-ollama-enabled-too-soon",
276
+ expectedRevision: 1,
277
+ packageId: "provider-ollama-cloud",
278
+ version: "0.0.1",
279
+ },
280
+ });
281
+ expect(refusedInstall).toMatchObject({
282
+ status: "rejected",
283
+ revision: 1,
284
+ failure: expect.stringContaining("custom-models"),
285
+ });
286
+ await settings.executeConfiguration({
287
+ schemaVersion: 1,
288
+ userId: "user-1",
289
+ command: {
290
+ schemaVersion: 1,
291
+ type: "user/install-package",
292
+ commandId: "install-ollama-disabled",
293
+ expectedRevision: 1,
294
+ packageId: "provider-ollama-cloud",
295
+ version: "0.0.1",
296
+ enabled: false,
297
+ },
298
+ });
299
+
300
+ const refused = await settings.executeConfiguration({
301
+ schemaVersion: 1,
302
+ userId: "user-1",
303
+ command: {
304
+ schemaVersion: 1,
305
+ type: "user/set-package-enabled",
306
+ commandId: "enable-ollama-too-soon",
307
+ expectedRevision: 2,
308
+ packageId: "provider-ollama-cloud",
309
+ enabled: true,
310
+ },
311
+ });
312
+ expect(refused).toMatchObject({
313
+ status: "rejected",
314
+ revision: 2,
315
+ failure: expect.stringContaining("custom-models"),
316
+ });
317
+ expect((await settings.read("user-1")).packages[1]?.state).toBe("disabled");
318
+
319
+ await settings.executeConfiguration({
320
+ schemaVersion: 1,
321
+ userId: "user-1",
322
+ command: {
323
+ schemaVersion: 1,
324
+ type: "user/set-package-enabled",
325
+ commandId: "enable-custom-models",
326
+ expectedRevision: 2,
327
+ packageId: "custom-models",
328
+ enabled: true,
329
+ },
330
+ });
331
+ await expect(
332
+ settings.executeConfiguration({
333
+ schemaVersion: 1,
334
+ userId: "user-1",
335
+ command: {
336
+ schemaVersion: 1,
337
+ type: "user/set-package-enabled",
338
+ commandId: "enable-ollama",
339
+ expectedRevision: 3,
340
+ packageId: "provider-ollama-cloud",
341
+ enabled: true,
342
+ },
343
+ }),
344
+ ).resolves.toMatchObject({ status: "applied", revision: 4 });
345
+ });
346
+
347
+ test("applies the platform model through the backend Contribution", async () => {
348
+ const storage = new MemoryStorage();
349
+ const settings = contribution(storage);
350
+
351
+ await expect(
352
+ settings.executeConfigurationCommand(
353
+ "user-1",
354
+ {
355
+ schemaVersion: 1,
356
+ type: "user/set-platform-model",
357
+ commandId: "bootstrap-platform-model",
358
+ expectedRevision: 0,
359
+ model: {
360
+ connectionId: "flock-default",
361
+ providerModelId: "@flock/auto",
362
+ },
363
+ },
364
+ storage,
365
+ ),
366
+ ).resolves.toMatchObject({ status: "applied", revision: 1 });
367
+ expect((await settings.read("user-1")).platformModel).toEqual({
368
+ connectionId: "flock-default",
369
+ providerModelId: "@flock/auto",
370
+ });
371
+ });
372
+
153
373
  test("owns durable User configuration independently of providers", async () => {
154
374
  const storage = new MemoryStorage();
155
375
  const settings = contribution(storage);
@@ -223,233 +443,6 @@ describe("User settings backend Contribution", () => {
223
443
  ).rejects.toThrow("Package is not available in this application");
224
444
  });
225
445
 
226
- test("coordinates Connection dependencies in provider-neutral User state", async () => {
227
- const settings = contribution();
228
- await settings.executeConfiguration({
229
- schemaVersion: 1,
230
- userId: "user-1",
231
- command: {
232
- schemaVersion: 1,
233
- type: "user/install-package",
234
- commandId: "install-ollama",
235
- expectedRevision: 0,
236
- packageId: "provider-ollama-cloud",
237
- version: "0.0.1",
238
- },
239
- });
240
- await settings.createConnection("user-1", {
241
- connectionId: "ollama-1",
242
- packageId: "provider-ollama-cloud",
243
- connectionTypeId: "ollama-cloud-account",
244
- displayName: "Work",
245
- state: "ready",
246
- providerType: "ollama-cloud",
247
- generation: "connection-generation",
248
- safeMetadata: {},
249
- });
250
- const requirement = {
251
- schemaVersion: 1 as const,
252
- packageId: "provider-ollama-cloud",
253
- packageVersion: "0.0.1",
254
- capabilityId: "ollama-cloud-models",
255
- connectionTypeIds: ["ollama-cloud-account"],
256
- };
257
-
258
- await expect(
259
- settings.claimConnectionDependency(
260
- "user-1",
261
- "ollama-1",
262
- "bot-1",
263
- "assignment-1",
264
- requirement,
265
- ),
266
- ).resolves.toBe(true);
267
- await expect(
268
- settings.acknowledgeConnectionDependency(
269
- "user-1",
270
- "ollama-1",
271
- "bot-1",
272
- "assignment-1",
273
- ),
274
- ).resolves.toBe(true);
275
- expect(
276
- (await settings.getConnection("user-1", "ollama-1"))?.safeMetadata,
277
- ).toMatchObject({
278
- dependentAssignments: [
279
- {
280
- botId: "bot-1",
281
- generation: "assignment-1",
282
- packageId: "provider-ollama-cloud",
283
- capabilityId: "ollama-cloud-models",
284
- status: "acknowledged",
285
- },
286
- ],
287
- });
288
- await expect(
289
- settings.compensateConnectionDependency(
290
- "user-1",
291
- "ollama-1",
292
- "bot-1",
293
- "assignment-1",
294
- ),
295
- ).resolves.toBe(false);
296
-
297
- await settings.createConnection("user-1", {
298
- connectionId: "ollama-2",
299
- packageId: "provider-ollama-cloud",
300
- connectionTypeId: "ollama-cloud-account",
301
- displayName: "Personal",
302
- state: "ready",
303
- providerType: "ollama-cloud",
304
- generation: "connection-generation-2",
305
- safeMetadata: {},
306
- });
307
- await settings.claimConnectionDependency(
308
- "user-1",
309
- "ollama-2",
310
- "bot-1",
311
- "assignment-2",
312
- requirement,
313
- );
314
- await settings.acknowledgeConnectionDependency(
315
- "user-1",
316
- "ollama-2",
317
- "bot-1",
318
- "assignment-2",
319
- );
320
-
321
- expect(
322
- (await settings.getConnection("user-1", "ollama-1"))?.safeMetadata,
323
- ).toMatchObject({ dependentAssignments: [] });
324
- expect(
325
- (await settings.getConnection("user-1", "ollama-2"))?.safeMetadata,
326
- ).toMatchObject({
327
- dependentAssignments: [
328
- {
329
- botId: "bot-1",
330
- generation: "assignment-2",
331
- packageId: "provider-ollama-cloud",
332
- capabilityId: "ollama-cloud-models",
333
- status: "acknowledged",
334
- },
335
- ],
336
- });
337
- await expect(
338
- settings.releaseConnectionDependency(
339
- "user-1",
340
- "ollama-2",
341
- "bot-1",
342
- "assignment-2",
343
- ),
344
- ).resolves.toBe(true);
345
- await expect(
346
- settings.releaseConnectionDependency(
347
- "user-1",
348
- "ollama-2",
349
- "bot-1",
350
- "assignment-2",
351
- ),
352
- ).resolves.toBe(true);
353
- expect(
354
- (await settings.getConnection("user-1", "ollama-2"))?.safeMetadata,
355
- ).toMatchObject({ dependentAssignments: [] });
356
- });
357
-
358
- test("rejects acknowledgement from a superseded dependency claim", async () => {
359
- const settings = contribution();
360
- await settings.executeConfiguration({
361
- schemaVersion: 1,
362
- userId: "user-1",
363
- command: {
364
- schemaVersion: 1,
365
- type: "user/install-package",
366
- commandId: "install-fenced-package",
367
- expectedRevision: 0,
368
- packageId: "provider-ollama-cloud",
369
- version: "0.0.1",
370
- },
371
- });
372
- for (const connectionId of ["ollama-old", "ollama-new"]) {
373
- await settings.createConnection("user-1", {
374
- connectionId,
375
- packageId: "provider-ollama-cloud",
376
- connectionTypeId: "ollama-cloud-account",
377
- displayName: connectionId,
378
- state: "ready",
379
- providerType: "ollama-cloud",
380
- generation: `${connectionId}-generation`,
381
- safeMetadata: {},
382
- });
383
- }
384
- const requirement = {
385
- schemaVersion: 1 as const,
386
- packageId: "provider-ollama-cloud",
387
- packageVersion: "0.0.1",
388
- capabilityId: "ollama-cloud-models",
389
- connectionTypeIds: ["ollama-cloud-account"],
390
- };
391
- await settings.claimConnectionDependency(
392
- "user-1",
393
- "ollama-old",
394
- "bot-1",
395
- "assignment-old",
396
- requirement,
397
- );
398
- await settings.claimConnectionDependency(
399
- "user-1",
400
- "ollama-new",
401
- "bot-1",
402
- "assignment-new",
403
- requirement,
404
- );
405
- await settings.claimConnectionDependency(
406
- "user-1",
407
- "ollama-old",
408
- "bot-1",
409
- "assignment-old",
410
- requirement,
411
- );
412
-
413
- await expect(
414
- settings.acknowledgeConnectionDependency(
415
- "user-1",
416
- "ollama-old",
417
- "bot-1",
418
- "assignment-old",
419
- ),
420
- ).resolves.toBe(false);
421
- expect(
422
- (await settings.getConnection("user-1", "ollama-new"))?.safeMetadata,
423
- ).toMatchObject({
424
- dependentAssignments: [
425
- { generation: "assignment-new", status: "pending" },
426
- ],
427
- });
428
- await expect(
429
- settings.compensateConnectionDependency(
430
- "user-1",
431
- "ollama-old",
432
- "bot-1",
433
- "assignment-old",
434
- ),
435
- ).resolves.toBe(true);
436
- expect(
437
- (await settings.getConnection("user-1", "ollama-old"))?.safeMetadata,
438
- ).toMatchObject({ dependentAssignments: [] });
439
-
440
- await expect(
441
- settings.acknowledgeConnectionDependency(
442
- "user-1",
443
- "ollama-new",
444
- "bot-1",
445
- "assignment-new",
446
- ),
447
- ).resolves.toBe(true);
448
- expect(
449
- (await settings.getConnection("user-1", "ollama-old"))?.safeMetadata,
450
- ).toMatchObject({ dependentAssignments: [] });
451
- });
452
-
453
446
  test("compacts revoked Connections and bounds active Connections", async () => {
454
447
  const settings = contribution();
455
448
  await settings.read("user-1");
@@ -935,92 +928,6 @@ describe("uninstall", () => {
935
928
  ]);
936
929
  });
937
930
 
938
- test("dependent Assignments become unavailable tombstones, not deletions", async () => {
939
- const storage = new MemoryStorage();
940
- const settings = contribution(storage);
941
- await settings.executeConfiguration({
942
- schemaVersion: 1,
943
- userId: "user-1",
944
- command: {
945
- schemaVersion: 1,
946
- type: "user/install-package",
947
- commandId: "install-provider",
948
- expectedRevision: 0,
949
- packageId: "provider-ollama-cloud",
950
- version: "0.0.1",
951
- },
952
- });
953
- await settings.createConnection("user-1", {
954
- connectionId: "connection-1",
955
- packageId: "provider-ollama-cloud",
956
- connectionTypeId: "ollama-cloud-account",
957
- displayName: "Work",
958
- state: "ready",
959
- safeMetadata: {},
960
- });
961
- const assignment = {
962
- assignmentId: "assignment-1",
963
- packageId: "provider-ollama-cloud",
964
- capabilityId: "ollama-cloud-models",
965
- connectionId: "connection-1",
966
- };
967
- const packages = [
968
- {
969
- packageId: "provider-ollama-cloud",
970
- version: "0.0.1",
971
- capabilities: [
972
- {
973
- id: "ollama-cloud-models",
974
- kind: "model" as const,
975
- connectionTypes: ["ollama-cloud-account"],
976
- },
977
- ],
978
- connectionTypes: [
979
- {
980
- id: "ollama-cloud-account",
981
- capabilities: ["ollama-cloud-models"],
982
- },
983
- ],
984
- },
985
- ];
986
-
987
- const installed = await settings.readSnapshot();
988
- expect(
989
- capabilityAssignmentFailureV1({ assignment, user: installed, packages }),
990
- ).toBeUndefined();
991
-
992
- await settings.executeConfiguration({
993
- schemaVersion: 1,
994
- userId: "user-1",
995
- command: {
996
- schemaVersion: 1,
997
- type: "user/uninstall-package",
998
- commandId: "uninstall-provider",
999
- expectedRevision: installed.revision,
1000
- packageId: "provider-ollama-cloud",
1001
- },
1002
- });
1003
-
1004
- const uninstalled = await settings.readSnapshot();
1005
- expect(
1006
- capabilityAssignmentFailureV1({
1007
- assignment,
1008
- user: uninstalled,
1009
- packages,
1010
- }),
1011
- ).toContain("is not installed and enabled");
1012
- expect(
1013
- resolveBotExecutionPlanV1({
1014
- bot: {
1015
- ...initializeBotSettingsV1("bot-1"),
1016
- assignments: [{ ...assignment, state: "enabled" }],
1017
- },
1018
- user: uninstalled,
1019
- packages,
1020
- }).assignments,
1021
- ).toEqual([{ ...assignment, state: "unavailable" }]);
1022
- });
1023
-
1024
931
  test("refuses to uninstall a Package that is not installed", async () => {
1025
932
  const settings = contribution();
1026
933
 
@@ -1079,6 +986,53 @@ describe("Package-level setting values", () => {
1079
986
  });
1080
987
  });
1081
988
 
989
+ test("removes only declared setting ids and drops an emptied value bag", async () => {
990
+ const settings = contribution();
991
+ await installOllama(settings, "user-1");
992
+ await settings.executeConfiguration({
993
+ schemaVersion: 1,
994
+ userId: "user-1",
995
+ command: {
996
+ schemaVersion: 1,
997
+ type: "user/set-package-settings",
998
+ commandId: "set-before-unset",
999
+ expectedRevision: 1,
1000
+ packageId: "provider-ollama-cloud",
1001
+ values: { "web-search-max-results": 4 },
1002
+ },
1003
+ });
1004
+
1005
+ await settings.executeConfiguration({
1006
+ schemaVersion: 1,
1007
+ userId: "user-1",
1008
+ command: {
1009
+ schemaVersion: 1,
1010
+ type: "user/set-package-settings",
1011
+ commandId: "unset-value",
1012
+ expectedRevision: 2,
1013
+ packageId: "provider-ollama-cloud",
1014
+ unset: ["web-search-max-results"],
1015
+ },
1016
+ });
1017
+ expect((await settings.read("user-1")).packages[0]?.values).toBeUndefined();
1018
+
1019
+ await expect(
1020
+ settings.executeConfiguration({
1021
+ schemaVersion: 1,
1022
+ userId: "user-1",
1023
+ command: {
1024
+ schemaVersion: 1,
1025
+ type: "user/set-package-settings",
1026
+ commandId: "unset-unknown",
1027
+ expectedRevision: 3,
1028
+ packageId: "provider-ollama-cloud",
1029
+ unset: ["unknown-setting"],
1030
+ },
1031
+ }),
1032
+ ).rejects.toThrow(/not declared by this Package/);
1033
+ expect((await settings.read("user-1")).revision).toBe(3);
1034
+ });
1035
+
1082
1036
  test("a replayed command id returns its receipt without applying twice", async () => {
1083
1037
  const settings = contribution();
1084
1038
  await installOllama(settings, "user-1");