@sema-agent/server 7.122.0 → 7.123.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 +77 -0
- package/README.md +5 -0
- package/README.zh-CN.md +3 -0
- package/USAGE.md +23 -5
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +52 -22
- package/dist/auto-mode-face.d.ts +12 -8
- package/dist/auto-mode-face.js +8 -7
- package/dist/boot/config-center.js +3 -0
- package/dist/config-catalog.js +1 -1
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +5 -3
- package/dist/config-center/http-client.js +3 -3
- package/dist/config-center/read-warnings.d.ts +56 -35
- package/dist/config-center/read-warnings.js +67 -19
- package/dist/config-center/types.d.ts +7 -8
- package/dist/config-lkg.d.ts +12 -9
- package/dist/config-lkg.js +38 -39
- package/dist/config-provider.d.ts +3 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +9 -1
- package/dist/config.d.ts +8 -8
- package/dist/config.js +69 -92
- package/dist/fleet/engine-signal.d.ts +54 -23
- package/dist/fleet/engine-signal.js +29 -5
- package/dist/fleet-client.d.ts +8 -6
- package/dist/fleet-client.js +4 -3
- package/dist/http/dispatch.js +5 -14
- package/dist/http/health-self-report.d.ts +3 -0
- package/dist/http/routes/capabilities.js +2 -1
- package/dist/http/routes/sessions.js +1 -4
- package/dist/http/stored-body-replay.d.ts +4 -3
- package/dist/http/stored-body-replay.js +2 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/leader.d.ts +7 -6
- package/dist/leader/leader.js +3 -1
- package/dist/leader/run-entry.d.ts +2 -2
- package/dist/leader/run-entry.js +3 -3
- package/dist/leader/wire.d.ts +2 -2
- package/dist/model-entry-refusal.d.ts +18 -9
- package/dist/model-entry-refusal.js +19 -14
- package/dist/model-route-endpoint.d.ts +21 -1
- package/dist/model-route-endpoint.js +9 -3
- package/dist/model-route-gap.d.ts +103 -0
- package/dist/model-route-gap.js +80 -0
- package/dist/namespaced-rule-name.d.ts +16 -30
- package/dist/namespaced-rule-name.js +2 -20
- package/dist/observability/fail-open.d.ts +0 -4
- package/dist/observability/fail-open.js +0 -4
- package/dist/plugins/one-time-migrations.js +27 -0
- package/dist/plugins/session-policy-store-sql.js +1 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
- package/dist/rule-entry-list.d.ts +3 -13
- package/dist/rule-entry-list.js +1 -12
- package/dist/run-local.js +4 -4
- package/dist/runtime-governance.d.ts +31 -8
- package/dist/runtime-governance.js +7 -2
- package/dist/session-policy-wire.d.ts +47 -49
- package/dist/session-policy-wire.js +41 -36
- package/dist/session-sync.d.ts +5 -1
- package/dist/session-sync.js +2 -1
- package/dist/spec-fields.d.ts +4 -4
- package/dist/spec-fields.js +1 -4
- package/dist/task-settings.d.ts +4 -5
- package/dist/task-settings.js +5 -8
- package/dist/unattended-permission-mode.d.ts +3 -3
- package/dist/unattended-permission-mode.js +2 -2
- package/package.json +3 -3
package/dist/config.js
CHANGED
|
@@ -28,23 +28,20 @@ import { lkgPathOverrideOf } from "./config-lkg.js";
|
|
|
28
28
|
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
29
29
|
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
30
30
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
31
|
-
import { allowLayerEntryCountIssue,
|
|
31
|
+
import { allowLayerEntryCountIssue, describeInertRuleEntries, inertRuleEntriesOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
|
|
32
32
|
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
33
33
|
import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
|
|
34
34
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
35
35
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
36
36
|
import { buildHttpUrlRefusal, httpUrlFlaws } from "./url-shape.js";
|
|
37
|
-
import {
|
|
37
|
+
import { csvOf, envLaneTable, judgeModelRouteGap, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
|
|
38
38
|
import { displayListEntry } from "./list-entry-display.js";
|
|
39
39
|
import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
|
|
40
40
|
import { allowlistedEnv } from "./server-secret-env.js";
|
|
41
41
|
import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
|
|
42
42
|
export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
43
43
|
function csv(name) {
|
|
44
|
-
return (process.env[name]
|
|
45
|
-
.split(",")
|
|
46
|
-
.map((s) => s.trim())
|
|
47
|
-
.filter(Boolean);
|
|
44
|
+
return csvOf(process.env[name]);
|
|
48
45
|
}
|
|
49
46
|
export function parseAutonomy(raw) {
|
|
50
47
|
const v = raw?.trim();
|
|
@@ -312,14 +309,6 @@ export function logConfigDiagnostics(logger) {
|
|
|
312
309
|
}
|
|
313
310
|
}
|
|
314
311
|
}
|
|
315
|
-
function firstSetEnv(...names) {
|
|
316
|
-
for (const n of names) {
|
|
317
|
-
const v = process.env[n];
|
|
318
|
-
if (v !== undefined && v !== "")
|
|
319
|
-
return v;
|
|
320
|
-
}
|
|
321
|
-
return undefined;
|
|
322
|
-
}
|
|
323
312
|
function clampEnvWithWarn(name, fallback, min, max = Number.POSITIVE_INFINITY) {
|
|
324
313
|
const n = numEnv(name, fallback);
|
|
325
314
|
const used = Math.min(max, Math.max(min, n));
|
|
@@ -952,44 +941,37 @@ export function isRosterGateArmed(configCenter) {
|
|
|
952
941
|
return configCenter !== undefined && !configCenter.dryRun && process.env.CONFIG_REQUIRE_ROSTER === "true";
|
|
953
942
|
}
|
|
954
943
|
function parseModelDomain() {
|
|
955
|
-
const
|
|
956
|
-
const
|
|
957
|
-
const
|
|
958
|
-
if (
|
|
959
|
-
const refusal = modelProviderRefusal(
|
|
944
|
+
const route = readModelRouteEnv(process.env);
|
|
945
|
+
const gatewayBaseUrl = route.gatewayBaseUrl;
|
|
946
|
+
const modelId = route.mainId;
|
|
947
|
+
if (route.providerSource === "explicit") {
|
|
948
|
+
const refusal = modelProviderRefusal(route.provider);
|
|
960
949
|
if (refusal !== undefined) {
|
|
961
950
|
throw new Error(refusal);
|
|
962
951
|
}
|
|
963
952
|
}
|
|
964
953
|
for (const [envKey, raw, knob] of [
|
|
965
954
|
["MODEL_GATEWAY_BASEURL", gatewayBaseUrl ?? "", "MODEL_API_KEY"],
|
|
966
|
-
...
|
|
967
|
-
["ANTHROPIC_BASE_URL",
|
|
955
|
+
...route.gatewayFallbackUrls.map((u, i) => [`MODEL_GATEWAY_FALLBACK_URLS[${i}]`, u, "MODEL_API_KEY"]),
|
|
956
|
+
["ANTHROPIC_BASE_URL", route.anthropicBaseUrl ?? "", "ANTHROPIC_API_KEY"],
|
|
968
957
|
]) {
|
|
969
958
|
const refusal = modelRouteUrlRefusal(envKey, raw, knob);
|
|
970
959
|
if (refusal !== undefined) {
|
|
971
960
|
throw new Error(refusal);
|
|
972
961
|
}
|
|
973
962
|
}
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
hint: "MODEL_PROVIDER is unset but an explicit ANTHROPIC base URL + credential are configured — inferred provider=anthropic (set MODEL_PROVIDER explicitly to override)",
|
|
987
|
-
},
|
|
988
|
-
});
|
|
989
|
-
return "anthropic";
|
|
990
|
-
}
|
|
991
|
-
return "gateway";
|
|
992
|
-
})();
|
|
963
|
+
const provider = route.provider;
|
|
964
|
+
if (route.providerSource === "inferred") {
|
|
965
|
+
CONFIG_NOTICES.push({
|
|
966
|
+
level: "warn",
|
|
967
|
+
event: "model_provider_inferred",
|
|
968
|
+
fields: {
|
|
969
|
+
provider: "anthropic",
|
|
970
|
+
baseUrl: buildDisplaySafeUrl(route.anthropicBaseUrl),
|
|
971
|
+
hint: "MODEL_PROVIDER is unset but an explicit ANTHROPIC base URL + credential are configured — inferred provider=anthropic (set MODEL_PROVIDER explicitly to override)",
|
|
972
|
+
},
|
|
973
|
+
});
|
|
974
|
+
}
|
|
993
975
|
const extraBody = buildExtraBody();
|
|
994
976
|
const thinkingDefault = parseDefaultThinking(process.env.MODEL_DEFAULT_THINKING);
|
|
995
977
|
const envCompatDeclared = buildModelCompat({
|
|
@@ -1021,35 +1003,40 @@ function parseModelDomain() {
|
|
|
1021
1003
|
...(envCompat ? { compat: envCompat } : {}),
|
|
1022
1004
|
...(extraBody ? { extraBody } : {}),
|
|
1023
1005
|
};
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1006
|
+
const cheapSlot = route.cheapSlot;
|
|
1007
|
+
switch (cheapSlot.kind) {
|
|
1008
|
+
case "without_main":
|
|
1009
|
+
CONFIG_NOTICES.push({
|
|
1010
|
+
level: "warn",
|
|
1011
|
+
event: "model_cheap_slot_without_main",
|
|
1012
|
+
fields: { hint: "MODEL_CHEAP_ID is set but MODEL_ID is not — the env cheap slot derives from the env main model, so it is not minted; the catalog (config.d/models.json or the config center) serves the roles. Set MODEL_ID to use the env slots, or unset MODEL_CHEAP_ID to silence this line." },
|
|
1013
|
+
});
|
|
1014
|
+
break;
|
|
1015
|
+
case "same_as_main":
|
|
1016
|
+
CONFIG_NOTICES.push({
|
|
1017
|
+
level: "warn",
|
|
1018
|
+
event: "config_env_entry_cheap_same_as_main",
|
|
1019
|
+
fields: {
|
|
1020
|
+
env: "MODEL_CHEAP_ID",
|
|
1021
|
+
model: modelId,
|
|
1022
|
+
hint: `MODEL_CHEAP_ID names the same model as MODEL_ID ("${modelId}") — the env cheap slot is the main gateway under a different id, ` +
|
|
1023
|
+
`so the same id IS the main model: no cheap slot is minted and subagent / summarize stay on the main model (roles -> default); ` +
|
|
1024
|
+
`any other MODEL_CHEAP_* key set here describes that slot and is not applied. Set MODEL_CHEAP_ID to a different (cheaper) model ` +
|
|
1025
|
+
`id to run those roles cheap, or declare two differently-named entries in config.d/models.json to give one upstream id a second ` +
|
|
1026
|
+
`shape (price / reasoning / window); unset MODEL_CHEAP_ID to silence this line.`,
|
|
1027
|
+
},
|
|
1028
|
+
});
|
|
1029
|
+
break;
|
|
1030
|
+
case "absent":
|
|
1031
|
+
case "minted":
|
|
1032
|
+
break;
|
|
1047
1033
|
}
|
|
1048
|
-
const
|
|
1034
|
+
const cheapMintedId = mintedCheapId(cheapSlot);
|
|
1035
|
+
const cheapModel = cheapMintedId !== undefined
|
|
1049
1036
|
? {
|
|
1050
1037
|
...model,
|
|
1051
|
-
id:
|
|
1052
|
-
name:
|
|
1038
|
+
id: cheapMintedId,
|
|
1039
|
+
name: cheapMintedId,
|
|
1053
1040
|
input: ((v) => (v === undefined ? model.input : v ? ["text", "image"] : ["text"]))(parseBoolWord("MODEL_CHEAP_VISION", process.env.MODEL_CHEAP_VISION)),
|
|
1054
1041
|
...buildModelCostKey(buildCostOrAbsent({
|
|
1055
1042
|
input: costEnv("MODEL_CHEAP_COST_INPUT", "declares-zero"),
|
|
@@ -1093,9 +1080,7 @@ function parseModelDomain() {
|
|
|
1093
1080
|
}
|
|
1094
1081
|
noticeAutoCompactAboveWindow("cheap", "MODEL_CHEAP_AUTO_COMPACT_TOKENS", cheapModel);
|
|
1095
1082
|
}
|
|
1096
|
-
const models = modelId
|
|
1097
|
-
if (cheapModel)
|
|
1098
|
-
models[cheapModel.id] = cheapModel;
|
|
1083
|
+
const models = envLaneTable(modelId, model, cheapModel);
|
|
1099
1084
|
const gatewayApiKey = process.env.MODEL_API_KEY;
|
|
1100
1085
|
if (gatewayBaseUrl === undefined && gatewayApiKey !== undefined && gatewayApiKey !== "") {
|
|
1101
1086
|
CONFIG_NOTICES.push({
|
|
@@ -1129,13 +1114,13 @@ function parseModelDomain() {
|
|
|
1129
1114
|
const degradeOn = parseDegradeOn(csv("MODEL_DEGRADE_ON"));
|
|
1130
1115
|
return {
|
|
1131
1116
|
...(gatewayBaseUrl !== undefined ? { gatewayBaseUrl, gatewayApiKey } : {}),
|
|
1132
|
-
gatewayFallbackUrls:
|
|
1117
|
+
gatewayFallbackUrls: route.gatewayFallbackUrls,
|
|
1133
1118
|
...(process.env.GATEWAY_MAX_RETRIES ? { gatewayMaxRetries: numEnvBounded("GATEWAY_MAX_RETRIES", "2", 0, 20) } : {}),
|
|
1134
|
-
anthropic:
|
|
1119
|
+
anthropic: route.anthropicRouteMounted
|
|
1135
1120
|
? {
|
|
1136
|
-
apiKey:
|
|
1137
|
-
authToken:
|
|
1138
|
-
baseUrl:
|
|
1121
|
+
apiKey: route.anthropicApiKey,
|
|
1122
|
+
authToken: route.anthropicAuthToken,
|
|
1123
|
+
baseUrl: route.anthropicBaseUrl,
|
|
1139
1124
|
version: process.env.ANTHROPIC_VERSION,
|
|
1140
1125
|
cacheBreakpoints: boolEnv("ANTHROPIC_CACHE_BREAKPOINTS", true),
|
|
1141
1126
|
...(process.env.ANTHROPIC_MAX_RETRIES ? { maxRetries: numEnv("ANTHROPIC_MAX_RETRIES", "2") } : {}),
|
|
@@ -1155,6 +1140,7 @@ function parseModelDomain() {
|
|
|
1155
1140
|
modelApiKeyEnv: {},
|
|
1156
1141
|
modelApiKeys: {},
|
|
1157
1142
|
modelRefusals: {},
|
|
1143
|
+
droppedCatalogEntries: [],
|
|
1158
1144
|
modelQuotaWeights: {},
|
|
1159
1145
|
tiers: {},
|
|
1160
1146
|
projects: {},
|
|
@@ -1272,9 +1258,6 @@ export function approvalGrantRefusal(names) {
|
|
|
1272
1258
|
const over = allowLayerEntryCountIssue(APPROVAL_GRANT_ENV, names);
|
|
1273
1259
|
if (over !== undefined)
|
|
1274
1260
|
return `${over.message} — refusing to start rather than running with a truncated list`;
|
|
1275
|
-
const empty = emptyToolSegmentEntryOf(names);
|
|
1276
|
-
if (empty !== undefined)
|
|
1277
|
-
return `${describeEmptyToolSegmentEntry(APPROVAL_GRANT_ENV, empty)} — refusing to start rather than running without it`;
|
|
1278
1261
|
const inert = inertRuleEntriesOf(names, "allow");
|
|
1279
1262
|
if (inert.length > 0)
|
|
1280
1263
|
return `${describeInertRuleEntries(APPROVAL_GRANT_ENV, inert)} — the engine would grant nothing for ${inert.length === 1 ? "it" : "them"}; refusing to start rather than running with an allow rule that never takes effect`;
|
|
@@ -1375,7 +1358,7 @@ function parseApprovalDomain(ctx) {
|
|
|
1375
1358
|
const trimmed = raw.trim();
|
|
1376
1359
|
if (trimmed === "" || trimmed.toLowerCase() === "off")
|
|
1377
1360
|
return [];
|
|
1378
|
-
return trimmed
|
|
1361
|
+
return csvOf(trimmed);
|
|
1379
1362
|
})(process.env.SENSITIVE_WRITE_PATTERNS),
|
|
1380
1363
|
...((decision) => (decision === undefined ? {} : { writeProtectedPaths: decision }))(parseWriteProtectedTableEnv(process.env.WRITE_PROTECTED_EXTRA, process.env.WRITE_PROTECTED_TABLE_REPLACE)),
|
|
1381
1364
|
...((raw) => {
|
|
@@ -1826,7 +1809,7 @@ function parseAuthDomain(ctx) {
|
|
|
1826
1809
|
authToken: process.env.SERVICE_AUTH_TOKEN,
|
|
1827
1810
|
authTokens,
|
|
1828
1811
|
allowUnauthedWrites: boolEnv("ALLOW_UNAUTHED_WRITES", false),
|
|
1829
|
-
corsOrigins: (
|
|
1812
|
+
corsOrigins: csv("CORS_ORIGIN"),
|
|
1830
1813
|
principalHeader: headerNameEnv("PRINCIPAL_HEADER", "x-agent-principal"),
|
|
1831
1814
|
requirePrincipal,
|
|
1832
1815
|
autonomy: parseAutonomy(process.env.AUTONOMY),
|
|
@@ -2011,7 +1994,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
2011
1994
|
? {
|
|
2012
1995
|
repoRoot: process.env.WORKTREE_REPO_ROOT,
|
|
2013
1996
|
...(process.env.WORKTREE_ALLOWED_ROOTS
|
|
2014
|
-
? { allowedRoots:
|
|
1997
|
+
? { allowedRoots: csv("WORKTREE_ALLOWED_ROOTS") }
|
|
2015
1998
|
: {}),
|
|
2016
1999
|
...(process.env.WORKTREE_BASE_COMMIT ? { commit: process.env.WORKTREE_BASE_COMMIT } : {}),
|
|
2017
2000
|
}
|
|
@@ -2095,7 +2078,7 @@ function parseLimitsHttpDomain() {
|
|
|
2095
2078
|
workspaceFileMaxBytes: optFinitePositiveEnv("WORKSPACE_FILE_MAX_BYTES") ?? 8 * 1024 * 1024,
|
|
2096
2079
|
attachmentMaxBytes: clampEnvWithWarn("ATTACHMENT_MAX_BYTES", String(32 * 1024 * 1024), LIMITS_HTTP_FLOORS.attachmentMaxBytes),
|
|
2097
2080
|
...(process.env.ATTACHMENT_MIME_ALLOWLIST
|
|
2098
|
-
? { attachmentMimeAllowlist:
|
|
2081
|
+
? { attachmentMimeAllowlist: csv("ATTACHMENT_MIME_ALLOWLIST").map((s) => s.toLowerCase()) }
|
|
2099
2082
|
: {}),
|
|
2100
2083
|
attachmentUnboundTtlMs: clampEnvWithWarn("ATTACHMENT_UNBOUND_TTL_MS", String(24 * 3600 * 1000), LIMITS_HTTP_FLOORS.attachmentUnboundTtlMs),
|
|
2101
2084
|
infraCostRates: {
|
|
@@ -2190,7 +2173,7 @@ function parseIntegrationsDomain(ctx) {
|
|
|
2190
2173
|
const fleetAdvertiseAddress = parseFleetAdvertiseAddress(process.env.FLEET_ADVERTISE_ADDRESS);
|
|
2191
2174
|
fleetFenceLaneOf({ ...fleetGeneration, configCenter, ...fleetAdvertiseAddress });
|
|
2192
2175
|
return {
|
|
2193
|
-
pluginsAllowHosts: (process.env.PLUGINS_ALLOW_HOSTS ?? "github.com")
|
|
2176
|
+
pluginsAllowHosts: csvOf(process.env.PLUGINS_ALLOW_HOSTS ?? "github.com"),
|
|
2194
2177
|
configBootFetchBudgetMs: clampEnvWithWarn("CONFIG_BOOT_FETCH_BUDGET_MS", "1500", 0),
|
|
2195
2178
|
gitApiBaseUrl: process.env.GIT_API_BASEURL,
|
|
2196
2179
|
gitApiToken: process.env.GIT_API_TOKEN,
|
|
@@ -2388,7 +2371,7 @@ const STORE_GROUP_KEYS = [
|
|
|
2388
2371
|
];
|
|
2389
2372
|
const MODEL_PLANE_GROUP_KEYS = [
|
|
2390
2373
|
"gatewayBaseUrl", "gatewayApiKey", "gatewayFallbackUrls", "gatewayMaxRetries", "anthropic", "resilience", "model", "envModel", "models",
|
|
2391
|
-
"modelApiKeyEnv", "modelApiKeys", "modelRefusals", "modelQuotaWeights", "tiers", "projects", "roles", "atModelAllowlist", "cascadeLadder", "degrade",
|
|
2374
|
+
"modelApiKeyEnv", "modelApiKeys", "modelRefusals", "droppedCatalogEntries", "modelQuotaWeights", "tiers", "projects", "roles", "atModelAllowlist", "cascadeLadder", "degrade",
|
|
2392
2375
|
];
|
|
2393
2376
|
const APPROVAL_GROUP_KEYS = [
|
|
2394
2377
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto", "approvalGrant",
|
|
@@ -2527,15 +2510,9 @@ export function loadConfig() {
|
|
|
2527
2510
|
throw new Error(laneRequiredRefusal);
|
|
2528
2511
|
}
|
|
2529
2512
|
{
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
gatewayFallbackUrls: modelPlane.gatewayFallbackUrls,
|
|
2534
|
-
anthropic: modelPlane.anthropic,
|
|
2535
|
-
models: envLaneDispatchable ? modelPlane.models : {},
|
|
2536
|
-
});
|
|
2537
|
-
if (refusal !== undefined) {
|
|
2538
|
-
throw new Error(refusal);
|
|
2513
|
+
const routeGap = judgeModelRouteGap(process.env, { envLaneDispatchable: !isRosterGateArmed(integrations.configCenter) });
|
|
2514
|
+
if (routeGap.gap !== "none") {
|
|
2515
|
+
throw new Error(routeGap.reason);
|
|
2539
2516
|
}
|
|
2540
2517
|
}
|
|
2541
2518
|
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. **一只铸点,三个出口**:{@link mintEngineSignal} 是「引擎此刻自己说自己什么」的唯一来源。`GET /health`(诊断面)、
|
|
7
7
|
* `GET /readyz`(就绪探针)与 fleet 心跳(`fleet-client.ts`)各读它**一次**,再按各自的出口形投影;没有出口另算一遍
|
|
8
|
-
* 就绪 / 重启 / 代际 /
|
|
8
|
+
* 就绪 / 排空 / 重启 / 代际 / 租约 / 世代账,也没有出口绕过它去读 `deps` 上的这些输入。
|
|
9
9
|
* 2. **就绪**仍只有一只谓词(`http/readiness.ts` 的 `readinessOf`),铸点把它的判决原样带上(`verdict`)。`/readyz` 200 ⇔
|
|
10
10
|
* `ready ∧ ¬draining ∧ leaseState ≠ denied` —— 后两项不是新判据:`draining` 本来就是就绪原因,而租约被拒**同一拍**经
|
|
11
11
|
* 唯一的排空入口进入排空(`fleet/serving-lease.ts`),所以「被拒 ⇒ 排空 ⇒ 不就绪」是构造出来的,不靠第二只谓词。
|
|
@@ -15,21 +15,25 @@
|
|
|
15
15
|
* 是安全可重启的,扣着的片不阻止它们);被扣的片为空(只差发布重试)⇒ 不是重启事实,不铸(契约 `RestartSignal.reasons` 至少一词;
|
|
16
16
|
* 那一形的面只有每拍一行运维 warn + 计数)。「扣着」在两出口上只有这一种读法(7.120.x 的 `/health.modelPlaneDeferred` 本版删除)。
|
|
17
17
|
* 合成只在 {@link buildRestartSignal} 一处,`restartState()` 是它唯一的调用方 —— 两个出口读的是同一只取值口。
|
|
18
|
-
* 4.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
18
|
+
* 4. **代际**:`generation` = env `FLEET_GENERATION` 的回声(规范十进制串;非规范在 boot 拒启,见 `config.ts`);缺席 ⇔ 非 fleet 成员。
|
|
19
|
+
* **身份不是引擎态**:`instanceId`(本进程起步自铸的那一只,`boot/stage-03` 的 uuidv7)是体的身份,只在两出口体的**顶层**说一次,
|
|
20
|
+
* 铸点不读它、契约形里不重复它。
|
|
21
|
+
* 5. **两形一读(展开 / 收缩)**:两出口今天各发两形 —— 嵌套键 {@link ENGINE_SIGNAL_KEY}(契约 `EngineSignal` 整只,{@link buildEngineSignal}),
|
|
22
|
+
* 与 8.0 扁平方言(布尔 `draining` + `drainingSince`、`/health` 就绪时不带 `ready`、`restart`、成员三键、`/health` 的世代账)。两形读的是
|
|
23
|
+
* **同一次**铸点读数里的**同一个字段**,投影全在本模块({@link buildEngineSignal} / {@link heartbeatReadinessKeys} / {@link healthDrainingKeys} /
|
|
24
|
+
* {@link fleetMembershipKeys} / {@link healthConfigLedgerKeys}),出口文件不拼引擎态的键。排空在两形上读的都是排空**事实**(`EngineDraining`,
|
|
25
|
+
* 三键齐才在),不是就绪原因 —— 所以「`engineSignal.draining` 在场 ⇔ 扁平 `draining` 为真、`drainingSince` = `engineSignal.draining.since`」
|
|
26
|
+
* 是构造出来的,不靠「唯一入口整组写四键」这条运行时不变量(那条不变量只管就绪原因与事实一致)。
|
|
27
|
+
* 心跳体 = 契约 `InstanceAnnouncement` + 嵌套键这一只:契约包 10.x 的心跳形还没有它的座位,10.x 中心按默认 strip 解析丢掉它、照收心跳;
|
|
28
|
+
* 世代账扁平两键契约心跳形没有座位,只上 `/health`(嵌套形里有,两出口都带)。非 fleet 部署的扁平键与 7.122.0 逐字同。
|
|
29
|
+
* 收缩:扁平布尔对(`draining` / `drainingSince`)settings-schema 12.0.0 从契约形删、本引擎 7.124.0+ 删发(届时删它们的投影);嵌套键名在
|
|
30
|
+
* 契约包自有这只常量的那一版提货时改读它。
|
|
27
31
|
* 6. **租约四态的词表属主是契约包**(`LeaseState`):本模块的读数型 {@link ServingLeaseView} 按它逐词对齐(下面的编译期断言两向钉),
|
|
28
32
|
* 契约加一个词 ⇒ 本仓编译红,不会悄悄多出一个本仓读不出的态。
|
|
29
33
|
*
|
|
30
|
-
* 模块形:纯函数(
|
|
34
|
+
* 模块形:纯函数(契约包只 import 型;值 import 只有两只纯叶 `http/readiness.ts` 与 `fleet/drain-entry.ts`)—— `http/` 与 `fleet-client.ts` 都读它,不许长出回环。
|
|
31
35
|
*/
|
|
32
|
-
import type { EngineDraining, InstanceAnnouncement, LeaseState, RestartSignal } from "@sema-agent/settings-schema/fleet";
|
|
36
|
+
import type { EngineDraining, EngineSignal, InstanceAnnouncement, LeaseState, RestartSignal } from "@sema-agent/settings-schema/fleet";
|
|
33
37
|
import { type ReadinessInputs, type ReadinessVerdict } from "../http/readiness.js";
|
|
34
38
|
/**
|
|
35
39
|
* 租约此刻的读数(租约客户端交出;纯数据)。态的词表 = 契约 `LeaseState`(四词的语义在契约注里:`none` 不生效 / `granted` 持有 /
|
|
@@ -94,9 +98,16 @@ export declare function configHandoffMissing(ledger: ConfigHandoffLedger): boole
|
|
|
94
98
|
/** `configHandoff` 的**唯一**合成点(纯):缺着 ⇒ `{ state: "missing", version: 应用着的那一代, since }`;否则缺席。`since` 由持账方锁存(发现缺着的那一拍)。
|
|
95
99
|
* `build*` = 纯数据;config-center 的 `configHandoffState()` 是唯一调用方。 */
|
|
96
100
|
export declare function buildConfigHandoff(ledger: ConfigHandoffLedger, since: number | undefined): ConfigHandoff | undefined;
|
|
101
|
+
/** 世代账的一次读数(config-center 的 `configApplyState()` 交出的那一份里本模块读的五键;`/health` 扁平五键与嵌套形的两键读同一份)。 */
|
|
102
|
+
export interface ConfigLedgerReading {
|
|
103
|
+
readonly configTargetVersion: number;
|
|
104
|
+
readonly configAppliedVersion: number;
|
|
105
|
+
readonly configTargetOrdinal: number;
|
|
106
|
+
readonly configAppliedOrdinal: number;
|
|
107
|
+
readonly configApplyStaleMs?: number;
|
|
108
|
+
}
|
|
97
109
|
/** 铸点读的输入 —— `ServiceDeps` 上的同名键(HTTP 面与心跳读的是**同一个**对象)。 */
|
|
98
110
|
export interface EngineSignalInputs extends ReadinessInputs {
|
|
99
|
-
readonly instanceId?: string;
|
|
100
111
|
/** `config.fleetGeneration` = env `FLEET_GENERATION` 的回声(boot 已判规范;缺席 ⇔ 非 fleet 成员)。 */
|
|
101
112
|
readonly config?: {
|
|
102
113
|
readonly fleetGeneration?: string;
|
|
@@ -108,34 +119,54 @@ export interface EngineSignalInputs extends ReadinessInputs {
|
|
|
108
119
|
};
|
|
109
120
|
readonly restartState?: () => RestartSignal | undefined;
|
|
110
121
|
readonly servingLease?: () => ServingLeaseView;
|
|
111
|
-
readonly configApplyState?: () =>
|
|
112
|
-
readonly configTargetVersion: number;
|
|
113
|
-
readonly configAppliedVersion: number;
|
|
114
|
-
} | undefined;
|
|
122
|
+
readonly configApplyState?: () => ConfigLedgerReading | undefined;
|
|
115
123
|
}
|
|
116
124
|
/** 铸出的信号(纯数据)。 */
|
|
117
125
|
export interface EngineSignalFacts {
|
|
118
126
|
/** 就绪判(`readinessOf` 原样)。 */
|
|
119
127
|
readonly verdict: ReadinessVerdict;
|
|
120
|
-
/** 在场 ⇔ 排空中(契约 `EngineDraining`:动作 / 起点 / 期限)。 */
|
|
128
|
+
/** 在场 ⇔ 排空中(契约 `EngineDraining`:动作 / 起点 / 期限)—— 两出口两形读排空的唯一字段(规则 5)。 */
|
|
121
129
|
readonly draining?: EngineDraining;
|
|
122
130
|
readonly restart?: RestartSignal;
|
|
123
131
|
readonly generation?: string;
|
|
124
|
-
readonly instanceId?: string;
|
|
125
132
|
readonly lease: ServingLeaseView;
|
|
126
|
-
|
|
127
|
-
readonly
|
|
133
|
+
/** 世代账的那一次读数(未接线 ⇒ 缺席)。 */
|
|
134
|
+
readonly configApply?: ConfigLedgerReading;
|
|
128
135
|
}
|
|
129
136
|
/** **唯一**铸点(活判,每次调用现读输入)。 */
|
|
130
137
|
export declare function mintEngineSignal(deps: EngineSignalInputs): EngineSignalFacts;
|
|
138
|
+
/**
|
|
139
|
+
* 两出口体上嵌套引擎信号的键名(规则 5;两出口同一只拼法)。契约包 10.x 没有这只常量(它的心跳形也没有这一座位);契约包自有它的那一版
|
|
140
|
+
* 提货时,本常量删、改读契约包的。
|
|
141
|
+
*/
|
|
142
|
+
export declare const ENGINE_SIGNAL_KEY = "engineSignal";
|
|
143
|
+
/** 体上的嵌套引擎信号:键 {@link ENGINE_SIGNAL_KEY},值 = 契约 `EngineSignal`。 */
|
|
144
|
+
export type EngineSignalCarrier = {
|
|
145
|
+
readonly [ENGINE_SIGNAL_KEY]: EngineSignal;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* 契约形 `EngineSignal` 的**唯一**投影(规则 5;两出口同调,同一次读数 ⇒ 同值同序)。键序照契约形:`ready` 恒说;`draining` = 排空事实;
|
|
149
|
+
* `restart` = 取值口交出的那个对象;成员三键 = {@link fleetMembershipKeys};世代账两键 = 同一次读数。身份不在这里(规则 4)。`build*` = 纯数据。
|
|
150
|
+
*/
|
|
151
|
+
export declare function buildEngineSignal(s: EngineSignalFacts): EngineSignal;
|
|
131
152
|
/**
|
|
132
153
|
* fleet 成员身份三键的**唯一**投影(两出口同调,同值同序):`generation` / `leaseState` / `leaseUntil`。
|
|
133
154
|
* 在场律:非 fleet 成员(无 `generation`)⇒ 空对象(两出口逐字节同 7.120.0);成员 ⇒ `generation` + `leaseState` 恒在,
|
|
134
155
|
* `leaseUntil` 只在 `granted`。
|
|
135
156
|
*/
|
|
136
157
|
export declare function fleetMembershipKeys(s: EngineSignalFacts): FleetMembershipKeys;
|
|
137
|
-
/**
|
|
138
|
-
export declare function
|
|
158
|
+
/** 心跳的 8.0 扁平方言 `ready` / `draining`(恒带,`false` 也带):`ready` = 就绪判,`draining` = 排空事实在场(规则 5,与嵌套形读同一个字段)。 */
|
|
159
|
+
export declare function heartbeatReadinessKeys(s: EngineSignalFacts): Pick<InstanceAnnouncement, "ready" | "draining">;
|
|
160
|
+
/** `/health` 的 8.0 扁平排空对:只在排空中带 —— `draining: true` + `drainingSince` = 排空事实的 `since`(规则 5,与嵌套形读同一个字段)。 */
|
|
161
|
+
export declare function healthDrainingKeys(s: EngineSignalFacts): {
|
|
162
|
+
readonly draining?: true;
|
|
163
|
+
readonly drainingSince?: number;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* `/health` 的世代账扁平键(未接线 ⇒ 全缺席):target / applied 两版本(与嵌套形同一次读数)+ 两只收货序数(进程内单调,「见过多少代 / 停在哪一代」
|
|
167
|
+
* 的推进判据)+ `configApplyStaleMs`(两者持续不等的时长,收敛即缺席)。只上 `/health`(契约心跳形没有这些座位)。
|
|
168
|
+
*/
|
|
169
|
+
export declare function healthConfigLedgerKeys(s: EngineSignalFacts): Partial<ConfigLedgerReading>;
|
|
139
170
|
/**
|
|
140
171
|
* `/health.restartRequired`(冗余顶层布尔,壳读它决定要不要问用户重启)的投影:在场 ⇔ 有**可执行**的重启信号
|
|
141
172
|
* (`restart` 在场且未被扣)—— 与 7.120.0 的读数逐字同(那时被扣的候选根本不出 `restart`)。
|
|
@@ -23,15 +23,24 @@ export function mintEngineSignal(deps) {
|
|
|
23
23
|
const ds = deps.drainState;
|
|
24
24
|
const draining = ds === undefined ? undefined : drainingFactOf(ds);
|
|
25
25
|
const restart = deps.restartState?.();
|
|
26
|
-
const
|
|
26
|
+
const configApply = deps.configApplyState?.();
|
|
27
27
|
return {
|
|
28
28
|
verdict,
|
|
29
29
|
...(draining !== undefined ? { draining } : {}),
|
|
30
30
|
...(restart !== undefined ? { restart } : {}),
|
|
31
31
|
...(deps.config?.fleetGeneration !== undefined ? { generation: deps.config.fleetGeneration } : {}),
|
|
32
|
-
...(deps.instanceId !== undefined ? { instanceId: deps.instanceId } : {}),
|
|
33
32
|
lease: deps.servingLease?.() ?? LEASE_NONE,
|
|
34
|
-
...(
|
|
33
|
+
...(configApply !== undefined ? { configApply } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export const ENGINE_SIGNAL_KEY = "engineSignal";
|
|
37
|
+
export function buildEngineSignal(s) {
|
|
38
|
+
return {
|
|
39
|
+
ready: s.verdict.ready,
|
|
40
|
+
...(s.draining !== undefined ? { draining: s.draining } : {}),
|
|
41
|
+
...(s.restart !== undefined ? { restart: s.restart } : {}),
|
|
42
|
+
...fleetMembershipKeys(s),
|
|
43
|
+
...(s.configApply !== undefined ? { configTargetVersion: s.configApply.configTargetVersion, configAppliedVersion: s.configApply.configAppliedVersion } : {}),
|
|
35
44
|
};
|
|
36
45
|
}
|
|
37
46
|
export function fleetMembershipKeys(s) {
|
|
@@ -51,8 +60,23 @@ export function fleetMembershipKeys(s) {
|
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
|
-
export function
|
|
55
|
-
return
|
|
63
|
+
export function heartbeatReadinessKeys(s) {
|
|
64
|
+
return { ready: s.verdict.ready, draining: s.draining !== undefined };
|
|
65
|
+
}
|
|
66
|
+
export function healthDrainingKeys(s) {
|
|
67
|
+
return s.draining === undefined ? {} : { draining: true, drainingSince: s.draining.since };
|
|
68
|
+
}
|
|
69
|
+
export function healthConfigLedgerKeys(s) {
|
|
70
|
+
const g = s.configApply;
|
|
71
|
+
if (g === undefined)
|
|
72
|
+
return {};
|
|
73
|
+
return {
|
|
74
|
+
configTargetVersion: g.configTargetVersion,
|
|
75
|
+
configAppliedVersion: g.configAppliedVersion,
|
|
76
|
+
configTargetOrdinal: g.configTargetOrdinal,
|
|
77
|
+
configAppliedOrdinal: g.configAppliedOrdinal,
|
|
78
|
+
...(g.configApplyStaleMs !== undefined ? { configApplyStaleMs: g.configApplyStaleMs } : {}),
|
|
79
|
+
};
|
|
56
80
|
}
|
|
57
81
|
export function restartActionable(s) {
|
|
58
82
|
return s.restart !== undefined && s.restart.withheld !== true;
|
package/dist/fleet-client.d.ts
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* 配置席 `fleetAdvertiseAddress`(env `FLEET_ADVERTISE_ADDRESS`,`loadConfig` 解析一次;可达性是运维的话,不自猜:k8s pod IP /
|
|
7
7
|
* docker alias / 127.0.0.1:port 只有部署环境自己知道)。不配中心的部署零行为变化;配了中心而不公告的引擎只报用量、不注册(7.105.0 及以前它连用量都不报)。
|
|
8
8
|
* - **announce=注册=心跳同形状幂等 upsert**:boot 后(listen 之后)POST 一次 + 每 heartbeatMs 续。
|
|
9
|
-
* `ready` / `draining`
|
|
10
|
-
* 与 `/health.ready`、`GET /readyz
|
|
11
|
-
* `draining
|
|
12
|
-
*
|
|
9
|
+
* `ready` / `draining` 两字段由引擎信号铸点的同一次读铸(S-740:`ready` = `http/readiness.ts` `readinessOf` 的判,
|
|
10
|
+
* 与 `/health.ready`、`GET /readyz`、计费提交门同源;= roster 已落 ∧ ¬draining),`draining` = 排空事实在场(与嵌套
|
|
11
|
+
* `engineSignal.draining` 同一个字段,`fleet/engine-signal.ts` 规则 5)。修前 `ready = !isDraining()`,不含 roster ⇒ roster 未落时
|
|
12
|
+
* 心跳说「就绪」而计费提交在 503。
|
|
13
|
+
* - **`engineSignal`(7.123.0)**:引擎态整只(契约 `EngineSignal`),与扁平键同一次读数;契约包 10.x 的心跳形没有它的座位(10.x 中心
|
|
14
|
+
* 默认 strip 解析丢掉它、照收心跳);扁平布尔 `draining` settings-schema 12.0.0 删、本引擎 7.124.0+ 删发。
|
|
13
15
|
* - **`restart?`(S-741)**:在场 ⇔ 本副本必须重启才能应用配置 —— 取自 `/health.restart` **同一只**取值口(装配层接
|
|
14
16
|
* `deployment.restartState`,config-center 的 `restartState()`),同一个对象,形 = settings-schema `RestartSignal`;
|
|
15
17
|
* 缺席即键缺席(中心按行整体替换,不合并 —— 一条不带它的心跳就是「不再需要」)。
|
|
@@ -80,8 +82,8 @@ export interface FleetClientOpts {
|
|
|
80
82
|
advertiseAddress?: string | undefined;
|
|
81
83
|
version: string;
|
|
82
84
|
/** 编排器片 2 件 1:引擎信号的**一只铸点**(装配层接 `mintEngineSignal(deployment)` —— `/health` 与 `/readyz` 读的同一只、同一个 deps 组)。
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
+
* 每条公告现铸**一次**,嵌套 `engineSignal` 与扁平 `ready` / `draining` / `restart` / 代际与租约三键都取自这一次(不会撕裂成 `ready:true` +
|
|
86
|
+
* `draining:true`,也不会出现心跳与 `/health` 各算一份重启或租约)。修前是 `readiness` + `restart` 两只取值口(S-740 / S-741),合进这一只。 */
|
|
85
87
|
signal: () => EngineSignalFacts;
|
|
86
88
|
/** S-768:模型拒因信号(装配层接 `/health` 同一只取值口 `deployment.modelRefusalsState`)。每条公告现读一次;`undefined` ⇒ 公告不带
|
|
87
89
|
* `modelRefusals` 键。必填:与 `restart` 同 —— 心跳面没有「不知道有没有拒」这一形(老引擎不发这个键,中心按 `version` 判「不可知」)。 */
|
package/dist/fleet-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InstanceAnnouncement, UsageReport, DEFAULT_HEARTBEAT_MS, readCapabilityReading } from "@sema-agent/settings-schema/fleet";
|
|
2
|
-
import {
|
|
2
|
+
import { ENGINE_SIGNAL_KEY, buildEngineSignal, fleetMembershipKeys, heartbeatReadinessKeys } from "./fleet/engine-signal.js";
|
|
3
3
|
import { centerFetch, centerLaneConfigured } from "./config-center/center-request.js";
|
|
4
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
5
5
|
export const FLEET_USAGE_KEY_CAP = 1024;
|
|
@@ -99,18 +99,19 @@ export function startFleetClient(opts) {
|
|
|
99
99
|
return;
|
|
100
100
|
try {
|
|
101
101
|
const signal = opts.signal();
|
|
102
|
+
const engineSignal = buildEngineSignal(signal);
|
|
102
103
|
const modelRefusals = opts.modelRefusals();
|
|
103
104
|
const body = {
|
|
104
105
|
instanceId: opts.instanceId,
|
|
105
106
|
worker: opts.center.worker,
|
|
106
107
|
address,
|
|
107
108
|
version: opts.version,
|
|
108
|
-
|
|
109
|
-
draining: drainingFlag(signal),
|
|
109
|
+
...heartbeatReadinessKeys(signal),
|
|
110
110
|
heartbeatMs,
|
|
111
111
|
...(signal.restart !== undefined ? { restart: signal.restart } : {}),
|
|
112
112
|
...(modelRefusals !== undefined ? { modelRefusals } : {}),
|
|
113
113
|
...fleetMembershipKeys(signal),
|
|
114
|
+
[ENGINE_SIGNAL_KEY]: engineSignal,
|
|
114
115
|
};
|
|
115
116
|
const { res } = await send("fleet-instances", "POST", body);
|
|
116
117
|
if (!res.ok)
|
package/dist/http/dispatch.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ROUTE_METHOD_ORDER } from "./route-ctx.js";
|
|
|
6
6
|
import { READ_CREDENTIAL_ROUTES, PUBLIC_ROUTES, POST_CREDENTIAL_ROUTES, routeLabel, isBillableSubmitPath, billableKindOf, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, lookupRoute, isProbePath, isSubscriptionRequest } from "./route-table.js";
|
|
7
7
|
import { sendJson, sendError, isSseResponse, endSseWithError } from "./send.js";
|
|
8
8
|
import { admissionRefusalOf, readinessOf, healthReadinessKeys, sendReadyz, sendBillableRefusal, billableRefusalOf } from "./readiness.js";
|
|
9
|
-
import { fleetMembershipKeys, mintEngineSignal, restartActionable } from "../fleet/engine-signal.js";
|
|
9
|
+
import { ENGINE_SIGNAL_KEY, buildEngineSignal, fleetMembershipKeys, healthConfigLedgerKeys, healthDrainingKeys, mintEngineSignal, restartActionable } from "../fleet/engine-signal.js";
|
|
10
10
|
import { buildHealthSelfReport } from "./health-self-report.js";
|
|
11
11
|
import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized, verifiedIdentitySource } from "./principal-gate.js";
|
|
12
12
|
const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
|
|
@@ -128,6 +128,7 @@ export function createDispatch(ctx) {
|
|
|
128
128
|
}
|
|
129
129
|
if (req.method === "GET" && url === "/health") {
|
|
130
130
|
const signal = mintEngineSignal(deps);
|
|
131
|
+
const engineSignal = buildEngineSignal(signal);
|
|
131
132
|
const configHash = createHash("sha256")
|
|
132
133
|
.update(JSON.stringify({ model: deps.config.model.id, sessionBackend: deps.config.sessionBackend, dbBackend: deps.config.dbBackend, dataRoot: deps.config.localDataRoot, configProvider: deps.config.configProvider, remoteExec: deps.config.remoteExec?.provider, port: deps.config.port }))
|
|
133
134
|
.digest("hex")
|
|
@@ -135,7 +136,7 @@ export function createDispatch(ctx) {
|
|
|
135
136
|
sendJson(res, 200, {
|
|
136
137
|
status: "ok",
|
|
137
138
|
...(typeof deps.capabilities?.version === "string" ? { version: deps.capabilities.version } : {}),
|
|
138
|
-
...(
|
|
139
|
+
...healthDrainingKeys(signal),
|
|
139
140
|
...(deps.drainState?.inflight ? { inflight: deps.drainState.inflight() } : {}),
|
|
140
141
|
...(deps.storePosture?.degradedReason !== undefined ? { durable: false, storeDegraded: true } : {}),
|
|
141
142
|
...healthReadinessKeys(signal.verdict),
|
|
@@ -156,18 +157,7 @@ export function createDispatch(ctx) {
|
|
|
156
157
|
const configHandoff = deps.configHandoffState?.();
|
|
157
158
|
return configHandoff ? { configHandoff } : {};
|
|
158
159
|
})(),
|
|
159
|
-
...(
|
|
160
|
-
const gen = deps.configApplyState?.();
|
|
161
|
-
if (!gen)
|
|
162
|
-
return {};
|
|
163
|
-
return {
|
|
164
|
-
configTargetVersion: gen.configTargetVersion,
|
|
165
|
-
configAppliedVersion: gen.configAppliedVersion,
|
|
166
|
-
configTargetOrdinal: gen.configTargetOrdinal,
|
|
167
|
-
configAppliedOrdinal: gen.configAppliedOrdinal,
|
|
168
|
-
...(gen.configApplyStaleMs !== undefined ? { configApplyStaleMs: gen.configApplyStaleMs } : {}),
|
|
169
|
-
};
|
|
170
|
-
})(),
|
|
160
|
+
...healthConfigLedgerKeys(signal),
|
|
171
161
|
...(() => {
|
|
172
162
|
const credential = deps.centerCredentialState?.();
|
|
173
163
|
return credential ? { centerCredential: credential } : {};
|
|
@@ -185,6 +175,7 @@ export function createDispatch(ctx) {
|
|
|
185
175
|
storeProbe: { live: probe.live, ageMs: probe.ageMs, ...(probe.error !== undefined ? { error: publicStoreProbeError(probe.error) } : {}) },
|
|
186
176
|
};
|
|
187
177
|
})(),
|
|
178
|
+
[ENGINE_SIGNAL_KEY]: engineSignal,
|
|
188
179
|
});
|
|
189
180
|
return;
|
|
190
181
|
}
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
* `/health` 排在凭据门之前、缺省全网卡监听:本模块只出闭集词、布尔与运维要发布的 URL。两只 URL 值都过 display-safe 律
|
|
24
24
|
* (`buildDisplaySafeUrl`,所有展示面 URL 的同一个铸点):userinfo 落地、query 值成记号、尾斜杠折掉 —— issuer 的来源
|
|
25
25
|
* (`AUTH_BRIDGE_ISSUER` / `SEMA_REGISTRY_URL`)起服时不判凭据形,这一道是它上无鉴权面的唯一闸。令牌、名单、worker 名、凭证一概不上。
|
|
26
|
+
* 同一张面上本模块之外还有一个**调用方自取**的串(如实,7.123.0 起;`model` / `dataRoot` / `version` 等其余非闭集的名是部署自己的事实,不是调用方放进来的):排空中的动作号 `engineSignal.draining.actionId`(`fleet/engine-signal.ts` 铸,契约 §0.4)——
|
|
27
|
+
* 引擎自起的排空是引擎的闭集词,经排空口(operator-only)进的排空是调用方自取的幂等键**原样**(契约包今天只要求非空串,推荐 `manual:<uuid>`),
|
|
28
|
+
* 所以那一键不是闭集词:调用方不要往里放秘密或人名。
|
|
26
29
|
*
|
|
27
30
|
* ## 在场律
|
|
28
31
|
* 三键**恒在场**;缺席 = 老 worker(≤7.120.x)。`center.issuer` / `center.webBaseUrl` 缺席即缺席(没有鉴权桥 / 没配旋钮),不拼不猜。
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PERMISSION_MODES, expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
2
|
import { modelImageInput, isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
3
|
+
import { buildDroppedEntriesWire } from "../../config-center/read-warnings.js";
|
|
3
4
|
import { modelKeyPoisonOf, resolveModelApiKey } from "../../key-resolver.js";
|
|
4
5
|
import { projectSealedKeyCapability } from "../../sealed-key.js";
|
|
5
6
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
@@ -254,7 +255,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
254
255
|
}));
|
|
255
256
|
const nonAlias = Object.entries(deps.config.models).filter(([n]) => n !== "default");
|
|
256
257
|
const defaultName = (nonAlias.find(([, m]) => m === deps.config.model) ?? nonAlias.find(([, m]) => m.id === deps.config.model.id))?.[0];
|
|
257
|
-
sendJson(res, 200, { models, default: deps.config.model.id, defaultModel: { id: deps.config.model.id, ...(defaultName ? { name: defaultName } : {}) } });
|
|
258
|
+
sendJson(res, 200, { models, default: deps.config.model.id, defaultModel: { id: deps.config.model.id, ...(defaultName ? { name: defaultName } : {}) }, droppedEntries: buildDroppedEntriesWire(deps.config.droppedCatalogEntries) });
|
|
258
259
|
return;
|
|
259
260
|
}
|
|
260
261
|
default:
|