@frockbot/kernel-composition 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/package.json +2 -2
- package/src/compiler.test.ts +17 -0
- package/src/compiler.ts +4 -0
- package/src/index.test.ts +91 -7
- package/src/isolate-host.test.ts +48 -8
- package/src/isolate-host.ts +9 -10
- package/src/manifest.ts +74 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/kernel-composition",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"cordis": "4.0.0-rc.8",
|
|
22
22
|
"semver": "7.8.5",
|
|
23
|
-
"@frockbot/kernel-contracts": "0.1.
|
|
23
|
+
"@frockbot/kernel-contracts": "0.1.4"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "1.4.0",
|
package/src/compiler.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ function runtimeManifest(
|
|
|
12
12
|
version?: string;
|
|
13
13
|
permissions?: string[];
|
|
14
14
|
dependencies?: Record<string, string>;
|
|
15
|
+
defaultEnablement?: "enabled" | "disabled";
|
|
15
16
|
compatibility?: string;
|
|
16
17
|
} = {},
|
|
17
18
|
) {
|
|
@@ -22,6 +23,7 @@ function runtimeManifest(
|
|
|
22
23
|
version: options.version ?? "1.0.0",
|
|
23
24
|
compatibility: { frockbot: options.compatibility ?? ">=0.0.1" },
|
|
24
25
|
dependencies: options.dependencies,
|
|
26
|
+
defaultEnablement: options.defaultEnablement,
|
|
25
27
|
contributions: { runtime: { entry: "./runtime" } },
|
|
26
28
|
permissions: options.permissions ?? [],
|
|
27
29
|
};
|
|
@@ -191,6 +193,21 @@ describe("compileApplicationPlan", () => {
|
|
|
191
193
|
);
|
|
192
194
|
});
|
|
193
195
|
|
|
196
|
+
test("allows a disabled Package to depend on a Catalog Package", async () => {
|
|
197
|
+
const plan = await compileApplicationPlan(
|
|
198
|
+
{ schemaVersion: 1, packages: [selection("@fixture/feature")] },
|
|
199
|
+
resolver({
|
|
200
|
+
"@fixture/feature": runtimeManifest("feature", {
|
|
201
|
+
dependencies: { optional: "^1.0.0" },
|
|
202
|
+
defaultEnablement: "disabled",
|
|
203
|
+
}),
|
|
204
|
+
}),
|
|
205
|
+
{ frockbotVersion: "1.0.0" },
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(plan.packages[0]?.manifest.defaultEnablement).toBe("disabled");
|
|
209
|
+
});
|
|
210
|
+
|
|
194
211
|
test("validates client roots and declared outlets", async () => {
|
|
195
212
|
const clientManifest = (
|
|
196
213
|
id: string,
|
package/src/compiler.ts
CHANGED
|
@@ -139,6 +139,10 @@ function orderedPackages(
|
|
|
139
139
|
).sort(([left], [right]) => left.localeCompare(right))) {
|
|
140
140
|
const dependency = packages.get(dependencyId);
|
|
141
141
|
if (!dependency) {
|
|
142
|
+
// A disabled-by-default Package may depend on a Package available only
|
|
143
|
+
// from the User's Catalog. Settings refuses to enable it until an
|
|
144
|
+
// installed dependency row exists; enabled built-ins still fail here.
|
|
145
|
+
if (pkg.manifest.defaultEnablement === "disabled") continue;
|
|
142
146
|
throw new Error(
|
|
143
147
|
`package "${pkg.id}" requires missing package "${dependencyId}"`,
|
|
144
148
|
);
|
package/src/index.test.ts
CHANGED
|
@@ -427,11 +427,95 @@ describe("decodeFrockBotManifest", () => {
|
|
|
427
427
|
});
|
|
428
428
|
});
|
|
429
429
|
|
|
430
|
+
test("decodes the exact provider-neutral model setting role", () => {
|
|
431
|
+
const decoded = decodeFrockBotManifest({
|
|
432
|
+
...manifest("custom-models"),
|
|
433
|
+
configuration: {
|
|
434
|
+
settings: [
|
|
435
|
+
{
|
|
436
|
+
id: "model",
|
|
437
|
+
schemaVersion: 1,
|
|
438
|
+
scopes: ["user", "bot"],
|
|
439
|
+
role: "model",
|
|
440
|
+
schema: {
|
|
441
|
+
type: "object",
|
|
442
|
+
properties: {
|
|
443
|
+
connectionId: { type: "string" },
|
|
444
|
+
providerModelId: { type: "string" },
|
|
445
|
+
},
|
|
446
|
+
required: ["connectionId", "providerModelId"],
|
|
447
|
+
additionalProperties: false,
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
expect(decoded.configuration?.settings).toEqual([
|
|
455
|
+
{
|
|
456
|
+
id: "model",
|
|
457
|
+
schemaVersion: 1,
|
|
458
|
+
scopes: ["user", "bot"],
|
|
459
|
+
role: "model",
|
|
460
|
+
schema: {
|
|
461
|
+
type: "object",
|
|
462
|
+
properties: {
|
|
463
|
+
connectionId: { type: "string" },
|
|
464
|
+
providerModelId: { type: "string" },
|
|
465
|
+
},
|
|
466
|
+
required: ["connectionId", "providerModelId"],
|
|
467
|
+
additionalProperties: false,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
]);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("rejects any other schema for a model-role setting", () => {
|
|
474
|
+
const exact = {
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: {
|
|
477
|
+
connectionId: { type: "string" },
|
|
478
|
+
providerModelId: { type: "string" },
|
|
479
|
+
},
|
|
480
|
+
required: ["connectionId", "providerModelId"],
|
|
481
|
+
additionalProperties: false,
|
|
482
|
+
};
|
|
483
|
+
for (const schema of [
|
|
484
|
+
{ ...exact, additionalProperties: true },
|
|
485
|
+
{ ...exact, required: ["connectionId"] },
|
|
486
|
+
{
|
|
487
|
+
...exact,
|
|
488
|
+
properties: {
|
|
489
|
+
...exact.properties,
|
|
490
|
+
providerModelId: { type: "number" },
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
{ ...exact, title: "Choose a model" },
|
|
494
|
+
]) {
|
|
495
|
+
expect(() =>
|
|
496
|
+
decodeFrockBotManifest({
|
|
497
|
+
...manifest("custom-models"),
|
|
498
|
+
configuration: {
|
|
499
|
+
settings: [
|
|
500
|
+
{
|
|
501
|
+
id: "model",
|
|
502
|
+
schemaVersion: 1,
|
|
503
|
+
scopes: ["user"],
|
|
504
|
+
role: "model",
|
|
505
|
+
schema,
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
}),
|
|
510
|
+
).toThrow(/model setting schema must be exactly/);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
430
514
|
test("decodes an ambient-native Connection without an authorization driver", () => {
|
|
431
515
|
const decoded = decodeFrockBotManifest({
|
|
432
516
|
schemaVersion: 4,
|
|
433
|
-
id: "
|
|
434
|
-
displayName: "
|
|
517
|
+
id: "flock-ai",
|
|
518
|
+
displayName: "Flock AI",
|
|
435
519
|
version: "1.0.0",
|
|
436
520
|
compatibility: { frockbot: ">=0.0.1" },
|
|
437
521
|
contributions: { runtime: { entry: "./runtime" } },
|
|
@@ -439,18 +523,18 @@ describe("decodeFrockBotManifest", () => {
|
|
|
439
523
|
configuration: {
|
|
440
524
|
connectionTypes: [
|
|
441
525
|
{
|
|
442
|
-
id: "
|
|
443
|
-
displayName: "
|
|
526
|
+
id: "flock-ai-account",
|
|
527
|
+
displayName: "Flock AI",
|
|
444
528
|
allowMultiple: false,
|
|
445
529
|
authorization: { kind: "ambient-native" },
|
|
446
|
-
capabilities: ["
|
|
530
|
+
capabilities: ["flock-ai-models"],
|
|
447
531
|
},
|
|
448
532
|
],
|
|
449
533
|
capabilities: [
|
|
450
534
|
{
|
|
451
|
-
id: "
|
|
535
|
+
id: "flock-ai-models",
|
|
452
536
|
kind: "model",
|
|
453
|
-
connectionTypes: ["
|
|
537
|
+
connectionTypes: ["flock-ai-account"],
|
|
454
538
|
},
|
|
455
539
|
],
|
|
456
540
|
},
|
package/src/isolate-host.test.ts
CHANGED
|
@@ -178,7 +178,7 @@ describe("Bot isolate contribution host", () => {
|
|
|
178
178
|
test("a caller that omits the binding digest does not compile", () => {
|
|
179
179
|
// @ts-expect-error the binding digest is required: an isolate loaded with
|
|
180
180
|
// no digest of its granted bindings would share a loader id across
|
|
181
|
-
//
|
|
181
|
+
// enabled bindings and generations.
|
|
182
182
|
void botIsolateModuleSetHashV1(CONTENT_HASH);
|
|
183
183
|
expect(true).toBe(true);
|
|
184
184
|
});
|
|
@@ -191,20 +191,60 @@ describe("Bot isolate contribution host", () => {
|
|
|
191
191
|
expect(loads[0]!.loaderId).not.toBe(other.loads[0]!.loaderId);
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
test("
|
|
194
|
+
test("reuses the loader id only for the same Bot, generation, and enabled set", async () => {
|
|
195
195
|
const { host: subject, loads } = host();
|
|
196
196
|
await subject.prepare(descriptor());
|
|
197
|
-
const
|
|
198
|
-
await
|
|
197
|
+
const same = host();
|
|
198
|
+
await same.host.prepare(descriptor());
|
|
199
199
|
const expected = await botIsolateModuleSetHashV1(
|
|
200
200
|
CONTENT_HASH,
|
|
201
201
|
BINDING_DIGEST,
|
|
202
202
|
);
|
|
203
|
-
expect(loads[0]!.loaderId).toBe(`bot-package:user-1
|
|
204
|
-
expect(
|
|
205
|
-
|
|
203
|
+
expect(loads[0]!.loaderId).toBe(`bot-package:user-1:${expected}`);
|
|
204
|
+
expect(same.loads[0]!.loaderId).toBe(loads[0]!.loaderId);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("changes the loader id with the Bot, generation, or enabled set", async () => {
|
|
208
|
+
const first = host();
|
|
209
|
+
const otherBot = host({ botId: "bot-2", bindingDigest: "d".repeat(64) });
|
|
210
|
+
const otherGeneration = host({
|
|
211
|
+
generationId: "gen-2",
|
|
212
|
+
bindingDigest: "e".repeat(64),
|
|
213
|
+
});
|
|
214
|
+
const otherEnabledSet = host({ bindingDigest: "f".repeat(64) });
|
|
215
|
+
|
|
216
|
+
await Promise.all([
|
|
217
|
+
first.host.prepare(descriptor()),
|
|
218
|
+
otherBot.host.prepare(descriptor()),
|
|
219
|
+
otherGeneration.host.prepare(descriptor()),
|
|
220
|
+
otherEnabledSet.host.prepare(descriptor()),
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const loaderIds = [
|
|
224
|
+
first.loads[0]!.loaderId,
|
|
225
|
+
otherBot.loads[0]!.loaderId,
|
|
226
|
+
otherGeneration.loads[0]!.loaderId,
|
|
227
|
+
otherEnabledSet.loads[0]!.loaderId,
|
|
228
|
+
];
|
|
229
|
+
expect(new Set(loaderIds).size).toBe(loaderIds.length);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("never shares a loader id across Users", async () => {
|
|
233
|
+
const first = host();
|
|
234
|
+
const otherUser = host({ userId: "user-2" });
|
|
235
|
+
|
|
236
|
+
await Promise.all([
|
|
237
|
+
first.host.prepare(descriptor()),
|
|
238
|
+
otherUser.host.prepare(descriptor()),
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
expect(first.loads[0]!.loaderId).toMatch(
|
|
242
|
+
/^bot-package:user-1:[0-9a-f]{64}$/,
|
|
243
|
+
);
|
|
244
|
+
expect(otherUser.loads[0]!.loaderId).toMatch(
|
|
245
|
+
/^bot-package:user-2:[0-9a-f]{64}$/,
|
|
206
246
|
);
|
|
207
|
-
expect(loads[0]!.loaderId).not.toBe(
|
|
247
|
+
expect(otherUser.loads[0]!.loaderId).not.toBe(first.loads[0]!.loaderId);
|
|
208
248
|
});
|
|
209
249
|
|
|
210
250
|
test("health failure is a prepare failure with a diagnostic", async () => {
|
package/src/isolate-host.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
// It sits beside `LocalCordisContributionHost` because it is the *other*
|
|
6
6
|
// execution host the constitution names — first-party Packages run in the
|
|
7
7
|
// kernel isolate, everything else runs in a loaded Worker with
|
|
8
|
-
// `globalOutbound` disabled and only
|
|
8
|
+
// `globalOutbound` disabled and only enabled-capability bindings.
|
|
9
9
|
//
|
|
10
10
|
// Two behaviours come straight from `docs/research/spike-worker-loader-from-do.md`:
|
|
11
11
|
// `.get()` never throws, so mount and `health()` are a single guarded phase;
|
|
12
12
|
// and a reused loader id silently serves the first code, so the id is nothing
|
|
13
|
-
// but the content address of the
|
|
13
|
+
// but the content address of the mounted modules and their baked-in bindings.
|
|
14
14
|
import {
|
|
15
15
|
decodeIsolateHealthV1,
|
|
16
16
|
decodeIsolateToolResultV1,
|
|
@@ -95,11 +95,11 @@ export interface BotIsolateHostOptions {
|
|
|
95
95
|
*/
|
|
96
96
|
capabilities: BotCapabilitiesStub;
|
|
97
97
|
/**
|
|
98
|
-
* A content address of
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* `env` it keeps
|
|
102
|
-
* isolate
|
|
98
|
+
* A content address of every binding this isolate is loaded with: User, Bot,
|
|
99
|
+
* Composition generation, and the User's enabled capability set. Required,
|
|
100
|
+
* and part of the loader id, because the `env` first loaded under one id is
|
|
101
|
+
* the `env` it keeps. Any changed binding must therefore produce a new
|
|
102
|
+
* isolate (AGENTS.md Package composition; ADR 0019).
|
|
103
103
|
*/
|
|
104
104
|
bindingDigest: string;
|
|
105
105
|
compatibilityDate: string;
|
|
@@ -120,8 +120,8 @@ export const BOT_ISOLATE_DEFAULT_HEALTH_DEADLINE_MS = 10_000;
|
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* The content address of what a Bot isolate mounts: the kernel wrapper text,
|
|
123
|
-
* the Package artifact, and the digest of
|
|
124
|
-
*
|
|
123
|
+
* the Package artifact, and the digest of every binding baked into its `env`.
|
|
124
|
+
* A change to any of the three is a new isolate.
|
|
125
125
|
*/
|
|
126
126
|
export async function botIsolateModuleSetHashV1(
|
|
127
127
|
artifactContentHash: string,
|
|
@@ -241,7 +241,6 @@ export class BotIsolateContributionHost implements ContributionHost {
|
|
|
241
241
|
const source = await this.loadSource(packageId, artifact.contentHash);
|
|
242
242
|
const loaderId = isolateLoaderIdV1({
|
|
243
243
|
userId: this.options.userId,
|
|
244
|
-
botId: this.options.botId,
|
|
245
244
|
artifactSetHash: await botIsolateModuleSetHashV1(
|
|
246
245
|
artifact.contentHash,
|
|
247
246
|
this.options.bindingDigest,
|
package/src/manifest.ts
CHANGED
|
@@ -81,6 +81,12 @@ export interface PackageSettingDefinition {
|
|
|
81
81
|
id: string;
|
|
82
82
|
schemaVersion: number;
|
|
83
83
|
scopes: SettingScope[];
|
|
84
|
+
/**
|
|
85
|
+
* A kernel-consumed semantic role. The model role is deliberately generic:
|
|
86
|
+
* ADR 0019 lets a Package opt the User into model choice without teaching
|
|
87
|
+
* the kernel that Package's identity or policy.
|
|
88
|
+
*/
|
|
89
|
+
role?: "model";
|
|
84
90
|
schema: PackageSettingSchema;
|
|
85
91
|
}
|
|
86
92
|
|
|
@@ -130,6 +136,7 @@ export interface FrockBotManifest {
|
|
|
130
136
|
version: string;
|
|
131
137
|
compatibility: { frockbot: string };
|
|
132
138
|
dependencies: Record<string, string>;
|
|
139
|
+
defaultEnablement?: "enabled" | "disabled";
|
|
133
140
|
contributions: {
|
|
134
141
|
backend?: BackendContribution[];
|
|
135
142
|
runtime?: RuntimeContribution;
|
|
@@ -221,6 +228,18 @@ function decodeDependencies(value: unknown): Record<string, string> {
|
|
|
221
228
|
return dependencies;
|
|
222
229
|
}
|
|
223
230
|
|
|
231
|
+
function decodeDefaultEnablement(
|
|
232
|
+
value: unknown,
|
|
233
|
+
): FrockBotManifest["defaultEnablement"] {
|
|
234
|
+
if (value === undefined) return undefined;
|
|
235
|
+
if (value !== "enabled" && value !== "disabled") {
|
|
236
|
+
throw new Error(
|
|
237
|
+
'manifest defaultEnablement must be "enabled" or "disabled"',
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
|
|
224
243
|
function decodeIdentity(
|
|
225
244
|
value: Record<string, unknown>,
|
|
226
245
|
): Pick<FrockBotManifest, "id" | "displayName" | "version" | "permissions"> {
|
|
@@ -301,6 +320,7 @@ function isV3OrLater(value: Record<string, unknown>): boolean {
|
|
|
301
320
|
|
|
302
321
|
function decodeV2(value: Record<string, unknown>): FrockBotManifest {
|
|
303
322
|
const identity = decodeIdentity(value);
|
|
323
|
+
const defaultEnablement = decodeDefaultEnablement(value.defaultEnablement);
|
|
304
324
|
if (!isRecord(value.compatibility)) {
|
|
305
325
|
throw new Error("manifest compatibility must be an object");
|
|
306
326
|
}
|
|
@@ -437,6 +457,7 @@ function decodeV2(value: Record<string, unknown>): FrockBotManifest {
|
|
|
437
457
|
frockbot: requiredString(value.compatibility, "frockbot"),
|
|
438
458
|
},
|
|
439
459
|
dependencies: decodeDependencies(value.dependencies),
|
|
460
|
+
...(defaultEnablement ? { defaultEnablement } : {}),
|
|
440
461
|
contributions,
|
|
441
462
|
};
|
|
442
463
|
}
|
|
@@ -905,6 +926,44 @@ function safeSchema(value: unknown): PackageSettingSchema {
|
|
|
905
926
|
return decodeSafeSchema(value, 0);
|
|
906
927
|
}
|
|
907
928
|
|
|
929
|
+
/**
|
|
930
|
+
* The one object contract the kernel interprets from a setting value. Keeping
|
|
931
|
+
* this exact prevents a Package from smuggling provider policy into the model
|
|
932
|
+
* seam while still letting ordinary settings use the supported schema subset.
|
|
933
|
+
*/
|
|
934
|
+
function assertModelBindingSchema(schema: PackageSettingSchema): void {
|
|
935
|
+
const fields = Reflect.ownKeys(schema);
|
|
936
|
+
const properties = schema.properties;
|
|
937
|
+
const required = schema.required;
|
|
938
|
+
if (
|
|
939
|
+
fields.length !== 4 ||
|
|
940
|
+
!fields.every((field) =>
|
|
941
|
+
["type", "properties", "required", "additionalProperties"].includes(
|
|
942
|
+
String(field),
|
|
943
|
+
),
|
|
944
|
+
) ||
|
|
945
|
+
schema.type !== "object" ||
|
|
946
|
+
schema.additionalProperties !== false ||
|
|
947
|
+
!properties ||
|
|
948
|
+
Reflect.ownKeys(properties).length !== 2 ||
|
|
949
|
+
!Object.hasOwn(properties, "connectionId") ||
|
|
950
|
+
!Object.hasOwn(properties, "providerModelId") ||
|
|
951
|
+
Reflect.ownKeys(properties.connectionId ?? {}).length !== 1 ||
|
|
952
|
+
properties.connectionId?.type !== "string" ||
|
|
953
|
+
Reflect.ownKeys(properties.providerModelId ?? {}).length !== 1 ||
|
|
954
|
+
properties.providerModelId?.type !== "string" ||
|
|
955
|
+
!required ||
|
|
956
|
+
required.length !== 2 ||
|
|
957
|
+
new Set(required).size !== 2 ||
|
|
958
|
+
!required.includes("connectionId") ||
|
|
959
|
+
!required.includes("providerModelId")
|
|
960
|
+
) {
|
|
961
|
+
throw new Error(
|
|
962
|
+
'manifest model setting schema must be exactly an object with required string properties "connectionId" and "providerModelId" and no additional properties',
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
908
967
|
function decodeCapabilityAdmission(value: unknown): {
|
|
909
968
|
turnTypes: TurnTypeV1[];
|
|
910
969
|
subagentRoles?: string[];
|
|
@@ -984,7 +1043,13 @@ function settingDefinitions(
|
|
|
984
1043
|
// that round-trips through this decoder decodes again unchanged.
|
|
985
1044
|
exactFields(
|
|
986
1045
|
setting,
|
|
987
|
-
[
|
|
1046
|
+
[
|
|
1047
|
+
"id",
|
|
1048
|
+
"schemaVersion",
|
|
1049
|
+
"schema",
|
|
1050
|
+
"scopes",
|
|
1051
|
+
...(scope === "package" ? ["role"] : []),
|
|
1052
|
+
],
|
|
988
1053
|
"manifest setting definition",
|
|
989
1054
|
);
|
|
990
1055
|
const schemaVersion = setting.schemaVersion;
|
|
@@ -1019,11 +1084,17 @@ function settingDefinitions(
|
|
|
1019
1084
|
) {
|
|
1020
1085
|
throw new Error("manifest setting scopes must contain user or bot");
|
|
1021
1086
|
}
|
|
1087
|
+
if (setting.role !== undefined && setting.role !== "model") {
|
|
1088
|
+
throw new Error('manifest setting role must be "model"');
|
|
1089
|
+
}
|
|
1090
|
+
const schema = safeSchema(setting.schema);
|
|
1091
|
+
if (setting.role === "model") assertModelBindingSchema(schema);
|
|
1022
1092
|
return {
|
|
1023
1093
|
id: definitionId(setting),
|
|
1024
1094
|
schemaVersion: schemaVersion as number,
|
|
1025
1095
|
scopes: scopes as SettingScope[],
|
|
1026
|
-
|
|
1096
|
+
...(setting.role === undefined ? {} : { role: setting.role }),
|
|
1097
|
+
schema,
|
|
1027
1098
|
};
|
|
1028
1099
|
});
|
|
1029
1100
|
}
|
|
@@ -1196,6 +1267,7 @@ export function decodeFrockBotManifest(value: unknown): FrockBotManifest {
|
|
|
1196
1267
|
"permissions",
|
|
1197
1268
|
"compatibility",
|
|
1198
1269
|
"dependencies",
|
|
1270
|
+
"defaultEnablement",
|
|
1199
1271
|
"contributions",
|
|
1200
1272
|
...(isV3OrLater(value) ? ["configuration"] : []),
|
|
1201
1273
|
],
|