@frockbot/plugin-settings 0.3.8 → 0.3.10
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 +10 -10
- package/src/user.test.ts +45 -0
- package/src/user.ts +56 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-settings",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@frockbot/catalog-core": "0.3.
|
|
25
|
-
"@frockbot/client-core": "0.3.
|
|
26
|
-
"@frockbot/client-ui": "0.3.
|
|
27
|
-
"@frockbot/configuration-core": "0.3.
|
|
28
|
-
"@frockbot/connection-core": "0.3.
|
|
29
|
-
"@frockbot/kernel-composition": "0.3.
|
|
30
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
31
|
-
"@frockbot/plugin-auth": "0.3.
|
|
32
|
-
"@frockbot/plugin-shell": "0.3.
|
|
24
|
+
"@frockbot/catalog-core": "0.3.10",
|
|
25
|
+
"@frockbot/client-core": "0.3.10",
|
|
26
|
+
"@frockbot/client-ui": "0.3.10",
|
|
27
|
+
"@frockbot/configuration-core": "0.3.10",
|
|
28
|
+
"@frockbot/connection-core": "0.3.10",
|
|
29
|
+
"@frockbot/kernel-composition": "0.3.10",
|
|
30
|
+
"@frockbot/kernel-contracts": "0.3.10",
|
|
31
|
+
"@frockbot/plugin-auth": "0.3.10",
|
|
32
|
+
"@frockbot/plugin-shell": "0.3.10",
|
|
33
33
|
"cordis": "4.0.0-rc.8",
|
|
34
34
|
"vue": "3.5.41"
|
|
35
35
|
},
|
package/src/user.test.ts
CHANGED
|
@@ -746,6 +746,51 @@ describe("User settings backend Contribution", () => {
|
|
|
746
746
|
},
|
|
747
747
|
}),
|
|
748
748
|
).resolves.toMatchObject({ status: "applied", revision: 4 });
|
|
749
|
+
|
|
750
|
+
// The mirror of the rule above, read the other way round. Disabling the
|
|
751
|
+
// dependency is allowed — refusing it would make a Package the User never
|
|
752
|
+
// chose able to pin one they did — and carries its dependents with it, so
|
|
753
|
+
// the account never lands in the state the enable path refuses:
|
|
754
|
+
// `custom-models=disabled provider-ollama-cloud=installed`.
|
|
755
|
+
await expect(
|
|
756
|
+
settings.executeConfiguration({
|
|
757
|
+
schemaVersion: 1,
|
|
758
|
+
userId: "user-1",
|
|
759
|
+
command: {
|
|
760
|
+
schemaVersion: 1,
|
|
761
|
+
type: "user/set-package-enabled",
|
|
762
|
+
commandId: "disable-custom-models",
|
|
763
|
+
expectedRevision: 4,
|
|
764
|
+
packageId: "custom-models",
|
|
765
|
+
enabled: false,
|
|
766
|
+
},
|
|
767
|
+
}),
|
|
768
|
+
).resolves.toMatchObject({ status: "applied", revision: 5 });
|
|
769
|
+
const cascaded = await settings.read("user-1");
|
|
770
|
+
expect(cascaded.packages[0]).toMatchObject({
|
|
771
|
+
packageId: "custom-models",
|
|
772
|
+
state: "disabled",
|
|
773
|
+
});
|
|
774
|
+
expect(cascaded.packages[1]).toMatchObject({
|
|
775
|
+
packageId: "provider-ollama-cloud",
|
|
776
|
+
state: "disabled",
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// The cascade is not automatic re-enablement: turning the dependency back
|
|
780
|
+
// on leaves the dependent off until the User asks for it.
|
|
781
|
+
await settings.executeConfiguration({
|
|
782
|
+
schemaVersion: 1,
|
|
783
|
+
userId: "user-1",
|
|
784
|
+
command: {
|
|
785
|
+
schemaVersion: 1,
|
|
786
|
+
type: "user/set-package-enabled",
|
|
787
|
+
commandId: "re-enable-custom-models",
|
|
788
|
+
expectedRevision: 5,
|
|
789
|
+
packageId: "custom-models",
|
|
790
|
+
enabled: true,
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
expect((await settings.read("user-1")).packages[1]?.state).toBe("disabled");
|
|
749
794
|
});
|
|
750
795
|
|
|
751
796
|
test("applies the platform model through the backend Contribution", async () => {
|
package/src/user.ts
CHANGED
|
@@ -731,6 +731,54 @@ export class UserSettingsBackendContribution {
|
|
|
731
731
|
return undefined;
|
|
732
732
|
}
|
|
733
733
|
|
|
734
|
+
/**
|
|
735
|
+
* The mirror of `packageDependencyFailure`, read the other way round.
|
|
736
|
+
*
|
|
737
|
+
* Enabling B before A is refused, so leaving B enabled once A is switched
|
|
738
|
+
* off would put the account in exactly the configuration the enable path
|
|
739
|
+
* will not create. Refusing the disable is not the answer either: a Package
|
|
740
|
+
* the User never chose — `provider-ollama-cloud` depends on `web` — would
|
|
741
|
+
* make `web` undisableable. So the disable is allowed and carries its
|
|
742
|
+
* dependents with it, transitively. Model binding degrades to the platform
|
|
743
|
+
* default rather than failing, so a cascade that reaches the Package a Bot's
|
|
744
|
+
* model is bound to costs that Bot its choice, not its next reply.
|
|
745
|
+
*
|
|
746
|
+
* Platform-owned Packages are never cascaded: they cannot be disabled by any
|
|
747
|
+
* other path, and in this application they depend only on each other.
|
|
748
|
+
*/
|
|
749
|
+
private cascadeDisabledDependents(
|
|
750
|
+
settings: UserSettingsViewV1,
|
|
751
|
+
): UserSettingsViewV1 {
|
|
752
|
+
let packages = settings.packages;
|
|
753
|
+
for (;;) {
|
|
754
|
+
const enabled = new Set(
|
|
755
|
+
packages
|
|
756
|
+
.filter((pkg) => pkg.state === "installed")
|
|
757
|
+
.map((pkg) => pkg.packageId),
|
|
758
|
+
);
|
|
759
|
+
const cascaded = packages.map((pkg) => {
|
|
760
|
+
if (pkg.state !== "installed") return pkg;
|
|
761
|
+
if (this.platformOwnedPackageIds.has(pkg.packageId)) return pkg;
|
|
762
|
+
const dependencies = this.packageDependencies.get(
|
|
763
|
+
`${pkg.packageId}\u0000${pkg.version}`,
|
|
764
|
+
);
|
|
765
|
+
if (!dependencies) return pkg;
|
|
766
|
+
const missing = Object.keys(dependencies).some(
|
|
767
|
+
(dependencyId) => !enabled.has(dependencyId),
|
|
768
|
+
);
|
|
769
|
+
return missing
|
|
770
|
+
? { ...pkg, state: "disabled" as const, failure: undefined }
|
|
771
|
+
: pkg;
|
|
772
|
+
});
|
|
773
|
+
if (cascaded.every((pkg, index) => pkg === packages[index])) {
|
|
774
|
+
return packages === settings.packages
|
|
775
|
+
? settings
|
|
776
|
+
: { ...settings, packages };
|
|
777
|
+
}
|
|
778
|
+
packages = cascaded;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
734
782
|
async readConfiguration(input: unknown): Promise<UserSettingsViewV1> {
|
|
735
783
|
const request = decodeUserConfigurationReadRpcV1(input);
|
|
736
784
|
// Settings owns the stored-record migration and platform-row repair. Run
|
|
@@ -1001,9 +1049,16 @@ export class UserSettingsBackendContribution {
|
|
|
1001
1049
|
await storage.put(receiptKey, { commandFingerprint, receipt });
|
|
1002
1050
|
return receipt;
|
|
1003
1051
|
}
|
|
1004
|
-
const
|
|
1052
|
+
const applied = applyUserCommand(current, command, (packageId, version) =>
|
|
1005
1053
|
this.settingDefinitions(packageId, version),
|
|
1006
1054
|
);
|
|
1055
|
+
// One command, one revision: the cascade is part of the disable the User
|
|
1056
|
+
// asked for, not a second write they have to reconcile against.
|
|
1057
|
+
const next =
|
|
1058
|
+
command.type === "user/uninstall-package" ||
|
|
1059
|
+
(command.type === "user/set-package-enabled" && !command.enabled)
|
|
1060
|
+
? this.cascadeDisabledDependents(applied)
|
|
1061
|
+
: applied;
|
|
1007
1062
|
const receipt: OperationReceiptV1 = {
|
|
1008
1063
|
schemaVersion: 1,
|
|
1009
1064
|
commandId: command.commandId,
|