@jini-ai/core 0.4.0 → 0.4.2
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/CHANGELOG.md +11 -0
- package/README.md +18 -1
- package/dist/agent-tools.d.ts +1 -0
- package/dist/approval.d.ts +134 -0
- package/dist/approval.js +141 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/model-facing-tool-errors.d.ts +152 -0
- package/dist/model-facing-tool-errors.js +124 -0
- package/dist/native-regexp.d.ts +10 -0
- package/dist/native-regexp.js +2 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/sse.d.ts +60 -0
- package/dist/primitives/sse.js +122 -0
- package/dist/redact.js +4 -3
- package/dist/registration-kit.d.ts +47 -0
- package/dist/registration-kit.js +55 -0
- package/dist/settings/agent-tools.d.ts +10 -0
- package/dist/settings/agent-tools.js +204 -0
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +157 -0
- package/dist/settings/agent-writable-preferences.d.ts +67 -0
- package/dist/settings/agent-writable-preferences.js +132 -0
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +83 -0
- package/dist/settings/change-feed.js +61 -0
- package/dist/settings/definitions-dispatch.d.ts +56 -0
- package/dist/settings/definitions-dispatch.js +86 -0
- package/dist/settings/dictionaries/index.d.ts +48 -0
- package/dist/settings/dictionaries/index.js +69 -0
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ar.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.bn.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.de.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.es.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fa.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hi.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hu.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.id.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.it.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ja.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ko.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pl.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ru.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.th.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.tr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.uk.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ur.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +27 -0
- package/dist/settings/ensure-definitions.d.ts +101 -0
- package/dist/settings/ensure-definitions.js +101 -0
- package/dist/settings/errors.d.ts +42 -0
- package/dist/settings/errors.js +46 -0
- package/dist/settings/express/cms-adapter.d.ts +20 -0
- package/dist/settings/express/cms-adapter.js +124 -0
- package/dist/settings/express/contracts.d.ts +135 -0
- package/dist/settings/express/contracts.js +2 -0
- package/dist/settings/express/http.d.ts +10 -0
- package/dist/settings/express/http.js +211 -0
- package/dist/settings/express/index.d.ts +5 -0
- package/dist/settings/express/index.js +4 -0
- package/dist/settings/express/shared.d.ts +68 -0
- package/dist/settings/express/shared.js +54 -0
- package/dist/settings/express/stream.d.ts +24 -0
- package/dist/settings/express/stream.js +147 -0
- package/dist/settings/index.d.ts +40 -0
- package/dist/settings/index.js +37 -0
- package/dist/settings/ports.d.ts +118 -0
- package/dist/settings/ports.js +2 -0
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +54 -0
- package/dist/settings/purge-service.js +106 -0
- package/dist/settings/repo.memory.d.ts +119 -0
- package/dist/settings/repo.memory.js +199 -0
- package/dist/settings/settings.d.ts +139 -0
- package/dist/settings/settings.js +259 -0
- package/dist/settings/tool-registrations.d.ts +54 -0
- package/dist/settings/tool-registrations.js +250 -0
- package/dist/settings/types.d.ts +125 -0
- package/dist/settings/types.js +3 -0
- package/dist/settings/ui-tab-definitions.d.ts +41 -0
- package/dist/settings/ui-tab-definitions.js +148 -0
- package/dist/settings/write-service.d.ts +320 -0
- package/dist/settings/write-service.js +683 -0
- package/dist/surface-exchanges.d.ts +209 -0
- package/dist/surface-exchanges.js +55 -0
- package/dist/tool-metadata.d.ts +71 -0
- package/dist/tool-metadata.js +56 -0
- package/dist/tool-permissions.d.ts +76 -0
- package/dist/tool-permissions.js +57 -0
- package/dist/tool-registry.d.ts +3 -0
- package/package.json +62 -4
- package/dist/internal.d.ts +0 -41
- package/dist/internal.js +0 -39
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { adaptLegacyAuthorize } from "../tool-permissions.js";
|
|
2
|
+
import { buildSettingsValueWriteHandler } from "./agent-value-write-tools.js";
|
|
3
|
+
import { AGENT_WRITE_CONFIRMATION_SETTINGS, AGENT_WRITE_DENIED_SETTINGS, findAgentWriteRule } from "./agent-write-denylist.js";
|
|
4
|
+
import { buildDomainRegistrations, indexCatalogById, optionalString, requireInputRecord, requireNoInput, requireString, withSchemaOnRejection } from "../registration-kit.js";
|
|
5
|
+
import { requireToolPermission } from "../tool-permissions.js";
|
|
6
|
+
import { getSettingsAgentToolCatalog } from "./agent-tools.js";
|
|
7
|
+
import { AGENT_PREFERENCE_WRITE_SCOPE, AGENT_WRITABLE_PREFERENCE_IDS, resolveAgentWritablePreference, } from "./agent-writable-preferences.js";
|
|
8
|
+
import { DefinitionNotFoundError, DefinitionTombstonedError, ScopeNotAllowedError, ValueValidationFailedError, } from "./errors.js";
|
|
9
|
+
import { getEffective, resolveDefinition, resolveDefinitionRaw } from "./settings.js";
|
|
10
|
+
import { SCOPE_BIT } from "./types.js";
|
|
11
|
+
import { set as setSettingValue } from "./write-service.js";
|
|
12
|
+
const CATALOG_BY_ID = indexCatalogById({ catalog: getSettingsAgentToolCatalog() });
|
|
13
|
+
/** Permission gating a read that names a DIFFERENT principal than the caller — mirrors a host's
|
|
14
|
+
* own HTTP admin layer's equivalent constant (kept as a literal here per this file's header; the
|
|
15
|
+
* two are data, not logic, so duplication carries no drift risk beyond a string comparison). */
|
|
16
|
+
const CROSS_PRINCIPAL_SETTINGS_READ_PERMISSION = "settings.user.read";
|
|
17
|
+
/**
|
|
18
|
+
* Decides which principal's user layer a settings read tool may target — the tool-wiring twin of
|
|
19
|
+
* a host's own HTTP admin layer's equivalent read-target resolver (see this file's header for why
|
|
20
|
+
* it is duplicated rather than imported).
|
|
21
|
+
*
|
|
22
|
+
* @throws {ForbiddenError} (`core/commands`, via `requireToolPermission`) If `requestedPrincipalId`
|
|
23
|
+
* names a different principal than the caller and the caller lacks `settings.user.read`.
|
|
24
|
+
* @returns The principal id to read (`undefined` = skip the user layer entirely).
|
|
25
|
+
* @complexity O(1); at most one `authorize()` call (none for a self-read or no-op read).
|
|
26
|
+
* @overallScore 100
|
|
27
|
+
*/
|
|
28
|
+
async function resolveOwnOrOtherPrincipalRead(routeDeps, required) {
|
|
29
|
+
const { requestedPrincipalId, callerPrincipalId } = required;
|
|
30
|
+
// Defaults to the CALLER, never to `undefined`. `undefined` does not mean "the caller" to
|
|
31
|
+
// `getEffective` — it means "skip the user layer entirely" (`settings.ts`'s
|
|
32
|
+
// `if (workspaceId && principalId)` guard), so an omitted `principalId` would otherwise make
|
|
33
|
+
// every read tool report the workspace/global/default value while silently ignoring the
|
|
34
|
+
// operator's own — a read tool that cannot see the layer the operator is actually looking at
|
|
35
|
+
// does not fail loudly; it produces confident wrong answers.
|
|
36
|
+
const target = requestedPrincipalId ?? callerPrincipalId;
|
|
37
|
+
if (target === callerPrincipalId)
|
|
38
|
+
return target;
|
|
39
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: callerPrincipalId, permission: CROSS_PRINCIPAL_SETTINGS_READ_PERMISSION }, { entityType: "setting-value" });
|
|
40
|
+
return requestedPrincipalId;
|
|
41
|
+
}
|
|
42
|
+
function layerValueOf(record) {
|
|
43
|
+
return record && record.state === "set" ? record.valueJson : null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Builds `write-service.ts`'s own five-field dep shape from `RouteDeps`.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately duplicates a host's own HTTP admin layer's equivalent field-mapping helper rather
|
|
49
|
+
* than importing it, for the reason this file's header already gives about
|
|
50
|
+
* `resolveOwnOrOtherPrincipalRead`: this is domain-owned wiring, and importing from a host's HTTP
|
|
51
|
+
* admin layer would invert the ports/adapters direction. It is a field mapping, not logic, so the
|
|
52
|
+
* two copies carry no behavioral drift risk — and `write-service.ts` is the single chokepoint
|
|
53
|
+
* either way, so a divergence here could only produce a missing dependency, which fails loudly.
|
|
54
|
+
*/
|
|
55
|
+
function toWriteDeps(routeDeps) {
|
|
56
|
+
return {
|
|
57
|
+
repo: routeDeps.settingsRepo,
|
|
58
|
+
clock: routeDeps.clock,
|
|
59
|
+
ids: routeDeps.idGen,
|
|
60
|
+
authorize: routeDeps.authorize,
|
|
61
|
+
principals: routeDeps.principalRepo,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Errors a DIFFERENT input would fix, for `withSchemaOnRejection`'s decoration.
|
|
66
|
+
*
|
|
67
|
+
* `ValueValidationFailedError` is the common one — the model sent a value of the wrong shape for
|
|
68
|
+
* the chosen setting, and republishing the schema (with its per-key value hints) lets it correct
|
|
69
|
+
* in one turn instead of guessing. The two definition errors are included because they are
|
|
70
|
+
* genuinely reachable from a bad `setting`: an id that passed the enum but names a definition this
|
|
71
|
+
* workspace has tombstoned. A `ForbiddenError` is deliberately absent — no input change fixes a
|
|
72
|
+
* missing grant, and decorating it would imply the call is worth retrying.
|
|
73
|
+
*/
|
|
74
|
+
function isPreferenceShapeRejection(error) {
|
|
75
|
+
return (error instanceof ValueValidationFailedError ||
|
|
76
|
+
error instanceof DefinitionNotFoundError ||
|
|
77
|
+
error instanceof DefinitionTombstonedError ||
|
|
78
|
+
error instanceof ScopeNotAllowedError);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* This wiring layer's OWN risk classification, authored from what each handler below actually
|
|
82
|
+
* calls. See `DerivedRiskByToolId` in the kit for why it is independent of the catalog's own
|
|
83
|
+
* `sideEffects` declaration — and note that the two excluded bulk/schema writes appear NOWHERE here, which is
|
|
84
|
+
* itself the strongest of the guards: an unclassified id cannot be wired at all.
|
|
85
|
+
*/
|
|
86
|
+
export const settingsDerivedRisk = new Map([
|
|
87
|
+
// -> write-service.set(): one value override and one revision, with optional human confirmation.
|
|
88
|
+
["settings_set_value", "mutates-durable-state"],
|
|
89
|
+
// -> write-service.clear(): one override marked cleared and one revision; no definition/delete.
|
|
90
|
+
["settings_clear_value", "mutates-durable-state"],
|
|
91
|
+
// -> settingsRepo.listActiveDefinitions() x2 + resolveDefinitionRaw() for aliases: reads only.
|
|
92
|
+
["settings_list_definitions", "none"],
|
|
93
|
+
// -> settingsRepo.listActiveDefinitions() x2 + getEffective() per key: reads only, no write path.
|
|
94
|
+
["settings_get_effective", "none"],
|
|
95
|
+
// -> resolveDefinition() + settingsRepo.getGlobalValue/getWorkspaceValue/getUserValue: reads only.
|
|
96
|
+
["settings_get_raw", "none"],
|
|
97
|
+
// -> write-service.set() at scope=user against the caller's own principal: writes a value row
|
|
98
|
+
// and a revision-ledger entry. Durable, and correctly classified as such even though every key
|
|
99
|
+
// it can reach is a reversible display preference — the classification describes what the
|
|
100
|
+
// handler DOES, not how much the operator would mind.
|
|
101
|
+
["settings_set_ui_preference", "mutates-durable-state"],
|
|
102
|
+
]);
|
|
103
|
+
/** Settings catalog entries this pass does not wire, and why — see `agent-tools.ts`'s own per-entry comments for the full reasoning. */
|
|
104
|
+
const UNWIRED_SETTINGS_TOOL_IDS = new Set([
|
|
105
|
+
// EXCLUDED BY DESIGN: bulk variant — clears every value in an operator-named namespace at a
|
|
106
|
+
// scope in one call, irreversible.
|
|
107
|
+
"settings_reset",
|
|
108
|
+
// EXCLUDED BY DESIGN: schema-level, not value-level — can rename/retype/deprecate/tombstone the
|
|
109
|
+
// definition a key resolves through, reinterpreting every existing stored value for that key
|
|
110
|
+
// platform-wide.
|
|
111
|
+
"settings_register_definitions",
|
|
112
|
+
]);
|
|
113
|
+
export function buildSettingsRegistrations(routeDeps) {
|
|
114
|
+
const handlers = {
|
|
115
|
+
settings_set_value: buildSettingsValueWriteHandler(routeDeps, "settings_set_value"),
|
|
116
|
+
settings_clear_value: buildSettingsValueWriteHandler(routeDeps, "settings_clear_value"),
|
|
117
|
+
settings_list_definitions: async (ctx) => {
|
|
118
|
+
requireNoInput({ input: ctx.input });
|
|
119
|
+
await routeDeps.settingsReady;
|
|
120
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "settings.read.definitions" }, { entityType: "setting-definition" });
|
|
121
|
+
const [platformDefs, siteDefs] = await Promise.all([
|
|
122
|
+
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: null }),
|
|
123
|
+
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: routeDeps.workspaceId }),
|
|
124
|
+
]);
|
|
125
|
+
const deniedRules = [...AGENT_WRITE_DENIED_SETTINGS, ...(routeDeps.extraDeniedSettings ?? [])];
|
|
126
|
+
const confirmationRules = [...(routeDeps.confirmationSettings ?? AGENT_WRITE_CONFIRMATION_SETTINGS), ...(routeDeps.extraConfirmationSettings ?? [])];
|
|
127
|
+
// Only aliases need an extra lookup. Their target's schema/scope and BOTH coordinates'
|
|
128
|
+
// key policies must agree with the write handler; dangling/tombstoned targets fail closed.
|
|
129
|
+
const data = (await Promise.all([...platformDefs, ...siteDefs].map(async (def) => {
|
|
130
|
+
const resolved = def.status === "alias"
|
|
131
|
+
? await resolveDefinitionRaw({ repo: routeDeps.settingsRepo }, { namespace: def.namespace, key: def.key, workspaceId: routeDeps.workspaceId })
|
|
132
|
+
: def;
|
|
133
|
+
const coordinates = resolved ? [def, resolved] : [def];
|
|
134
|
+
const denied = coordinates.some((coordinate) => !!findAgentWriteRule(deniedRules, coordinate));
|
|
135
|
+
const confirmationRequired = coordinates.some((coordinate) => !!findAgentWriteRule(confirmationRules, coordinate));
|
|
136
|
+
const scopes = resolved?.scopes ?? def.scopes;
|
|
137
|
+
return { namespace: def.namespace, key: def.key, ownerKind: def.ownerKind, scopes, status: def.status, version: def.version,
|
|
138
|
+
schema: resolved?.schema ?? def.schema,
|
|
139
|
+
agentWritable: !!resolved && resolved.status !== "tombstone" && !denied && (!confirmationRequired || !!routeDeps.confirmWrite) && (scopes & (SCOPE_BIT.workspace | SCOPE_BIT.user)) !== 0,
|
|
140
|
+
confirmationRequired };
|
|
141
|
+
})))
|
|
142
|
+
.sort((a, b) => a.namespace.localeCompare(b.namespace) || a.key.localeCompare(b.key));
|
|
143
|
+
return { data };
|
|
144
|
+
},
|
|
145
|
+
settings_get_effective: async (ctx) => {
|
|
146
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
147
|
+
const namespace = requireString({ input: input, key: "namespace" });
|
|
148
|
+
await routeDeps.settingsReady;
|
|
149
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "settings.read" }, { entityType: "setting-value" });
|
|
150
|
+
const workspaceId = routeDeps.workspaceId;
|
|
151
|
+
const principalId = await resolveOwnOrOtherPrincipalRead(routeDeps, {
|
|
152
|
+
requestedPrincipalId: optionalString({ input: input, key: "principalId" }),
|
|
153
|
+
callerPrincipalId: ctx.principal.id,
|
|
154
|
+
});
|
|
155
|
+
const [platformDefs, siteDefs] = await Promise.all([
|
|
156
|
+
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId: null }),
|
|
157
|
+
routeDeps.settingsRepo.listActiveDefinitions({ workspaceId }),
|
|
158
|
+
]);
|
|
159
|
+
const keys = new Set([...platformDefs, ...siteDefs].filter((d) => d.namespace === namespace).map((d) => d.key));
|
|
160
|
+
const data = [];
|
|
161
|
+
for (const key of keys) {
|
|
162
|
+
const resolved = await getEffective({ repo: routeDeps.settingsRepo }, { namespace, key, scopeContext: { workspaceId, principalId } });
|
|
163
|
+
if (resolved)
|
|
164
|
+
data.push({ key, value: resolved.value, sourceLayer: resolved.sourceLayer, defVersion: resolved.defVersion });
|
|
165
|
+
}
|
|
166
|
+
return { data };
|
|
167
|
+
},
|
|
168
|
+
settings_get_raw: async (ctx) => {
|
|
169
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
170
|
+
const namespace = requireString({ input: input, key: "namespace" });
|
|
171
|
+
const key = requireString({ input: input, key: "key" });
|
|
172
|
+
await routeDeps.settingsReady;
|
|
173
|
+
await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "settings.read.raw" }, { entityType: "setting-value" });
|
|
174
|
+
const workspaceId = routeDeps.workspaceId;
|
|
175
|
+
const principalId = await resolveOwnOrOtherPrincipalRead(routeDeps, {
|
|
176
|
+
requestedPrincipalId: optionalString({ input: input, key: "principalId" }),
|
|
177
|
+
callerPrincipalId: ctx.principal.id,
|
|
178
|
+
});
|
|
179
|
+
const definition = await resolveDefinition({ repo: routeDeps.settingsRepo }, { namespace, key, workspaceId });
|
|
180
|
+
if (!definition)
|
|
181
|
+
throw new Error(`definition '${namespace}.${key}' was not found`);
|
|
182
|
+
const [globalValue, workspaceValue, userValue] = await Promise.all([
|
|
183
|
+
routeDeps.settingsRepo.getGlobalValue(definition.settingId),
|
|
184
|
+
routeDeps.settingsRepo.getWorkspaceValue({ workspaceId, settingId: definition.settingId }),
|
|
185
|
+
principalId ? routeDeps.settingsRepo.getUserValue({ workspaceId, principalId, settingId: definition.settingId }) : Promise.resolve(null),
|
|
186
|
+
]);
|
|
187
|
+
return {
|
|
188
|
+
key: `${namespace}.${key}`,
|
|
189
|
+
global: layerValueOf(globalValue),
|
|
190
|
+
workspace: layerValueOf(workspaceValue),
|
|
191
|
+
user: layerValueOf(userValue),
|
|
192
|
+
default: definition.defaultValue,
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
settings_set_ui_preference: async (ctx) => {
|
|
196
|
+
const input = requireInputRecord({ input: ctx.input });
|
|
197
|
+
const settingId = requireString({ input: input, key: "setting" });
|
|
198
|
+
// Re-checked here even though the published schema carries the same enum. See
|
|
199
|
+
// `agent-writable-preferences.ts` on why: the enum is enforced by a caller's descriptor
|
|
200
|
+
// validation, and this is the check that still holds if a stale or bypassed descriptor lets
|
|
201
|
+
// an unlisted id through. Rejected BEFORE `settingsUiTabsReady` is awaited so an unlisted id
|
|
202
|
+
// cannot even be used to probe which definitions exist.
|
|
203
|
+
const preference = resolveAgentWritablePreference(settingId);
|
|
204
|
+
if (!preference) {
|
|
205
|
+
throw new Error(`'${settingId}' is not an agent-writable preference. Allowed: ${AGENT_WRITABLE_PREFERENCE_IDS.join(", ")}.`);
|
|
206
|
+
}
|
|
207
|
+
// `in` rather than a truthiness check: `false` and `0` are legitimate values here
|
|
208
|
+
// (`soundEnabled`, `desktopEnabled`), and rejecting them would make the two boolean
|
|
209
|
+
// preferences settable in one direction only.
|
|
210
|
+
if (!("value" in input)) {
|
|
211
|
+
throw new Error(`a 'value' is required to set '${settingId}'`);
|
|
212
|
+
}
|
|
213
|
+
const value = input.value;
|
|
214
|
+
// This tool's definitions are registered by `ensureSettingsUiTabDefinitions()` at boot, not
|
|
215
|
+
// by any legacy-migration promise a host might also cover — awaiting the wrong promise would
|
|
216
|
+
// make an early call fail with a spurious "definition was not found".
|
|
217
|
+
await routeDeps.settingsUiTabsReady;
|
|
218
|
+
// No `requireToolPermission` call here, unlike every read above. That is deliberate and is
|
|
219
|
+
// the opposite of the reads' situation: the read paths carry no `authorize()` of their own,
|
|
220
|
+
// so this layer must supply it, whereas `write-service.set()` IS the authorization
|
|
221
|
+
// chokepoint and derives the permission itself from the scope and target. Adding a. See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
222
|
+
// pre-check would mean this file naming a permission string that the chokepoint might later
|
|
223
|
+
// derive differently — the exact fail-open drift `deriveRequiredPermission`'s own header
|
|
224
|
+
// (Red-Team ) says must not be duplicated. Omitting `principalId` is what makes the. See docs/decisions/DR-003-settings-ledger-invariants.md.
|
|
225
|
+
// derived permission `settings.user.self.write`.
|
|
226
|
+
const { value: stored, revisionSeq } = await withSchemaOnRejection({
|
|
227
|
+
toolId: "settings_set_ui_preference",
|
|
228
|
+
catalog: CATALOG_BY_ID,
|
|
229
|
+
isShapeRejection: ({ error }) => isPreferenceShapeRejection(error), fn: () => setSettingValue({
|
|
230
|
+
deps: toWriteDeps(routeDeps),
|
|
231
|
+
input: {
|
|
232
|
+
namespace: preference.namespace,
|
|
233
|
+
key: preference.key,
|
|
234
|
+
scope: AGENT_PREFERENCE_WRITE_SCOPE,
|
|
235
|
+
value,
|
|
236
|
+
workspaceId: routeDeps.workspaceId,
|
|
237
|
+
// `principalId` is deliberately NOT passed. `write-service.set()` falls back to
|
|
238
|
+
// `callerPrincipalId` for a user-scoped write, so the caller's own layer is the only
|
|
239
|
+
// reachable target — and the self-vs-other permission derivation sees a self-write.
|
|
240
|
+
callerPrincipalId: ctx.principal.id,
|
|
241
|
+
authWorkspaceId: routeDeps.workspaceId,
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
});
|
|
245
|
+
return { setting: settingId, value: stored, scope: AGENT_PREFERENCE_WRITE_SCOPE, revisionSeq };
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
return buildDomainRegistrations({ domain: "settings", catalogModule: "settings/agent-tools.ts", catalog: CATALOG_BY_ID, handlers, derivedRisk: settingsDerivedRisk }, { unwiredToolIds: UNWIRED_SETTINGS_TOOL_IDS });
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=tool-registrations.js.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ISODateTime, JsonValue, UUID } from "../primitives/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* @file Settings domain types.
|
|
4
|
+
*
|
|
5
|
+
* Purpose:
|
|
6
|
+
* Record shapes + the schema-validation vocabulary shared by `settings.ts`,
|
|
7
|
+
* `write-service.ts`, `purge-service.ts`, and both repo adapters. Interfaces
|
|
8
|
+
* and types only — no feature logic (mirrors `identity/types.ts`).
|
|
9
|
+
*/
|
|
10
|
+
export type SettingScope = "global" | "workspace" | "user";
|
|
11
|
+
export type SettingOwnerKind = "core" | "site" | "theme";
|
|
12
|
+
export type DefinitionStatus = "active" | "alias" | "deprecated" | "tombstone";
|
|
13
|
+
export type ValueState = "set" | "cleared";
|
|
14
|
+
export type RevisionEntityKind = "definition" | "value";
|
|
15
|
+
export type RevisionOp = "register" | "alias" | "retype"
|
|
16
|
+
/**
|
|
17
|
+
* A `core` definition's stored `default_json` was reconciled to the default
|
|
18
|
+
* its source declares (`write-service.ts`'s `reconcileDefinitionDefault`).
|
|
19
|
+
* Distinct from `retype`: the schema is unchanged, so no stored value needs
|
|
20
|
+
* coercion and the definition version does NOT advance. Only unset-value
|
|
21
|
+
* resolution changes.
|
|
22
|
+
*/
|
|
23
|
+
| "redefault" | "deprecate" | "tombstone" | "set" | "clear" | "purge" | "coerce";
|
|
24
|
+
/** Scope bitmask values: global=1, workspace=2, user=4. Legal range 1..7. */
|
|
25
|
+
export declare const SCOPE_BIT: {
|
|
26
|
+
readonly global: 1;
|
|
27
|
+
readonly workspace: 2;
|
|
28
|
+
readonly user: 4;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* A deliberately small, total value-schema vocabulary (Article III — this
|
|
32
|
+
* feature registers string/number/boolean/enum settings; a generic JSON
|
|
33
|
+
* Schema engine is not needed and would be speculative generality). Every
|
|
34
|
+
* variant is exhaustively validatable by `validateValue`.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* `{type:"json"}` is a deliberately narrow, scalar-ADJACENT exception to the
|
|
38
|
+
* "deliberately small" vocabulary above: it accepts any JSON value
|
|
39
|
+
* (object/array/scalar) and validates nothing about its internal shape —
|
|
40
|
+
* that is the registering feature's own write-path responsibility, never
|
|
41
|
+
* this ledger's job.
|
|
42
|
+
*
|
|
43
|
+
* Enforcement rule that must survive any refactor: Code Review must confirm
|
|
44
|
+
* that EVERY use is genuinely unbounded, list-shaped data with no scalar
|
|
45
|
+
* decomposition available — it is not a general escape hatch for fields that
|
|
46
|
+
* could be scalar-decomposed instead. The check is per-use, deliberately not
|
|
47
|
+
* a headcount. See `docs/decisions/settings-json-schema-variant.md` for the
|
|
48
|
+
* originating rationale and a related non-null-default invariant.
|
|
49
|
+
*/
|
|
50
|
+
export type SettingValueSchema = {
|
|
51
|
+
type: "string";
|
|
52
|
+
nullable?: boolean;
|
|
53
|
+
} | {
|
|
54
|
+
type: "number";
|
|
55
|
+
nullable?: boolean;
|
|
56
|
+
} | {
|
|
57
|
+
type: "boolean";
|
|
58
|
+
nullable?: boolean;
|
|
59
|
+
} | {
|
|
60
|
+
type: "enum";
|
|
61
|
+
values: readonly string[];
|
|
62
|
+
nullable?: boolean;
|
|
63
|
+
} | {
|
|
64
|
+
type: "json";
|
|
65
|
+
nullable?: boolean;
|
|
66
|
+
};
|
|
67
|
+
export interface SettingDefinitionRecord {
|
|
68
|
+
settingId: UUID;
|
|
69
|
+
version: number;
|
|
70
|
+
/** NULL = platform def (core/theme); non-null = site-owned. */
|
|
71
|
+
workspaceId: UUID | null;
|
|
72
|
+
namespace: string;
|
|
73
|
+
key: string;
|
|
74
|
+
ownerKind: SettingOwnerKind;
|
|
75
|
+
ownerId: string | null;
|
|
76
|
+
schema: SettingValueSchema;
|
|
77
|
+
defaultValue: JsonValue | null;
|
|
78
|
+
/** Bitmask over `SCOPE_BIT`, legal range 1..7. */
|
|
79
|
+
scopes: number;
|
|
80
|
+
secret: boolean;
|
|
81
|
+
status: DefinitionStatus;
|
|
82
|
+
aliasOfNamespace: string | null;
|
|
83
|
+
aliasOfKey: string | null;
|
|
84
|
+
/** Present only when `version > 1`; a total coercer id/tag for the prior version. */
|
|
85
|
+
coercionTag: string | null;
|
|
86
|
+
createdAt: ISODateTime;
|
|
87
|
+
updatedAt: ISODateTime;
|
|
88
|
+
}
|
|
89
|
+
export interface SettingValueRecord {
|
|
90
|
+
settingId: UUID;
|
|
91
|
+
scope: SettingScope;
|
|
92
|
+
/** Present for workspace/user scope; null for global. */
|
|
93
|
+
workspaceId: UUID | null;
|
|
94
|
+
/** Present for user scope only. */
|
|
95
|
+
principalId: UUID | null;
|
|
96
|
+
valueJson: JsonValue | null;
|
|
97
|
+
state: ValueState;
|
|
98
|
+
defVersion: number;
|
|
99
|
+
seq: number;
|
|
100
|
+
updatedBy: UUID;
|
|
101
|
+
updatedAt: ISODateTime;
|
|
102
|
+
originPluginId: string | null;
|
|
103
|
+
}
|
|
104
|
+
export interface SettingRevisionRecord {
|
|
105
|
+
seq: number;
|
|
106
|
+
entityKind: RevisionEntityKind;
|
|
107
|
+
settingId: UUID;
|
|
108
|
+
scope: SettingScope | null;
|
|
109
|
+
workspaceId: UUID | null;
|
|
110
|
+
principalId: UUID | null;
|
|
111
|
+
op: RevisionOp;
|
|
112
|
+
beforeJson: JsonValue | null;
|
|
113
|
+
afterJson: JsonValue | null;
|
|
114
|
+
defVersion: number;
|
|
115
|
+
actor: UUID;
|
|
116
|
+
originPluginId: string | null;
|
|
117
|
+
changeSetId: string | null;
|
|
118
|
+
createdAt: ISODateTime;
|
|
119
|
+
}
|
|
120
|
+
/** The context a resolver/write call is evaluated against. */
|
|
121
|
+
export interface SettingScopeContext {
|
|
122
|
+
workspaceId?: UUID | undefined;
|
|
123
|
+
principalId?: UUID | undefined;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { UUID } from "../primitives/index.js";
|
|
2
|
+
import { type EnsureSettingDefinitionsDeps } from "./ensure-definitions.js";
|
|
3
|
+
/**
|
|
4
|
+
* @file Boot-time definition registration for the settings-dialog tabs that
|
|
5
|
+
* need nothing but ledger storage — Instructions, Notifications, and Privacy.
|
|
6
|
+
*
|
|
7
|
+
* These three sit together because they are the same shape of problem, not
|
|
8
|
+
* because they are the same domain: each is a `value` + `onChange` component
|
|
9
|
+
* from a shared UI library with no async port, so the entire host-side cost
|
|
10
|
+
* is "register some keys and map them". A tab that needs real backend work
|
|
11
|
+
* (routes, a port) should keep its own module rather than accreting into
|
|
12
|
+
* this one.
|
|
13
|
+
*
|
|
14
|
+
* All three read and write through the generic settings ledger routes a host
|
|
15
|
+
* exposes, gated by the ledger's own scope-derived `settings.workspace.write`
|
|
16
|
+
* check. There is no bespoke get/set service and no per-tab permission — the
|
|
17
|
+
* reason the shared `ensureSettingDefinitions` helper exists.
|
|
18
|
+
*/
|
|
19
|
+
/** Custom instructions applied to every admin-assistant conversation. */
|
|
20
|
+
export declare const INSTRUCTIONS_NAMESPACE = "core.instructions";
|
|
21
|
+
/** Completion-sound and desktop-notification preferences. */
|
|
22
|
+
export declare const NOTIFICATIONS_NAMESPACE = "core.notifications";
|
|
23
|
+
/** Telemetry consent state. */
|
|
24
|
+
export declare const PRIVACY_NAMESPACE = "core.privacy";
|
|
25
|
+
/** Theme choice and accent color for the settings-dialog surface. */
|
|
26
|
+
export declare const APPEARANCE_NAMESPACE = "core.appearance";
|
|
27
|
+
/** Admin UI locale. */
|
|
28
|
+
export declare const LANGUAGE_NAMESPACE = "core.language";
|
|
29
|
+
export interface EnsureSettingsUiTabDefinitionsInput {
|
|
30
|
+
/** The trusted boot-time actor these writes are attributed to. */
|
|
31
|
+
systemPrincipalId: UUID;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Idempotently registers the Instructions, Notifications, and Privacy
|
|
35
|
+
* definitions. Safe to call on every boot.
|
|
36
|
+
*
|
|
37
|
+
* @complexity O(n) in the total number of definitions (9), each a
|
|
38
|
+
* skip-if-registered check plus at most one `registerDefinitions` call.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ensureSettingsUiTabDefinitions(deps: EnsureSettingDefinitionsDeps, input: EnsureSettingsUiTabDefinitionsInput): Promise<void>;
|
|
41
|
+
//# sourceMappingURL=ui-tab-definitions.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ensureSettingDefinitions, } from "./ensure-definitions.js";
|
|
2
|
+
import { SCOPE_BIT } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* @file Boot-time definition registration for the settings-dialog tabs that
|
|
5
|
+
* need nothing but ledger storage — Instructions, Notifications, and Privacy.
|
|
6
|
+
*
|
|
7
|
+
* These three sit together because they are the same shape of problem, not
|
|
8
|
+
* because they are the same domain: each is a `value` + `onChange` component
|
|
9
|
+
* from a shared UI library with no async port, so the entire host-side cost
|
|
10
|
+
* is "register some keys and map them". A tab that needs real backend work
|
|
11
|
+
* (routes, a port) should keep its own module rather than accreting into
|
|
12
|
+
* this one.
|
|
13
|
+
*
|
|
14
|
+
* All three read and write through the generic settings ledger routes a host
|
|
15
|
+
* exposes, gated by the ledger's own scope-derived `settings.workspace.write`
|
|
16
|
+
* check. There is no bespoke get/set service and no per-tab permission — the
|
|
17
|
+
* reason the shared `ensureSettingDefinitions` helper exists.
|
|
18
|
+
*/
|
|
19
|
+
/** Custom instructions applied to every admin-assistant conversation. */
|
|
20
|
+
export const INSTRUCTIONS_NAMESPACE = "core.instructions";
|
|
21
|
+
/** Completion-sound and desktop-notification preferences. */
|
|
22
|
+
export const NOTIFICATIONS_NAMESPACE = "core.notifications";
|
|
23
|
+
/** Telemetry consent state. */
|
|
24
|
+
export const PRIVACY_NAMESPACE = "core.privacy";
|
|
25
|
+
/** Theme choice and accent color for the settings-dialog surface. */
|
|
26
|
+
export const APPEARANCE_NAMESPACE = "core.appearance";
|
|
27
|
+
/** Admin UI locale. */
|
|
28
|
+
export const LANGUAGE_NAMESPACE = "core.language";
|
|
29
|
+
/**
|
|
30
|
+
* "No consent decision recorded yet", which a host's UI layer may spell as
|
|
31
|
+
* `privacyDecisionAt: null`.
|
|
32
|
+
*
|
|
33
|
+
* A literal `null` default is not registrable (see `SettingDefinitionSpec.
|
|
34
|
+
* defaultValue` — non-secret definitions reject a null default outright), so
|
|
35
|
+
* this needs a sentinel. `0` is safe rather than merely convenient: the field
|
|
36
|
+
* is epoch milliseconds, and 0 is 1970-01-01, which cannot be a real decision
|
|
37
|
+
* timestamp for software that did not exist then. The tab uses this exact
|
|
38
|
+
* distinction — an unset value renders the first-run consent prompt, a set one
|
|
39
|
+
* renders the "decided" state — so collapsing the two would silently re-prompt
|
|
40
|
+
* an operator who has already chosen.
|
|
41
|
+
*/
|
|
42
|
+
const NO_DECISION_SENTINEL = 0;
|
|
43
|
+
/**
|
|
44
|
+
* "No installation id", which a host's UI layer may spell as
|
|
45
|
+
* `installationId: null` (its meaning: telemetry sharing is fully declined).
|
|
46
|
+
* `""` is not a legal id — the tab generates opaque non-empty strings — so it
|
|
47
|
+
* cannot collide with a real one.
|
|
48
|
+
*/
|
|
49
|
+
const NO_INSTALLATION_ID_SENTINEL = "";
|
|
50
|
+
const INSTRUCTIONS_DEFINITIONS = [
|
|
51
|
+
// A host's `InstructionsTab` may collapse an all-empty textarea to `undefined` rather than `''`
|
|
52
|
+
// (its `onChange` contract), so the adapter maps that back to this empty-string default instead
|
|
53
|
+
// of storing a distinguishable "explicitly emptied" state the tab cannot itself produce.
|
|
54
|
+
{ key: "custom", schema: { type: "string" }, defaultValue: "" },
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Notification preferences are the one genuinely PER-OPERATOR surface in this
|
|
58
|
+
* set, so they register `SCOPE_BIT.user` alongside `workspace` and the adapter
|
|
59
|
+
* writes at user scope. Two admins sharing a workspace should not fight over
|
|
60
|
+
* whether a completion sound plays on the other's machine — unlike a tab
|
|
61
|
+
* whose backing resource is genuinely shared workspace state.
|
|
62
|
+
*
|
|
63
|
+
* The `workspace` bit stays in the mask so an operator with no user-scoped row
|
|
64
|
+
* still inherits a workspace-level default rather than falling all the way
|
|
65
|
+
* back to the registered one.
|
|
66
|
+
*/
|
|
67
|
+
const NOTIFICATIONS_SCOPES = SCOPE_BIT.user | SCOPE_BIT.workspace;
|
|
68
|
+
const NOTIFICATIONS_DEFINITIONS = [
|
|
69
|
+
{ key: "soundEnabled", schema: { type: "boolean" }, defaultValue: false, scopes: NOTIFICATIONS_SCOPES },
|
|
70
|
+
// Free-form strings rather than an enum: sound ids are plain strings upstream and the catalog is
|
|
71
|
+
// owned by a host's UI library. Pinning an enum here would make a package-side catalog addition
|
|
72
|
+
// fail validation on an already-registered definition, which is a worse failure than accepting an
|
|
73
|
+
// id the catalog no longer lists (the tab falls back to its own default for an unknown id).
|
|
74
|
+
{ key: "successSoundId", schema: { type: "string" }, defaultValue: "ding", scopes: NOTIFICATIONS_SCOPES },
|
|
75
|
+
{ key: "failureSoundId", schema: { type: "string" }, defaultValue: "buzz", scopes: NOTIFICATIONS_SCOPES },
|
|
76
|
+
// Desktop notifications also need the BROWSER's Notification permission,
|
|
77
|
+
// which is per-browser and not ours to store. This flag is only the
|
|
78
|
+
// operator's intent; the tab reconciles it against the live permission.
|
|
79
|
+
{ key: "desktopEnabled", schema: { type: "boolean" }, defaultValue: false, scopes: NOTIFICATIONS_SCOPES },
|
|
80
|
+
];
|
|
81
|
+
const PRIVACY_DEFINITIONS = [
|
|
82
|
+
// Both telemetry categories default OFF. A "consent state" type may type them optional, but
|
|
83
|
+
// "unset" is carried by `decisionAt` instead — an operator who has decided has a timestamp, and
|
|
84
|
+
// these two then mean exactly what they say.
|
|
85
|
+
{ key: "telemetry.metrics", schema: { type: "boolean" }, defaultValue: false },
|
|
86
|
+
{ key: "telemetry.content", schema: { type: "boolean" }, defaultValue: false },
|
|
87
|
+
{ key: "installationId", schema: { type: "string" }, defaultValue: NO_INSTALLATION_ID_SENTINEL },
|
|
88
|
+
{ key: "decisionAt", schema: { type: "number" }, defaultValue: NO_DECISION_SENTINEL },
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Appearance and Language are per-OPERATOR, same reasoning as Notifications:
|
|
92
|
+
* which theme one admin looks at, and which language they read, are not
|
|
93
|
+
* properties of the workspace. The `workspace` bit stays in the mask so an
|
|
94
|
+
* operator with no user-scoped row still inherits a workspace default.
|
|
95
|
+
*/
|
|
96
|
+
const PER_OPERATOR_SCOPES = SCOPE_BIT.user | SCOPE_BIT.workspace;
|
|
97
|
+
const APPEARANCE_DEFINITIONS = [
|
|
98
|
+
// Defaults to LIGHT rather than "system" here — a host whose surrounding admin UI is
|
|
99
|
+
// light-only would put a dark panel inside a light app for every operator on a dark-mode
|
|
100
|
+
// machine if this defaulted to following the OS. "system" stays selectable; a host can
|
|
101
|
+
// override this default at its own registration call site if its UI has a real dark variant.
|
|
102
|
+
{
|
|
103
|
+
key: "theme",
|
|
104
|
+
schema: { type: "enum", values: ["system", "light", "dark"] },
|
|
105
|
+
defaultValue: "light",
|
|
106
|
+
scopes: PER_OPERATOR_SCOPES,
|
|
107
|
+
},
|
|
108
|
+
// Free-form string rather than an enum over a swatch palette: a host's UI may ship a color
|
|
109
|
+
// picker, so any `#rrggbb` is reachable, and its own accent-color normalizer already rejects
|
|
110
|
+
// malformed values at render time. Pinning an enum here would reject a legitimate custom color
|
|
111
|
+
// at the ledger.
|
|
112
|
+
{
|
|
113
|
+
key: "accentColor",
|
|
114
|
+
schema: { type: "string" },
|
|
115
|
+
defaultValue: "#2563eb",
|
|
116
|
+
scopes: PER_OPERATOR_SCOPES,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
const LANGUAGE_DEFINITIONS = [
|
|
120
|
+
// A BCP-47-ish code, not an enum. The catalog of real locales is a host's own i18n concern, not
|
|
121
|
+
// this library's; the tab takes a host-supplied `LocaleOption[]` and an unknown stored code
|
|
122
|
+
// simply selects nothing rather than failing a ledger write.
|
|
123
|
+
{ key: "locale", schema: { type: "string" }, defaultValue: "en", scopes: PER_OPERATOR_SCOPES },
|
|
124
|
+
];
|
|
125
|
+
/**
|
|
126
|
+
* Idempotently registers the Instructions, Notifications, and Privacy
|
|
127
|
+
* definitions. Safe to call on every boot.
|
|
128
|
+
*
|
|
129
|
+
* @complexity O(n) in the total number of definitions (9), each a
|
|
130
|
+
* skip-if-registered check plus at most one `registerDefinitions` call.
|
|
131
|
+
*/
|
|
132
|
+
export async function ensureSettingsUiTabDefinitions(deps, input) {
|
|
133
|
+
const namespaces = [
|
|
134
|
+
{ namespace: INSTRUCTIONS_NAMESPACE, definitions: INSTRUCTIONS_DEFINITIONS },
|
|
135
|
+
{ namespace: NOTIFICATIONS_NAMESPACE, definitions: NOTIFICATIONS_DEFINITIONS },
|
|
136
|
+
{ namespace: PRIVACY_NAMESPACE, definitions: PRIVACY_DEFINITIONS },
|
|
137
|
+
{ namespace: APPEARANCE_NAMESPACE, definitions: APPEARANCE_DEFINITIONS },
|
|
138
|
+
{ namespace: LANGUAGE_NAMESPACE, definitions: LANGUAGE_DEFINITIONS },
|
|
139
|
+
];
|
|
140
|
+
for (const { namespace, definitions } of namespaces) {
|
|
141
|
+
await ensureSettingDefinitions(deps, {
|
|
142
|
+
namespace,
|
|
143
|
+
definitions,
|
|
144
|
+
systemPrincipalId: input.systemPrincipalId,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=ui-tab-definitions.js.map
|