@sema-agent/server 7.99.0 → 7.100.0
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/MIGRATION.md +13 -0
- package/USAGE.md +75 -52
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +74 -1
- package/dist/approval-card.js +41 -8
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +39 -81
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +2 -2
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +12 -13
- package/dist/config-center/apply-effective.d.ts +34 -39
- package/dist/config-center/apply-effective.js +100 -64
- package/dist/config-center/apply-ledger.d.ts +35 -16
- package/dist/config-center/apply-ledger.js +25 -18
- package/dist/config-center/facade.d.ts +11 -5
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/types.d.ts +3 -3
- package/dist/config-types.d.ts +22 -7
- package/dist/cross-session-settings.js +1 -1
- package/dist/host-lsp-manager.d.ts +29 -0
- package/dist/host-lsp-manager.js +14 -0
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +8 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/memory-compliance.js +5 -0
- package/dist/http/routes/memory-origin.js +5 -0
- package/dist/http/routes/sessions.js +6 -0
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-preflight.d.ts +46 -0
- package/dist/memory-layer-preflight.js +121 -0
- package/dist/memory-operator-faces.js +17 -1
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +8 -4
- package/dist/observability/fail-open.js +8 -4
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -0
- package/dist/observability/secret-env-scrub.js +1 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/web-search.d.ts +42 -22
- package/dist/plugins/web-search.js +141 -49
- package/dist/run-local.js +6 -5
- package/dist/runs.js +6 -5
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/server-secret-env.d.ts +4 -2
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +107 -19
- package/dist/tool-approval.js +64 -15
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +3 -3
|
@@ -5,9 +5,11 @@ import { buildModelCompat, readModelCompat } from "../model-compat.js";
|
|
|
5
5
|
import { validateCommandRules } from "../runtime-governance.js";
|
|
6
6
|
import { projectCollabToWorkflows, registerCollabWorkflows } from "../capabilities/collab-workflows.js";
|
|
7
7
|
import { stageLimits } from "./stage-limits.js";
|
|
8
|
-
import { BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
|
|
8
|
+
import { APPROVAL_GROUP_KEYS, BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
|
|
9
9
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
10
10
|
import { crossInvariant } from "../config-invariants.js";
|
|
11
|
+
import { gateIntentUnserviceableReason } from "../approval.js";
|
|
12
|
+
import { refusedGroups } from "./apply-ledger.js";
|
|
11
13
|
const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
|
|
12
14
|
function envDerivedModel(config) {
|
|
13
15
|
const m = config.envModel;
|
|
@@ -93,7 +95,7 @@ function stageAtModelAllowlist(raw, models, logger) {
|
|
|
93
95
|
}
|
|
94
96
|
return names;
|
|
95
97
|
}
|
|
96
|
-
export function applyEffective(config, eff, logger, opts
|
|
98
|
+
export function applyEffective(config, eff, logger, opts) {
|
|
97
99
|
const staged = stageEffective(config, eff, logger, opts);
|
|
98
100
|
const gen = opts.generation ?? staged.version;
|
|
99
101
|
const live = appliedGeneration.get(config);
|
|
@@ -118,25 +120,30 @@ export function applyEffective(config, eff, logger, opts = {}) {
|
|
|
118
120
|
commitStaged(config, staged);
|
|
119
121
|
if (ordered)
|
|
120
122
|
appliedGeneration.set(config, gen);
|
|
121
|
-
opts.report
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
if (opts.report) {
|
|
124
|
+
const report = {
|
|
125
|
+
version: staged.version,
|
|
126
|
+
...(opts.generation !== undefined ? { ordinal: opts.generation } : {}),
|
|
127
|
+
groups: {
|
|
128
|
+
limitsHttp: {
|
|
129
|
+
applied: staged.limits.changed,
|
|
130
|
+
...(staged.limits.deferred.length > 0 ? { deferred: staged.limits.deferred } : {}),
|
|
131
|
+
...(staged.limits.rejected ? { rejected: staged.limits.rejected } : {}),
|
|
132
|
+
},
|
|
133
|
+
approvalGate: groupReportOf(staged.approvalGate),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
opts.report({ ...report, fullyApplied: refusedGroups(report).length === 0 });
|
|
137
|
+
}
|
|
131
138
|
if (staged.limits.changed.length > 0)
|
|
132
139
|
logger?.info("sema_registry_limits", { version: staged.version, keys: staged.limits.changed });
|
|
133
140
|
if (staged.modelPlane)
|
|
134
141
|
logger?.info("sema_registry_models", staged.modelPlane.infoLine);
|
|
135
|
-
if (staged.
|
|
136
|
-
logger?.info("sema_registry_runtime", staged.
|
|
142
|
+
if (staged.approvalGate.next !== undefined)
|
|
143
|
+
logger?.info("sema_registry_runtime", { version: staged.version, approvalRequire: staged.approvalGate.next });
|
|
137
144
|
if (Object.keys(staged.hot.infoLine).length > 0)
|
|
138
145
|
logger?.info("sema_registry_runtime_hot", staged.hot.infoLine);
|
|
139
|
-
for (const n of staged.collabPlane
|
|
146
|
+
for (const n of staged.collabPlane.notices) {
|
|
140
147
|
if (n.level === "warn")
|
|
141
148
|
logger?.warn(n.msg, n.fields);
|
|
142
149
|
else
|
|
@@ -158,10 +165,8 @@ function commitStaged(config, staged) {
|
|
|
158
165
|
mutateInPlace(config.tiers, mp.activeTiers);
|
|
159
166
|
config.atModelAllowlist = mp.atModelAllowlist;
|
|
160
167
|
}
|
|
161
|
-
if (staged.
|
|
162
|
-
|
|
163
|
-
config[k] = v;
|
|
164
|
-
}
|
|
168
|
+
if (staged.approvalGate.next !== undefined)
|
|
169
|
+
config.approvalRequire = staged.approvalGate.next;
|
|
165
170
|
for (const [k, v] of staged.limits.assignments)
|
|
166
171
|
config[k] = v;
|
|
167
172
|
for (const k of staged.limits.deletions)
|
|
@@ -170,12 +175,11 @@ function commitStaged(config, staged) {
|
|
|
170
175
|
config.autonomy = staged.hot.autonomyNext;
|
|
171
176
|
if (staged.hot.setCommandPolicy)
|
|
172
177
|
config.commandPolicy = staged.hot.commandPolicyNext;
|
|
173
|
-
|
|
174
|
-
registerCollabWorkflows(staged.collabPlane.workflows);
|
|
178
|
+
registerCollabWorkflows(staged.collabPlane.workflows);
|
|
175
179
|
mutateInPlace(config.projects, staged.projects);
|
|
176
180
|
}
|
|
177
181
|
function stageEffective(config, eff, logger, opts) {
|
|
178
|
-
if (
|
|
182
|
+
if (opts.leg === "boot" && !eff.version) {
|
|
179
183
|
logger?.warn("sema_registry_unpublished", {
|
|
180
184
|
version: eff.version,
|
|
181
185
|
note: "empty/unpublished effective config — using env + built-in fallback; publish a config or unset CONFIG_PUBLISH_MODE",
|
|
@@ -183,7 +187,7 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
183
187
|
}
|
|
184
188
|
const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
|
|
185
189
|
let modelPlane;
|
|
186
|
-
if (enabled.length > 0
|
|
190
|
+
if (enabled.length > 0) {
|
|
187
191
|
const models = {};
|
|
188
192
|
const modelApiKeyEnv = {};
|
|
189
193
|
const modelApiKeys = {};
|
|
@@ -282,21 +286,18 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
282
286
|
infoLine: { count: enabled.length, default: models.default.id, defaultSource: picked.source, roles: Object.keys(roles), tiers: Object.keys(activeTiers), atModelAllowlist: atModelAllowlist.length, sealedKeys: Object.values(modelApiKeys).filter((v) => typeof v === "string").length, sealedPoisoned: Object.values(modelApiKeys).filter((v) => typeof v !== "string").length },
|
|
283
287
|
};
|
|
284
288
|
}
|
|
285
|
-
const
|
|
289
|
+
const approvalGate = stageApprovalGate(config, eff.governance, opts, logger);
|
|
286
290
|
const limits = stageLimits(config, { version: typeof eff.version === "number" ? eff.version : 0, ...("limits" in eff ? { limits: eff.limits } : {}), legacyGates: legacyGateValues(eff.runtime) }, logger);
|
|
287
291
|
const hot = stageRuntimeHot(config, eff.governance, logger);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
notices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
|
|
298
|
-
collabPlane = { workflows: projected.workflows, notices };
|
|
299
|
-
}
|
|
292
|
+
const projected = projectCollabToWorkflows(eff.collab?.templates);
|
|
293
|
+
const collabNotices = [];
|
|
294
|
+
if (Object.keys(projected.workflows).length > 0)
|
|
295
|
+
collabNotices.push({ level: "info", msg: "sema_registry_collab_workflows", fields: { workflows: Object.keys(projected.workflows) } });
|
|
296
|
+
if (projected.skipped.length > 0)
|
|
297
|
+
collabNotices.push({ level: "warn", msg: "sema_registry_collab_skipped", fields: { skipped: projected.skipped } });
|
|
298
|
+
if (projected.notes.length > 0)
|
|
299
|
+
collabNotices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
|
|
300
|
+
const collabPlane = { workflows: projected.workflows, notices: collabNotices };
|
|
300
301
|
const projDomain = eff.projects;
|
|
301
302
|
const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain) &&
|
|
302
303
|
"projects" in projDomain && typeof projDomain.projects === "object"
|
|
@@ -316,15 +317,14 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
316
317
|
return {
|
|
317
318
|
version: typeof eff.version === "number" && Number.isFinite(eff.version) ? eff.version : 0,
|
|
318
319
|
...(modelPlane !== undefined ? { modelPlane } : {}),
|
|
319
|
-
|
|
320
|
+
approvalGate,
|
|
320
321
|
limits,
|
|
321
322
|
hot,
|
|
322
|
-
|
|
323
|
+
collabPlane,
|
|
323
324
|
projects,
|
|
324
325
|
};
|
|
325
326
|
}
|
|
326
327
|
export const RUNTIME_GATE_KEYS = ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
|
|
327
|
-
export const BOOT_GATE_KEYS = ["approvalRequire"];
|
|
328
328
|
export function centerManagedConfigKeys(eff) {
|
|
329
329
|
const out = new Set();
|
|
330
330
|
if (!eff)
|
|
@@ -372,40 +372,76 @@ export function runtimeGatePresent(rt, key) {
|
|
|
372
372
|
return typeof v === "number" && v > 0;
|
|
373
373
|
return typeof v === "number";
|
|
374
374
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
};
|
|
380
|
-
const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
|
|
381
|
-
const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
|
|
375
|
+
const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
|
|
376
|
+
const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
|
|
377
|
+
const KEPT_WOULD_NEVER_PARK = "the prior approvalRequire is unchanged — this deployment has nowhere to park a gated call, so applying the list would leave it silently unenforced";
|
|
378
|
+
function stageApprovalRequire(raw) {
|
|
382
379
|
if (!Array.isArray(raw)) {
|
|
383
|
-
|
|
384
|
-
`Refusing the
|
|
380
|
+
const detail = `published governance.approvalRequire must be an ARRAY of whole tool names — got ${raw === null ? "null" : typeof raw}. ` +
|
|
381
|
+
`Refusing the group instead of coercing it: an empty list means "no approval gate", so coercing a malformed value would SILENTLY remove the gate.`;
|
|
382
|
+
return { ok: false, detail, reason: detail, names: [], kept: KEPT_WOULD_REMOVE_GATE };
|
|
385
383
|
}
|
|
386
384
|
const nonString = raw
|
|
387
385
|
.map((n, i) => (typeof n === "string" ? undefined : { i, t: n === null ? "null" : typeof n }))
|
|
388
386
|
.filter((e) => e !== undefined);
|
|
389
387
|
if (nonString.length > 0) {
|
|
390
|
-
|
|
388
|
+
const detail = `published governance.approvalRequire contains ${nonString.length} non-string entr${nonString.length === 1 ? "y" : "ies"} ` +
|
|
391
389
|
`(${nonString.map((e) => `[${e.i}]:${e.t}`).join(", ")}) — every entry must be a whole tool name string. ` +
|
|
392
|
-
`Refusing the
|
|
390
|
+
`Refusing the group instead of dropping them: dropping every entry would SILENTLY remove the approval gate.`;
|
|
391
|
+
return { ok: false, detail, reason: detail, names: nonString.map((e) => `[${e.i}]`), kept: KEPT_WOULD_REMOVE_GATE };
|
|
393
392
|
}
|
|
394
393
|
const names = raw.filter((n) => typeof n === "string").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
395
394
|
const bad = findUnmatchableToolNames(names, { consumer: "tool-name-table", behavior: "deny" });
|
|
396
395
|
if (bad.length > 0) {
|
|
397
|
-
return
|
|
396
|
+
return {
|
|
397
|
+
ok: false,
|
|
398
|
+
detail: formatUnmatchableToolNames("published governance.approvalRequire", bad),
|
|
399
|
+
reason: `published governance.approvalRequire has ${bad.length} entr${bad.length === 1 ? "y" : "ies"} that match no live tool (retired name / incomplete MCP name / CC-form rule) — the offending entries are named in the sema_registry_approval_require_invalid log line`,
|
|
400
|
+
names: bad.map((b) => b.name),
|
|
401
|
+
kept: KEPT_WOULD_MATCH_NOTHING,
|
|
402
|
+
};
|
|
398
403
|
}
|
|
399
|
-
return { value: names };
|
|
404
|
+
return { ok: true, value: names };
|
|
400
405
|
}
|
|
401
|
-
function
|
|
406
|
+
function unserviceableOnLeg(leg, candidate) {
|
|
407
|
+
switch (leg.leg) {
|
|
408
|
+
case "boot":
|
|
409
|
+
return undefined;
|
|
410
|
+
case "hot":
|
|
411
|
+
return gateIntentUnserviceableReason(candidate, leg.checkpointStorePresent);
|
|
412
|
+
default: {
|
|
413
|
+
const unknownLeg = leg;
|
|
414
|
+
throw new Error(`applyEffective: unknown apply leg ${JSON.stringify(unknownLeg)}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function groupReportOf(g) {
|
|
419
|
+
return { applied: g.next !== undefined ? [...APPROVAL_GROUP_KEYS] : [], ...(g.rejected ? { rejected: g.rejected } : {}) };
|
|
420
|
+
}
|
|
421
|
+
const approvalEnvBaseline = new WeakMap();
|
|
422
|
+
function stageApprovalGate(config, gov, leg, logger) {
|
|
423
|
+
if (!approvalEnvBaseline.has(config))
|
|
424
|
+
approvalEnvBaseline.set(config, Object.freeze([...config.approvalRequire]));
|
|
425
|
+
const refuse = (r) => {
|
|
426
|
+
logger?.warn("sema_registry_approval_require_invalid", { keys: [...APPROVAL_GROUP_KEYS], detail: r.detail, names: [...r.names], kept: r.kept });
|
|
427
|
+
return { rejected: { keys: [...APPROVAL_GROUP_KEYS], reason: r.reason } };
|
|
428
|
+
};
|
|
402
429
|
const raw = gov?.approvalRequire;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
430
|
+
let next;
|
|
431
|
+
if (raw === undefined) {
|
|
432
|
+
next = [...approvalEnvBaseline.get(config)];
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const verdict = stageApprovalRequire(raw);
|
|
436
|
+
if (!verdict.ok)
|
|
437
|
+
return refuse(verdict);
|
|
438
|
+
next = verdict.value;
|
|
439
|
+
}
|
|
440
|
+
const unserviceable = unserviceableOnLeg(leg, { ...config, approvalRequire: next });
|
|
441
|
+
if (unserviceable !== undefined)
|
|
442
|
+
return refuse({ detail: unserviceable, reason: unserviceable, names: next, kept: KEPT_WOULD_NEVER_PARK });
|
|
443
|
+
const same = next.length === config.approvalRequire.length && next.every((n, i) => n === config.approvalRequire[i]);
|
|
444
|
+
return same ? {} : { next };
|
|
409
445
|
}
|
|
410
446
|
function legacyGateValues(rt) {
|
|
411
447
|
const out = {};
|
|
@@ -416,16 +452,16 @@ function legacyGateValues(rt) {
|
|
|
416
452
|
out[k] = rt[k];
|
|
417
453
|
return out;
|
|
418
454
|
}
|
|
419
|
-
export function applyRuntimeGates(config, eff, logger) {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
config
|
|
455
|
+
export function applyRuntimeGates(config, eff, leg, logger) {
|
|
456
|
+
const approval = stageApprovalGate(config, eff?.governance, leg, logger);
|
|
457
|
+
if (approval.next !== undefined)
|
|
458
|
+
config.approvalRequire = approval.next;
|
|
423
459
|
const limits = stageLimits(config, { version: 0, legacyGates: legacyGateValues(eff?.runtime) }, logger);
|
|
424
460
|
for (const [k, v] of limits.assignments)
|
|
425
461
|
config[k] = v;
|
|
426
462
|
for (const k of limits.deletions)
|
|
427
463
|
delete config[k];
|
|
428
|
-
const info = { ...
|
|
464
|
+
const info = { ...(approval.next !== undefined ? { approvalRequire: approval.next } : {}), ...Object.fromEntries(limits.assignments) };
|
|
429
465
|
if (Object.keys(info).length > 0)
|
|
430
466
|
logger?.info("sema_registry_runtime", info);
|
|
431
467
|
}
|
|
@@ -20,7 +20,16 @@
|
|
|
20
20
|
* 🔴 原子承诺的**域**是单进程(§2):本账记的是「本副本的收敛状态」,不是 fleet 的。fleet 级判据 = 各副本
|
|
21
21
|
* /health 的 applied == target,拉齐工具归运维面。
|
|
22
22
|
*/
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* 世代账按组记账的**组闭集**(读面 `groups` 的键集;加组 = 这里加词,报告形 `Record<组, …>` 随之编译红)。
|
|
25
|
+
* · `limitsHttp` —— [ref] 批1 限额组(属主 `stage-limits.ts`);
|
|
26
|
+
* · `approvalGate` —— S-668 波 2 审批组(`governance.approvalRequire`;属主 `apply-effective.ts` 的
|
|
27
|
+
* `stageApprovalGate`:坏形 / 工具名匹配不到 / ④ 门意图不可服务 ⇒ 整组拒)。
|
|
28
|
+
* 两组同姿势、各用各的判据表(审批组**不**复用限额组的跨键不变量表 —— 那张表的候选视图只叠限额键)。
|
|
29
|
+
*/
|
|
30
|
+
export declare const CONFIG_APPLY_GROUPS: readonly ["limitsHttp", "approvalGate"];
|
|
31
|
+
export type ConfigApplyGroup = (typeof CONFIG_APPLY_GROUPS)[number];
|
|
32
|
+
/** 一组的收敛状态(读面形对所有组相同)。 */
|
|
24
33
|
export interface ConfigApplyGroupState {
|
|
25
34
|
/** 本组最后一次**真的落地**的源版本(身份标)。 */
|
|
26
35
|
readonly appliedVersion: number;
|
|
@@ -48,27 +57,37 @@ export interface ConfigApplyLedgerView {
|
|
|
48
57
|
readonly configAppliedOrdinal: number;
|
|
49
58
|
/** target≠applied 的**持续**时长(ms);收敛时键缺席(健康面不制造噪音)。 */
|
|
50
59
|
readonly configApplyStaleMs?: number;
|
|
51
|
-
readonly groups: Readonly<Record<
|
|
60
|
+
readonly groups: Readonly<Partial<Record<ConfigApplyGroup, ConfigApplyGroupState>>>;
|
|
61
|
+
}
|
|
62
|
+
/** 一组在一次 apply 里的报告(所有组同形)。 */
|
|
63
|
+
export interface ConfigApplyGroupReport {
|
|
64
|
+
/** 本世代真正换了值的键。 */
|
|
65
|
+
readonly applied: readonly string[];
|
|
66
|
+
/** 值合法但**不热**的键(逐键 defer:变更只能重启生效)。 */
|
|
67
|
+
readonly deferred?: ReadonlyArray<{
|
|
68
|
+
readonly key: string;
|
|
69
|
+
readonly reason: string;
|
|
70
|
+
}>;
|
|
71
|
+
/** 整组被拒时的成因(消毒:键名 + 判据文案,绝不带值)。 */
|
|
72
|
+
readonly rejected?: {
|
|
73
|
+
readonly keys: readonly string[];
|
|
74
|
+
readonly reason: string;
|
|
75
|
+
readonly restartReason?: string;
|
|
76
|
+
};
|
|
52
77
|
}
|
|
53
|
-
/** applyEffective 的 post-commit 报告(见 apply-effective.ts `ApplyReport`)
|
|
78
|
+
/** applyEffective 的 post-commit 报告(见 apply-effective.ts `ApplyReport`)—— **每一组都报**。 */
|
|
54
79
|
export interface ConfigApplyReport {
|
|
55
80
|
readonly version: number;
|
|
56
81
|
/** [ref]:本候选的收货序数(applyEffective opts.generation 原样带回;缺席=version 兼容臂)。 */
|
|
57
82
|
readonly ordinal?: number;
|
|
58
|
-
readonly
|
|
59
|
-
readonly applied: readonly string[];
|
|
60
|
-
readonly deferred?: ReadonlyArray<{
|
|
61
|
-
readonly key: string;
|
|
62
|
-
readonly reason: string;
|
|
63
|
-
}>;
|
|
64
|
-
readonly rejected?: {
|
|
65
|
-
readonly keys: readonly string[];
|
|
66
|
-
readonly reason: string;
|
|
67
|
-
readonly restartReason?: string;
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
readonly fullyApplied?: boolean;
|
|
83
|
+
readonly groups: Readonly<Record<ConfigApplyGroup, ConfigApplyGroupReport>>;
|
|
71
84
|
}
|
|
85
|
+
/** 本报告里被整组拒的那些组(按组闭集序)。空 ⇒ 候选完整应用。两条 commit 腿的「部分应用」warn 与世代账共用。 */
|
|
86
|
+
export declare function refusedGroups(report: ConfigApplyReport): Array<{
|
|
87
|
+
readonly group: ConfigApplyGroup;
|
|
88
|
+
readonly keys: readonly string[];
|
|
89
|
+
readonly reason: string;
|
|
90
|
+
}>;
|
|
72
91
|
export interface ConfigApplyLedger {
|
|
73
92
|
/** 拉到一个候选(无论后续是否应用)。ordinal=收货序数,version=身份标。 */
|
|
74
93
|
seenTarget(ordinal: number, version: number): void;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export const CONFIG_APPLY_GROUPS = ["limitsHttp", "approvalGate"];
|
|
2
|
+
export function refusedGroups(report) {
|
|
3
|
+
return CONFIG_APPLY_GROUPS.flatMap((group) => {
|
|
4
|
+
const r = report.groups[group].rejected;
|
|
5
|
+
return r ? [{ group, keys: r.keys, reason: r.reason }] : [];
|
|
6
|
+
});
|
|
7
|
+
}
|
|
1
8
|
export function createConfigApplyLedger(now = () => Date.now()) {
|
|
2
9
|
let targetOrd = 0;
|
|
3
10
|
let appliedOrd = 0;
|
|
@@ -6,6 +13,21 @@ export function createConfigApplyLedger(now = () => Date.now()) {
|
|
|
6
13
|
const rejectedOrds = new Set();
|
|
7
14
|
let divergedSince;
|
|
8
15
|
const groups = {};
|
|
16
|
+
const nextGroupState = (prev, r, version) => {
|
|
17
|
+
if (r.rejected) {
|
|
18
|
+
return {
|
|
19
|
+
appliedVersion: prev?.appliedVersion ?? 0,
|
|
20
|
+
...(prev?.deferredKeys ? { deferredKeys: prev.deferredKeys } : {}),
|
|
21
|
+
lastRejected: { keys: [...r.rejected.keys], reason: r.rejected.reason, version, at: now() },
|
|
22
|
+
...(r.rejected.restartReason ? { deferredKeys: r.rejected.keys.map((key) => ({ key, reason: r.rejected.restartReason })) } : {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
appliedVersion: version,
|
|
27
|
+
...(r.deferred && r.deferred.length > 0 ? { deferredKeys: r.deferred.map((d) => ({ key: d.key, reason: d.reason })) } : {}),
|
|
28
|
+
...(prev?.lastRejected ? { lastRejected: prev.lastRejected } : {}),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
9
31
|
const reconcile = () => {
|
|
10
32
|
if (targetOrd === appliedOrd)
|
|
11
33
|
divergedSince = undefined;
|
|
@@ -32,28 +54,13 @@ export function createConfigApplyLedger(now = () => Date.now()) {
|
|
|
32
54
|
reconcile();
|
|
33
55
|
},
|
|
34
56
|
recordApply(report) {
|
|
35
|
-
const prev = groups.limitsHttp;
|
|
36
57
|
const ord = report.ordinal ?? report.version;
|
|
37
|
-
if (report.
|
|
58
|
+
if (refusedGroups(report).length > 0)
|
|
38
59
|
rejectedOrds.add(ord);
|
|
39
60
|
else
|
|
40
61
|
rejectedOrds.delete(ord);
|
|
41
|
-
|
|
42
|
-
groups
|
|
43
|
-
appliedVersion: prev?.appliedVersion ?? 0,
|
|
44
|
-
...(prev?.deferredKeys ? { deferredKeys: prev.deferredKeys } : {}),
|
|
45
|
-
lastRejected: { keys: [...report.limits.rejected.keys], reason: report.limits.rejected.reason, version: report.version, at: now() },
|
|
46
|
-
...(report.limits.rejected.restartReason
|
|
47
|
-
? { deferredKeys: report.limits.rejected.keys.map((key) => ({ key, reason: report.limits.rejected.restartReason })) }
|
|
48
|
-
: {}),
|
|
49
|
-
};
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
groups.limitsHttp = {
|
|
53
|
-
appliedVersion: report.version,
|
|
54
|
-
...(report.limits.deferred && report.limits.deferred.length > 0 ? { deferredKeys: report.limits.deferred.map((d) => ({ key: d.key, reason: d.reason })) } : {}),
|
|
55
|
-
...(prev?.lastRejected ? { lastRejected: prev.lastRejected } : {}),
|
|
56
|
-
};
|
|
62
|
+
for (const g of CONFIG_APPLY_GROUPS)
|
|
63
|
+
groups[g] = nextGroupState(groups[g], report.groups[g], report.version);
|
|
57
64
|
},
|
|
58
65
|
view() {
|
|
59
66
|
const stale = divergedSince !== undefined ? now() - divergedSince : undefined;
|
|
@@ -25,14 +25,20 @@
|
|
|
25
25
|
* reference. Both brains re-resolve `model.baseUrl || config.baseUrl` inside `buildRequest()` on every
|
|
26
26
|
* call (core 2.1.0 `brain/openai.js` + `brain/anthropic.js`), so a moved gateway takes effect on the
|
|
27
27
|
* next task with no restart.
|
|
28
|
-
* - the
|
|
29
|
-
* at construction,
|
|
30
|
-
*
|
|
28
|
+
* - tiers (and the whole plane under an active tier table) — **HOT** as well: core expands a PRIVATE catalog
|
|
29
|
+
* copy at Runner construction, and the refresh loop swaps every changed plane into all boot Runners before
|
|
30
|
+
* commit (`Runner.swapDeps`, core 7.10.0; production always wires it). The old "tier-frozen ⇒ defer to restart"
|
|
31
|
+
* arm and its `models-tiers` restart slice retired in S-668 波 0 — `/v1/models` and tier-word routing move on
|
|
32
|
+
* the same tick.
|
|
33
|
+
* - **`degrade-route`** — one of the two genuinely boot-frozen catalog consumers left (the other is the
|
|
34
|
+
* memory-consolidation driver seat, last bullet): reactive degrade
|
|
31
35
|
* (`MODEL_DEGRADE_REACTIVE`) bakes the target Model + its gateway + its key into the brain composition
|
|
32
36
|
* at boot. It cannot be hot-applied without rebuilding the brain, so it is registered on the existing
|
|
33
37
|
* restartRequired /health contract instead (see `restart-signal.ts`). Fail-loud beats serving a stale
|
|
34
38
|
* gateway silently on the rate-limit path.
|
|
35
|
-
* - skills/mcp/scenarios
|
|
39
|
+
* - skills(+plugins)/mcp/a2a/scenarios — restart-to-apply by construction (baked into the boot
|
|
40
|
+
* wiring); so is the memory-consolidation driver's model seat (`memory-consolidation` slice, S-668 波 0).
|
|
41
|
+
* (`governance.approvalRequire` left this list in S-668 波 2 — its own staged group, hot on both legs.)
|
|
36
42
|
*
|
|
37
43
|
* ([ref] A13, internal-lossless) This module is now a FACADE: the implementation lives
|
|
38
44
|
* alongside it in `src/config-center/` split by responsibility group (HTTP client / EffectiveConfig
|
|
@@ -43,7 +49,7 @@
|
|
|
43
49
|
*/
|
|
44
50
|
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
45
51
|
export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, } from "./apply-effective.js";
|
|
46
|
-
export { restartReasons,
|
|
52
|
+
export { restartReasons, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type RestartSignal, } from "./restart-signal.js";
|
|
47
53
|
export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
48
54
|
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
|
|
49
55
|
export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterModel, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
2
2
|
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, } from "./apply-effective.js";
|
|
3
|
-
export { restartReasons,
|
|
3
|
+
export { restartReasons, modelPlaneChanged, } from "./restart-signal.js";
|
|
4
4
|
export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
5
5
|
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
6
6
|
//# sourceMappingURL=facade.js.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 键 → 组 → 换代手法 → **参数化用例 ID**(不是文件名——文件名粒度挡不住「文件在、用例没了」)。
|
|
7
7
|
*
|
|
8
8
|
* 机器门在 test/config-hot/batch1-hot-keys-registry.test.ts:登记为热却没有三断言用例 = 红;
|
|
9
|
-
* 表的键集与 stage 真正消费的键集(`BATCH1_LIMIT_KEYS`)不等 = 红。
|
|
9
|
+
* 表的键集与 stage 真正消费的键集(`BATCH1_LIMIT_KEYS` ∪ `APPROVAL_GROUP_KEYS`)不等 = 红。
|
|
10
10
|
*
|
|
11
11
|
* ⚠️ 本表**只登记已落地的批**。批2-4 的键在各自批次里追加(设计稿 §6),不提前写进来 —— 一个「登记为热
|
|
12
12
|
* 但没接线」的条目正是本表要消灭的那种谎。
|
|
@@ -27,8 +27,8 @@ export interface HotKeyEntry {
|
|
|
27
27
|
readonly key: keyof ServiceConfigFlat;
|
|
28
28
|
readonly group: keyof ServiceConfigGroups;
|
|
29
29
|
readonly form: HotKeyForm;
|
|
30
|
-
/**
|
|
31
|
-
readonly batch: 1;
|
|
30
|
+
/** 落地批次:1 = [ref] 批1(limitsHttp 组);2 = S-668 波 2(审批组 —— [ref] §6 原排的「批2」)。 */
|
|
31
|
+
readonly batch: 1 | 2;
|
|
32
32
|
readonly cases: HotKeyCases;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
@@ -57,6 +57,13 @@ export declare const DEFERRED_LIMIT_KEYS: ReadonlyArray<{
|
|
|
57
57
|
readonly restartReason: string;
|
|
58
58
|
readonly why: string;
|
|
59
59
|
}>;
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* S-668 波 2([ref] §3)—— **审批组**真正接线的键:`apply-effective.ts` 的 `stageApprovalGate` 消费、报告(世代账
|
|
62
|
+
* `groups.approvalGate`)与拒绝(拒因的键名)都取这一张表。换代手法 A′:消费点本就逐请求现读活 config(审批基线 /
|
|
63
|
+
* SendUserFile 子代闸 / 会话与记忆读面 / parked 赎回),leader 腿与单用户基线改成现读之后,热只差 refresh 腿的写口。
|
|
64
|
+
*/
|
|
65
|
+
export declare const APPROVAL_GROUP_KEYS: readonly ["approvalRequire"];
|
|
66
|
+
export type ApprovalGroupKey = (typeof APPROVAL_GROUP_KEYS)[number];
|
|
67
|
+
/** 热键表 = 批1 消费键集**减去**显式 deferred 的那些 + 审批组(表只登记真的热的键)。 */
|
|
61
68
|
export declare const HOT_KEYS_REGISTRY: readonly HotKeyEntry[];
|
|
62
69
|
//# sourceMappingURL=hot-keys-registry.d.ts.map
|
|
@@ -29,11 +29,15 @@ export const DEFERRED_LIMIT_KEYS = [
|
|
|
29
29
|
},
|
|
30
30
|
];
|
|
31
31
|
const DEFERRED_KEYS = new Set(DEFERRED_LIMIT_KEYS.map((d) => d.key));
|
|
32
|
-
export const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
export const APPROVAL_GROUP_KEYS = ["approvalRequire"];
|
|
33
|
+
export const HOT_KEYS_REGISTRY = [
|
|
34
|
+
...BATCH1_LIMIT_KEYS.filter((key) => !DEFERRED_KEYS.has(key)).map((key) => ({
|
|
35
|
+
key,
|
|
36
|
+
group: "limitsHttp",
|
|
37
|
+
form: (B_SETTER_KEYS.includes(key) ? "B-setter" : "A-prime"),
|
|
38
|
+
batch: 1,
|
|
39
|
+
cases: cases(key),
|
|
40
|
+
})),
|
|
41
|
+
...APPROVAL_GROUP_KEYS.map((key) => ({ key, group: "approval", form: "A-prime", batch: 2, cases: cases(key) })),
|
|
42
|
+
];
|
|
39
43
|
//# sourceMappingURL=hot-keys-registry.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EffectiveConfig } from "./types.js";
|
|
2
|
-
export type RestartSlice = "skills" | "mcp" | "a2a" | "scenarios" | "
|
|
2
|
+
export type RestartSlice = "skills" | "mcp" | "a2a" | "scenarios" | "degrade-route" | "read-face" | "memory-consolidation";
|
|
3
3
|
/** What the CALLER must contribute about boot-baked catalog consumers that live OUTSIDE the Runner — the
|
|
4
4
|
* slices cannot see them, because they are configured from env (`ServiceConfig`), not from the center's
|
|
5
5
|
* EffectiveConfig. Absent/empty = that lane is off ⇒ its slice is inert (null on both sides). */
|
|
@@ -18,6 +18,18 @@ export interface RestartSliceCtx {
|
|
|
18
18
|
* 新增/改址/域整体出现或消失仍签(新 mount / 改址只能 restart 才生效)。缺席/false = 旧语义照签
|
|
19
19
|
* (fail-closed:没接席的部署里删除向没人承接,restart 是它唯一的生效路径)。 */
|
|
20
20
|
mcpRevocationLive?: boolean;
|
|
21
|
+
/** S-668 波 0(过渡;**波 1 转热后删**):记忆整理驱动席的两侧读数 —— `baked` = boot 期 core 解析器选中的
|
|
22
|
+
* 那只 Model 的指纹,`current` = 同一只解析器对本拍活配置再解一次的指纹(读法见
|
|
23
|
+
* boot/memory-consolidation.ts `createConsolidationSeatWatch`)。缺席 = 阀门关着(`MEMORY_CONSOLIDATION_DRIVER`
|
|
24
|
+
* 不是 on,或记忆引擎没接线)⇒ 没有 boot 烤死的席 ⇒ `memory-consolidation` 片恒不签。 */
|
|
25
|
+
consolidationSeat?: {
|
|
26
|
+
readonly baked: unknown;
|
|
27
|
+
readonly current: unknown;
|
|
28
|
+
};
|
|
29
|
+
/** S-668 波 0(codex r1 [medium]):插件 host 白名单(env `PLUGINS_ALLOW_HOSTS`,boot 常量)—— 白名单外的声明
|
|
30
|
+
* 装载器整条跳过,它们的变化不是重启理由。生产 `restartCtx` 恒传 `config.pluginsAllowHosts`;缺席(单元调用)
|
|
31
|
+
* = 装载器的出厂名单 `DEFAULT_PLUGIN_ALLOW_HOSTS`。 */
|
|
32
|
+
pluginsAllowHosts?: readonly string[];
|
|
21
33
|
}
|
|
22
34
|
/** Structured restart signal an orchestrator consumes (GET /health → `restart`). Change-detected against the
|
|
23
35
|
* process's BOOT config, NOT presence — so it stays absent when nothing baked-at-boot changed (presence would
|
|
@@ -46,18 +58,13 @@ export declare function stableStringify(v: unknown): string;
|
|
|
46
58
|
* can auto rolling-restart on a non-empty result WITHOUT a restart loop, because the comparison is always
|
|
47
59
|
* against boot (a refresh that re-fires the same diff is idempotent, not a fresh trigger). */
|
|
48
60
|
export declare function restartReasons(boot: EffectiveConfig | undefined, current: EffectiveConfig, ctx?: RestartSliceCtx): RestartSlice[];
|
|
49
|
-
/**
|
|
50
|
-
* default) of `next` differs from the last
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
/** codex R12: TRUE when this candidate resolves an ACTIVE tier table. Used by main.ts's defer predicate — a
|
|
58
|
-
* tier-less-booted Runner never re-runs expandTiers, so hot-applying an ARRIVING tier table splits admission
|
|
59
|
-
* (tier words pass the expanded gate) from execution (core throws "Unknown model ref"). Hot-apply of the model
|
|
60
|
-
* plane is safe only when BOTH the constructed generation and the candidate are tier-less. */
|
|
61
|
-
export declare function planeHasActiveTiers(eff: EffectiveConfig): boolean;
|
|
61
|
+
/** The refresh loop's SWAP gate: TRUE when the MODEL PLANE (enabled models + active tier table + resolved
|
|
62
|
+
* default) of `next` differs from `prev`, the last plane handed to `swapRunnerDeps` — only then does the tick
|
|
63
|
+
* pass `swapPlane` to applyEffective, so a candidate that moved only hot fields (pricing values / quota weights /
|
|
64
|
+
* the `enabled:true` spelling, see catalogEntryFingerprint) or nothing on the plane swaps nothing. `prev ===
|
|
65
|
+
* undefined` (env-boot plane, no EffectiveConfig to compare) is conservatively "changed": the first center plane
|
|
66
|
+
* always swaps. Not gated on tier activity: a Runner's expanded catalog goes stale on ANY plane change,
|
|
67
|
+
* including a tiers-off flip, and the default entry rides the fingerprint because the Runner's copy carries its
|
|
68
|
+
* own `default` (codex R8; SAME resolver as applyEffective — resolveDefaultModelName, single source). */
|
|
62
69
|
export declare function modelPlaneChanged(prev: EffectiveConfig | undefined, next: EffectiveConfig): boolean;
|
|
63
70
|
//# sourceMappingURL=restart-signal.d.ts.map
|