@sema-agent/server 7.135.0 → 7.136.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 +64 -2
- package/USAGE.md +62 -25
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +1 -0
- package/dist/approval-content-kind.js +1 -1
- package/dist/auth-bridge.d.ts +34 -5
- package/dist/auth-bridge.js +20 -3
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/resolve-spec.js +10 -7
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stores.js +2 -3
- package/dist/boot/web-admin.js +10 -17
- package/dist/brain.js +12 -3
- package/dist/budget.d.ts +3 -3
- package/dist/budget.js +4 -0
- package/dist/capabilities/mcp-probe.d.ts +5 -4
- package/dist/capability-facts.d.ts +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- package/dist/config-catalog.js +12 -10
- package/dist/config-d-migration.js +1 -1
- package/dist/config-invariants.d.ts +1 -1
- package/dist/config-invariants.js +8 -0
- package/dist/config-lkg.d.ts +0 -38
- package/dist/config-lkg.js +1 -83
- package/dist/config-types.d.ts +12 -4
- package/dist/config.js +26 -8
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +6 -4
- package/dist/file-history-disclosure.js +22 -6
- package/dist/fleet-client.d.ts +32 -1
- package/dist/fleet-client.js +24 -4
- package/dist/fleet-lease.d.ts +7 -3
- package/dist/fleet-lease.js +31 -23
- package/dist/hooks/hook-llm.js +14 -8
- package/dist/hooks/hook-runner.d.ts +4 -4
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +28 -9
- package/dist/http/checkpoint-ctx.js +5 -1
- package/dist/http/dispatch.js +9 -9
- package/dist/http/resume-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- package/dist/http/routes/approvals-assistant.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +47 -12
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/session-sync.js +31 -9
- package/dist/http/routes/sessions.js +18 -14
- package/dist/http/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +5 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/model-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -1
- package/dist/object-store-buckets.d.ts +13 -0
- package/dist/object-store-buckets.js +3 -0
- package/dist/observability/fail-open.d.ts +6 -6
- package/dist/observability/fail-open.js +6 -6
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -1
- package/dist/parked-decide.d.ts +4 -3
- package/dist/parked-decide.js +26 -7
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +5 -5
- package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -0
- package/dist/plugins/blob-backend.js +2 -1
- package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
- package/dist/plugins/checkpoint-index-columns.js +28 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +27 -3
- package/dist/plugins/checkpoint-store-sql.js +58 -29
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +30 -23
- package/dist/plugins/local-checkpoint-store.js +4 -3
- package/dist/plugins/one-time-migrations.d.ts +21 -28
- package/dist/plugins/one-time-migrations.js +39 -123
- package/dist/plugins/remote-env-k8s.d.ts +18 -5
- package/dist/plugins/remote-env-k8s.js +24 -7
- package/dist/plugins/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
- package/dist/plugins/snapshot-bucket-presence.js +35 -7
- package/dist/plugins/sql-errors.d.ts +39 -0
- package/dist/plugins/sql-errors.js +49 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +8 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -2
- package/dist/request-declaration-refusal.d.ts +94 -0
- package/dist/request-declaration-refusal.js +78 -0
- package/dist/request-image-urls.d.ts +6 -0
- package/dist/request-image-urls.js +27 -0
- package/dist/request-key-closure.d.ts +5 -0
- package/dist/request-key-closure.js +4 -1
- package/dist/runtime-caps-resolver.js +1 -1
- package/dist/sandbox-pkg-source.d.ts +28 -1
- package/dist/sandbox-pkg-source.js +1 -2
- package/dist/sealed-key.d.ts +19 -0
- package/dist/sealed-key.js +47 -4
- package/dist/session-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- package/dist/session-row.d.ts +3 -17
- package/dist/session-row.js +4 -0
- package/dist/session-sync.d.ts +15 -3
- package/dist/session-sync.js +5 -2
- package/dist/task-a2a.d.ts +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +6 -14
- package/dist/task-mcp.js +16 -51
- package/dist/tool-approval.d.ts +23 -10
- package/dist/tool-approval.js +51 -36
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/exit-seat.js +37 -13
- package/dist/trace/redact.d.ts +28 -12
- package/dist/trace/redact.js +233 -94
- package/dist/trace/self-minted-wire.d.ts +267 -0
- package/dist/trace/self-minted-wire.js +79 -0
- package/dist/url-credential-refusal.d.ts +52 -0
- package/dist/url-credential-refusal.js +14 -0
- package/dist/url-refusal.d.ts +13 -4
- package/dist/url-refusal.js +8 -2
- package/dist/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/package.json +3 -2
- package/scripts/drain-check-local.mjs +2 -1
- package/scripts/drain-check.mysql.sql +3 -1
- package/scripts/drain-check.pg.sql +3 -1
- package/skills/schedule.md +1 -1
- package/wire-changes/server@7.104.0.json +134 -0
- package/wire-changes/server@7.105.0.json +53 -0
- package/wire-changes/server@7.106.0.json +475 -0
- package/wire-changes/server@7.107.0.json +64 -0
- package/wire-changes/server@7.108.0.json +153 -0
- package/wire-changes/server@7.109.0.json +46 -0
- package/wire-changes/server@7.110.0.json +90 -0
- package/wire-changes/server@7.111.0.json +86 -0
- package/wire-changes/server@7.112.0.json +60 -0
- package/wire-changes/server@7.113.0.json +26 -0
- package/wire-changes/server@7.114.0.json +26 -0
- package/wire-changes/server@7.115.0.json +97 -0
- package/wire-changes/server@7.116.0.json +218 -0
- package/wire-changes/server@7.116.1.json +26 -0
- package/wire-changes/server@7.117.0.json +65 -0
- package/wire-changes/server@7.118.0.json +129 -0
- package/wire-changes/server@7.119.0.json +65 -0
- package/wire-changes/server@7.120.0.json +89 -0
- package/wire-changes/server@7.121.0.json +219 -0
- package/wire-changes/server@7.122.0.json +54 -0
- package/wire-changes/server@7.123.0.json +26 -0
- package/wire-changes/server@7.124.0.json +336 -0
- package/wire-changes/server@7.124.1.json +26 -0
- package/wire-changes/server@7.125.0.json +111 -0
- package/wire-changes/server@7.126.0.json +285 -0
- package/wire-changes/server@7.127.0.json +77 -0
- package/wire-changes/server@7.127.1.json +26 -0
- package/wire-changes/server@7.127.2.json +53 -0
- package/wire-changes/server@7.128.0.json +26 -0
- package/wire-changes/server@7.129.0.json +106 -0
- package/wire-changes/server@7.129.1.json +26 -0
- package/wire-changes/server@7.130.0.json +26 -0
- package/wire-changes/server@7.131.0.json +118 -0
- package/wire-changes/server@7.132.0.json +226 -0
- package/wire-changes/server@7.133.0.json +1155 -0
- package/wire-changes/server@7.134.0.json +107 -0
- package/wire-changes/server@7.135.0.json +174 -0
- package/wire-changes/server@7.136.0.json +448 -0
- package/dist/url-credential.d.ts +0 -52
- package/dist/url-credential.js +0 -49
package/dist/brain.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createHash } from "node:crypto";
|
|
|
6
6
|
import { isModelKeyPoison, modelKeyPoisonIdentity } from "./key-resolver.js";
|
|
7
7
|
import { anthropicRouteRoot, brainLaneFor, mountRoutes, routeTableOf } from "./model-provider.js";
|
|
8
8
|
import { openaiRouteBaseUrl } from "./model-route-endpoint.js";
|
|
9
|
+
import { anthropicLaneCredentialOf } from "./model-route-gap.js";
|
|
9
10
|
import { catalogEntryFingerprint } from "./web-admin-client/restart-signal.js";
|
|
10
11
|
import { buildDisplaySafeRefusal, buildDisplaySafeUrl } from "./trace/redact.js";
|
|
11
12
|
import { createModelReachBrain } from "./model-unreachable-notice.js";
|
|
@@ -82,9 +83,7 @@ export function createBrainAssembly(config, deps = {}) {
|
|
|
82
83
|
? createFailoverBrain(openaiBrains)
|
|
83
84
|
: openaiBrains[0];
|
|
84
85
|
const brainRoutes = mountRoutes(config, (anthropicCfg) => constructRouteBrain("anthropic route", [anthropicCfg.baseUrl], () => createAnthropicBrain({
|
|
85
|
-
...(anthropicCfg
|
|
86
|
-
? { headers: { Authorization: `Bearer ${anthropicCfg.authToken}` } }
|
|
87
|
-
: { apiKey: anthropicCfg.apiKey }),
|
|
86
|
+
...anthropicCredentialFields(anthropicLaneCredentialOf(anthropicCfg)),
|
|
88
87
|
baseUrl: anthropicRouteRoot(anthropicCfg),
|
|
89
88
|
version: anthropicCfg.version,
|
|
90
89
|
cacheBreakpoints: anthropicCfg.cacheBreakpoints,
|
|
@@ -139,6 +138,16 @@ export function createBrainAssembly(config, deps = {}) {
|
|
|
139
138
|
}
|
|
140
139
|
return { brain: createModelReachBrain(brain), degradeRoute: { read: () => ({ baked: degradeBaked, current: degradeRouteFingerprint(config) }) } };
|
|
141
140
|
}
|
|
141
|
+
function anthropicCredentialFields(credential) {
|
|
142
|
+
if (credential === undefined)
|
|
143
|
+
return {};
|
|
144
|
+
switch (credential.kind) {
|
|
145
|
+
case "x-api-key":
|
|
146
|
+
return { apiKey: credential.value };
|
|
147
|
+
case "bearer":
|
|
148
|
+
return { headers: { Authorization: `Bearer ${credential.value}` } };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
142
151
|
export function createBrain(config, deps = {}) {
|
|
143
152
|
return createBrainAssembly(config, deps).brain;
|
|
144
153
|
}
|
package/dist/budget.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { type Model, type ModelPricing, type TracerHook } from "@sema-agent/core";
|
|
9
9
|
import type { Metrics } from "./observability/metrics.js";
|
|
10
10
|
import type { QuotaTracker } from "./observability/cost-quota.js";
|
|
11
|
-
import type {
|
|
11
|
+
import type { FleetUsageBins } from "./fleet-client.js";
|
|
12
12
|
import type { FleetLeaseManager } from "./fleet-lease.js";
|
|
13
13
|
import { type PromptManifestTracker } from "./observability/prompt-manifest.js";
|
|
14
14
|
/** ③ Per-model pricing keyed by model id (core falls back to each Model.cost when a model is absent).
|
|
@@ -108,7 +108,7 @@ export { type PromptManifestRecord, promptManifestRecordOf, configAssembledRecor
|
|
|
108
108
|
* fires for EVERY call (top-level + async + council sub-tasks) → `model_cost_micro_usd_total` is the authoritative
|
|
109
109
|
* spend, and (when a {@link ModelUsageTracker} is supplied) the per-task `modelUsage` echo's source.
|
|
110
110
|
*/
|
|
111
|
-
export declare function createTracer(metrics: Pick<Metrics, "inc" | "observe">, costQuota?: QuotaTracker, modelUsage?: ModelUsageTracker, fleetUsage?: Pick<
|
|
111
|
+
export declare function createTracer(metrics: Pick<Metrics, "inc" | "observe">, costQuota?: QuotaTracker, modelUsage?: ModelUsageTracker, fleetUsage?: Pick<FleetUsageBins, "record" | "pinExecution">, fleetLease?: Pick<FleetLeaseManager, "recordSpend" | "pinExecution">, quotaWeightFor?: (model: string) => number, promptManifests?: PromptManifestTracker): TracerHook;
|
|
112
112
|
/**
|
|
113
113
|
* [ref] side-query 记账 seam(codex R3 high:sideQuery 不走 core tracer——brain.stream 直调不发
|
|
114
114
|
* brain.call——createTracer 的四路 sink(costQuota/model_cost 指标/fleetUsage 批报/fleetLease 本地扣减)
|
|
@@ -121,7 +121,7 @@ export declare function createTracer(metrics: Pick<Metrics, "inc" | "observe">,
|
|
|
121
121
|
* (保守,绝不少收);根修=core 在 SideQueryResult 上回权威 costMicroUsd(已提货 core)。model 标签/
|
|
122
122
|
* weight 键用**路由身份 r.model**(池配置有界)非 servedModel(provider 上报,无界)。
|
|
123
123
|
*/
|
|
124
|
-
export declare function createSideQueryAccountant(metrics: Pick<Metrics, "inc">, costQuota?: QuotaTracker, fleetUsage?: Pick<
|
|
124
|
+
export declare function createSideQueryAccountant(metrics: Pick<Metrics, "inc">, costQuota?: QuotaTracker, fleetUsage?: Pick<FleetUsageBins, "record">, fleetLease?: Pick<FleetLeaseManager, "recordSpend">, quotaWeightFor?: (model: string) => number): (principal: string | undefined, r: {
|
|
125
125
|
model: string;
|
|
126
126
|
usage?: {
|
|
127
127
|
input?: number;
|
package/dist/budget.js
CHANGED
|
@@ -77,6 +77,10 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
|
|
|
77
77
|
const modelId = cardinalityGuard(256, () => metrics.inc("metric_label_overflow_total", { label: "model" }));
|
|
78
78
|
const providerName = cardinalityGuard(64, () => metrics.inc("metric_label_overflow_total", { label: "provider" }));
|
|
79
79
|
return (e) => {
|
|
80
|
+
if (e.kind === "task.start") {
|
|
81
|
+
fleetUsage?.pinExecution();
|
|
82
|
+
fleetLease?.pinExecution();
|
|
83
|
+
}
|
|
80
84
|
if (e.kind === "brain.call") {
|
|
81
85
|
metrics.inc("brain_calls_total", { provider: providerName(e.provider ?? "unknown"), model: modelId(e.model) });
|
|
82
86
|
if (e.costMicroUsd) {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* 在 core 那边没有任何期限(core `docs/KNOWN-LIMITS.md`)—— 那一形上本面同样没有期限,补偿只有限速与缓存。
|
|
34
34
|
*/
|
|
35
35
|
import { type McpServerSpec } from "@sema-agent/core";
|
|
36
|
-
import type {
|
|
36
|
+
import type { DeclarationRefusal } from "../request-declaration-refusal.js";
|
|
37
37
|
/**
|
|
38
38
|
* 本面的**拒码闭集**(设计稿 §2 的四枚)。闭集写成型,是为了让 {@link MCP_PROBE_HTTP_STATUS} 那张表
|
|
39
39
|
* 「漏一枚 = 编译红」—— [ref] 的安全轴纪律:词表是闭集,未处置的成员必须是编译错误而不是运行期惊喜。
|
|
@@ -158,8 +158,9 @@ export interface McpProbeRefusal {
|
|
|
158
158
|
readonly message: string;
|
|
159
159
|
/** `request.body_shape` 专用:词表外 / 本面不接线的键,全路径形(已清洗+截断+排序+封顶)。 */
|
|
160
160
|
readonly unknownKeys?: readonly string[];
|
|
161
|
-
/**
|
|
162
|
-
|
|
161
|
+
/** 申报超上限 ⇒ 撞帽机读位一组键(`field` · `reason:"over_cap"` · `count` · `cap`);申报的 `transport.url` 带凭据 ⇒ `field` + `reason` ——
|
|
162
|
+
* 与提交腿同一只回答者交回的同一组机读位,原样转述。 */
|
|
163
|
+
readonly facts?: DeclarationRefusal["facts"];
|
|
163
164
|
}
|
|
164
165
|
/**
|
|
165
166
|
* `POST …/probe` 的体读器:闭形、逐条点名、零静默丢。成功 ⇒ 交给 core 的 `specs`(**原样**转发被读出来的
|
|
@@ -167,7 +168,7 @@ export interface McpProbeRefusal {
|
|
|
167
168
|
*
|
|
168
169
|
* ## 申报「能不能原样用」:问提交腿 fresh 门**同一只**回答者,不另判
|
|
169
170
|
* 本面只判自己那一层(体是对象、顶层只受理 `mcpServers`、这个键必填);`mcpServers` 本身 —— 列表的形、上限、每条的键名与键值 —— 全部交给
|
|
170
|
-
* `task-mcp.ts` 的 `requestMcpDeclarationRefusal`,拒(码 + 句 + `unknownKeys` +
|
|
171
|
+
* `task-mcp.ts` 的 `requestMcpDeclarationRefusal`,拒(码 + 句 + `unknownKeys` + 撞帽 / url 带凭据两形的机读位)原样转述:同一份申报在两扇门上只有一个答案,零拨号之前就给。
|
|
171
172
|
* 回答者受理的申报由**同一只**读器(`validateRequestMcp`)交回,读器的产物就是申报本身(只差判官自己的合法归一),经 `toCoreMcpServerSpec`
|
|
172
173
|
* 得到的就是一条任务提交会交给引擎的那一份 —— 本面拨的正是它。读器只在回答者受理之后才跑,所以它对重放存量 body 才有的降级留痕
|
|
173
174
|
* (`source` 写坏的 F 类丢弃)不会在一次被拒的探测上虚记。
|
|
@@ -23,6 +23,7 @@ import type { ServiceConfig } from "./config.js";
|
|
|
23
23
|
import type { OwnerAwareSessionStore } from "./security.js";
|
|
24
24
|
import type { ServiceFileHistoryStore } from "./plugins/store-backend.js";
|
|
25
25
|
import { resolveStreamApprovalGate, type ApprovalFrameFamily } from "./tool-approval.js";
|
|
26
|
+
import { type SyncBlobHistoryStore } from "./session-sync.js";
|
|
26
27
|
import { type WorkspaceSyncCapability } from "./workspace-space.js";
|
|
27
28
|
import type { WorkspaceCacheCapability } from "./workspace-cache.js";
|
|
28
29
|
/** why 闭集:词 → 给运维看的一句义(诊断 / 测试读它;wire 上的拒句由各门自写)。 */
|
|
@@ -171,13 +172,8 @@ export declare function sessionSync(d: Pick<Deps, "backend" | "sessionStorage" |
|
|
|
171
172
|
sessionStorage: SyncSessionStore;
|
|
172
173
|
historyStore: ServiceFileHistoryStore;
|
|
173
174
|
}>;
|
|
174
|
-
/** blob
|
|
175
|
-
|
|
176
|
-
readonly syncBlobFaces: true;
|
|
177
|
-
} & {
|
|
178
|
-
[P in "putBlob" | "getBlob"]-?: NonNullable<ServiceFileHistoryStore[P]>;
|
|
179
|
-
};
|
|
180
|
-
/** 会话同步的**文件历史半族**:对话半族 ∧ 文件历史店有 blob 搬运面(只有 SQL 两只店有)。 */
|
|
175
|
+
/** 会话同步的**文件历史半族**:对话半族 ∧ 文件历史店有 blob 搬运面(只有 SQL 两只店有)。「店有没有 blob 两面」只在 `syncBlobFacesOf` 判一次(marker 门,不做名字探测),
|
|
176
|
+
* blob 取值口 `blobGetterOf` 读的是同一只 —— 本事实为真 ⟺ 取值口通到 `historyStore.getBlob`。 */
|
|
181
177
|
export declare function sessionSyncFileHistory(d: Pick<Deps, "backend" | "sessionStorage" | "fileHistoryStore" | "sessionPolicyStore">): Fact<SessionSyncWhy | "sync_blob_faces_absent", {
|
|
182
178
|
historyStore: SyncBlobHistoryStore;
|
|
183
179
|
}>;
|
package/dist/capability-facts.js
CHANGED
|
@@ -4,7 +4,7 @@ import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
|
|
|
4
4
|
import { presentedApprovalFrameFamily, resolveStreamApprovalGate } from "./tool-approval.js";
|
|
5
5
|
import { selfOrchestrationDenial } from "./task-workflow.js";
|
|
6
6
|
import { centerEntitlementSourceWired } from "./runtime-caps-resolver.js";
|
|
7
|
-
import { overwriteWipeIncapable } from "./session-sync.js";
|
|
7
|
+
import { overwriteWipeIncapable, syncBlobFacesOf } from "./session-sync.js";
|
|
8
8
|
import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, workspaceSnapshotTimeoutOf } from "./workspace-space.js";
|
|
9
9
|
import { headReadOf } from "./session-incarnation.js";
|
|
10
10
|
export const CAPABILITY_FACT_WHY = {
|
|
@@ -172,10 +172,8 @@ export function sessionSyncFileHistory(d) {
|
|
|
172
172
|
const sync = sessionSync(d);
|
|
173
173
|
if (!sync.available)
|
|
174
174
|
return sync;
|
|
175
|
-
const
|
|
176
|
-
return
|
|
177
|
-
? yes({ historyStore: fs })
|
|
178
|
-
: no("sync_blob_faces_absent");
|
|
175
|
+
const faces = syncBlobFacesOf(sync.historyStore);
|
|
176
|
+
return faces !== undefined ? yes({ historyStore: faces }) : no("sync_blob_faces_absent");
|
|
179
177
|
}
|
|
180
178
|
export function checkpointPark(d) {
|
|
181
179
|
return d.checkpointStore !== undefined ? yes({ checkpointStore: d.checkpointStore }) : no("checkpoint_store_absent");
|
|
@@ -97,9 +97,6 @@ export type CenterCredentialSourceSpec = Exclude<WebAdminCredentialDeclaration,
|
|
|
97
97
|
* 路径(之后 cwd 变了也读同一个文件 —— 纯契约不碰 `node:path`,这一步归读方)。
|
|
98
98
|
*/
|
|
99
99
|
export declare function readCenterCredentialDeclaration(env?: Readonly<Record<string, string | undefined>>): WebAdminCredentialDeclaration;
|
|
100
|
-
/** 拉取令牌形的身份 = worker(拉取令牌代表 worker,不是人)。种类判别与 formatVersion 1 LKG 的一次性迁移
|
|
101
|
-
* (`config-lkg.ts` `migrateV1Lkg`:v1 信封只记 worker,只有这一形的身份推得出来)共用这一只。 */
|
|
102
|
-
export declare function pullTokenIdentity(worker: string | undefined): string;
|
|
103
100
|
/**
|
|
104
101
|
* 凭证来源工厂(活对象:文件形每次取值读盘并记健康态)。`worker` = `SEMA_WEB_ADMIN_WORKER`(拉取令牌形的身份)。
|
|
105
102
|
* `readFile` / `now` 只为测试注入。
|
|
@@ -161,6 +158,23 @@ export declare function answerStillCurrent(source: CenterCredentialSource, usedI
|
|
|
161
158
|
* 中心答复;缓存 / 在飞 / 条件请求的校验子全在箱里,换了人结构上够不着上一个人的。
|
|
162
159
|
* · 请求发出之后才换人:答复到达时由 {@link answerStillCurrent} 判丢弃(请求装配侧,见 http-client.ts)。
|
|
163
160
|
* 旧身份的箱不淘汰(量级 = 进程生命期内出现过的身份数):换回来的身份在 TTL 内照常命中它自己的缓存。
|
|
161
|
+
* 取箱口两只({@link IdentityBins}):`now`(此刻身份)与 `accounting`(记账身份:执行开始时钉下的那个,[ref])。
|
|
164
162
|
*/
|
|
165
|
-
export declare function createIdentityBins<T>(source: CenterCredentialSource, make: (pinned: CenterCredentialSource) => T):
|
|
163
|
+
export declare function createIdentityBins<T>(source: CenterCredentialSource, make: (pinned: CenterCredentialSource) => T): IdentityBins<T>;
|
|
164
|
+
/**
|
|
165
|
+
* 同一只分箱规则的两个取箱口(箱表、钉住的凭证、惰建都只有一份):
|
|
166
|
+
* · `now` —— 此刻凭证身份的箱(缓存 / 在飞去重 / 用量环上报这一类「此刻是谁就替谁办」的读者);
|
|
167
|
+
* · `accounting` —— **记账身份**的箱([ref]:花费扣减、用量记账):这次执行开始时钉下的身份({@link pinAccountingIdentity});不在执行里 / 没钉 ⇒ 同 `now`。
|
|
168
|
+
* 在飞调用完成时凭证换了人或暂时读不到,都不改它记在谁的箱里。
|
|
169
|
+
*/
|
|
170
|
+
export interface IdentityBins<T> {
|
|
171
|
+
now(): T;
|
|
172
|
+
accounting(): T;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* [ref] —— 把这次执行的**记账身份**钉在此刻(执行的第一帧 `task.start` 由 tracer 调;每个凭证来源每次执行至多钉一次,先到为准 ——
|
|
176
|
+
* 同一执行里子任务的 `task.start` 不改它)。此刻身份未知(凭证读不到)⇒ **不钉**:不把这次执行记到「身份未知」的无主箱,它的记账按记账那一刻的身份。
|
|
177
|
+
* 不在执行作用域里(`withPrincipal` 之外)⇒ 无动作。
|
|
178
|
+
*/
|
|
179
|
+
export declare function pinAccountingIdentity(source: CenterCredentialSource): void;
|
|
166
180
|
//# sourceMappingURL=center-credential.d.ts.map
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { resolve as resolvePath } from "node:path";
|
|
4
4
|
import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
5
|
+
import { currentExecutionPins } from "./observability/principal-context.js";
|
|
5
6
|
import { WEB_ADMIN_ENV, judgeWebAdminCredentialDeclaration, parseWebAdminCredentialFile } from "@sema-agent/settings-schema";
|
|
6
7
|
export class CenterCredentialUnavailableError extends Error {
|
|
7
8
|
reason;
|
|
@@ -37,7 +38,7 @@ function classify(bearer, worker) {
|
|
|
37
38
|
return { ok: true, kind: "user-jwt", bearer, identity: userTokenIdentity(bearer) };
|
|
38
39
|
return { ok: true, kind: "pull-token", bearer, identity: pullTokenIdentity(worker), workerScoped: bearer.startsWith("wpt_") };
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
+
function pullTokenIdentity(worker) {
|
|
41
42
|
return `pull-token:${worker ?? "global"}`;
|
|
42
43
|
}
|
|
43
44
|
const NONE = { ok: true, kind: "none", identity: "none" };
|
|
@@ -178,14 +179,28 @@ export function answerStillCurrent(source, usedIdentity) {
|
|
|
178
179
|
}
|
|
179
180
|
export function createIdentityBins(source, make) {
|
|
180
181
|
const bins = new Map();
|
|
181
|
-
|
|
182
|
-
const identity = credentialIdentityNow(source);
|
|
182
|
+
const binOf = (identity) => {
|
|
183
183
|
if (bins.has(identity))
|
|
184
184
|
return bins.get(identity);
|
|
185
185
|
const made = make(pinCredential(source, identity));
|
|
186
186
|
bins.set(identity, made);
|
|
187
187
|
return made;
|
|
188
188
|
};
|
|
189
|
+
return {
|
|
190
|
+
now: () => binOf(credentialIdentityNow(source)),
|
|
191
|
+
accounting: () => binOf(accountingIdentityOf(source)),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
export function pinAccountingIdentity(source) {
|
|
195
|
+
const pins = currentExecutionPins();
|
|
196
|
+
if (pins === undefined || pins.has(source))
|
|
197
|
+
return;
|
|
198
|
+
const identity = credentialIdentityNow(source);
|
|
199
|
+
if (identity !== undefined)
|
|
200
|
+
pins.set(source, identity);
|
|
201
|
+
}
|
|
202
|
+
function accountingIdentityOf(source) {
|
|
203
|
+
return currentExecutionPins()?.get(source) ?? credentialIdentityNow(source);
|
|
189
204
|
}
|
|
190
205
|
function pinCredential(source, identity) {
|
|
191
206
|
return {
|
package/dist/config-catalog.js
CHANGED
|
@@ -4,7 +4,8 @@ import { WEB_ADMIN_ENV, DOMAIN_SCHEMAS, buildRetiredEnvWire } from "@sema-agent/
|
|
|
4
4
|
import { CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS, CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS, CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS } from "./center-credential-renewer.js";
|
|
5
5
|
import { SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS } from "./sandbox-egress-policy.js";
|
|
6
6
|
import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE_SIZE_DEFAULT, WORKSPACE_CACHE_WORDS } from "./workspace-cache.js";
|
|
7
|
-
import {
|
|
7
|
+
import { SESSION_SNAPSHOT_BUCKET_DEFAULT, WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "./object-store-buckets.js";
|
|
8
|
+
import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE } from "./workspace-space.js";
|
|
8
9
|
import { APPROVAL_ASK_RETENTION_DEFAULT_MS, APPROVAL_ASK_RETENTION_FLOOR_MS } from "./approval-ask-retention.js";
|
|
9
10
|
import { LIVENESS_MS_DEFAULT } from "./exec-bounds.js";
|
|
10
11
|
import { THINKING_FORMATS } from "./model-compat.js";
|
|
@@ -60,8 +61,8 @@ export const CONFIG_CATALOG = [
|
|
|
60
61
|
r("ADB_SERIAL", "orchestration", "string", "adb 车道目标设备 serial(REMOTE_EXEC=adb 的必需件,缺 ⇒ 拒启)"),
|
|
61
62
|
r("AGENT_DATA_DIR", "store", "path", "本机数据根回落链一环(LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent);记忆引擎缺省根同链", { danger: DATA, derivedDefaultNote: "缺席 ⇒ 链上下一环,最终 ~/.ai-agent" }),
|
|
62
63
|
r("ALLOW_UNAUTHED_WRITES", "auth", "boolean", "无 service token 时仍接受任务提交(本地开发逃生口;缺省 fail-closed 拒提交)", { staticDefault: "false", danger: SEC, configKey: "allowUnauthedWrites" }),
|
|
63
|
-
r("ANTHROPIC_API_KEY", "modelPlane", "secret", "Anthropic 协议路线的 x-api-key 凭据(
|
|
64
|
-
r("ANTHROPIC_AUTH_TOKEN", "modelPlane", "secret", "Anthropic 兼容生态的 Bearer 凭据(
|
|
64
|
+
r("ANTHROPIC_API_KEY", "modelPlane", "secret", "Anthropic 协议路线的 x-api-key 凭据(只发 x-api-key 头;与 ANTHROPIC_AUTH_TOKEN 互斥 —— 同设拒启,机读码 `config.anthropic_credential_conflict`;与 BASE_URL 同在时可推断 provider)", { danger: [...SEC, ...BILL] }),
|
|
65
|
+
r("ANTHROPIC_AUTH_TOKEN", "modelPlane", "secret", "Anthropic 兼容生态的 Bearer 凭据(只发 Authorization: Bearer 头;与 ANTHROPIC_API_KEY 互斥 —— 同设拒启,机读码 `config.anthropic_credential_conflict`)", { danger: [...SEC, ...BILL] }),
|
|
65
66
|
r("ANTHROPIC_BASE_URL", "modelPlane", "url", "Anthropic 协议路线 base URL(与凭据同在且 MODEL_PROVIDER 未设 ⇒ 推断 provider=anthropic 并 boot notice;S-475:带 userinfo 拒启,机读码 `config.gateway_url_userinfo`;S-483:解析不动/不透明形拒启,机读码 `config.model_route_url_invalid`;S-514:带 `#fragment` 拒启,机读码 `config.model_route_url_fragment` —— fragment 从不上线,后缀会落进被丢弃的那一半)"),
|
|
66
67
|
r("ANTHROPIC_CACHE_BREAKPOINTS", "modelPlane", "boolean", "Anthropic 路线 prompt-cache breakpoints 开关", { staticDefault: "true" }),
|
|
67
68
|
r("ANTHROPIC_MAX_RETRIES", "modelPlane", "number", "Anthropic 路线 SDK 每次调用重试上限", { derivedDefaultNote: "缺席 ⇒ 不传引擎,core/SDK 默认当家" }),
|
|
@@ -236,10 +237,10 @@ export const CONFIG_CATALOG = [
|
|
|
236
237
|
r("MAX_TASK_COST_USD", "limitsHttp", "number", "单任务成本天花板(0=关)", { staticDefault: "0", danger: BILL, configKey: "maxTaskCostUsd", center: cLimits("maxTaskCostUsd") }),
|
|
237
238
|
r("MAX_TASK_TOKENS", "limitsHttp", "number", "单任务 token 天花板(per-slice 窗;0=关)", { staticDefault: "0", danger: BILL, configKey: "maxTaskTokens", center: cLimits("maxTaskTokens") }),
|
|
238
239
|
r("MCP_ELICITATION_ENABLED", "approval", "boolean", "入站 MCP elicitation HITL(per-server opt-in 另有一道)", { staticDefault: "false", danger: SEC }),
|
|
239
|
-
r("MCP_ELICITATION_MAX_CONCURRENT", "approval", "number", "elicitation 每 run 并发帽", { derivedDefaultNote: "缺席 ⇒
|
|
240
|
-
r("MCP_ELICITATION_MAX_TOTAL", "approval", "number", "elicitation 每 run 总量帽", { derivedDefaultNote: "缺席 ⇒
|
|
241
|
-
r("MCP_ELICITATION_MIN_INTERVAL_MS", "approval", "number", "elicitation per-server 最小间隔(ms)", { derivedDefaultNote: "缺席 ⇒
|
|
242
|
-
r("MCP_ELICITATION_TTL_MS", "approval", "number", "elicitation 无人应答释放窗(ms)", { derivedDefaultNote: "缺席 ⇒
|
|
240
|
+
r("MCP_ELICITATION_MAX_CONCURRENT", "approval", "number", "elicitation 每 run 并发帽", { derivedDefaultNote: "缺席 ⇒ 本服务出厂窗(DEFAULT_ELICITATION_THROTTLE)" }),
|
|
241
|
+
r("MCP_ELICITATION_MAX_TOTAL", "approval", "number", "elicitation 每 run 总量帽", { derivedDefaultNote: "缺席 ⇒ 本服务出厂窗(DEFAULT_ELICITATION_THROTTLE)" }),
|
|
242
|
+
r("MCP_ELICITATION_MIN_INTERVAL_MS", "approval", "number", "elicitation per-server 最小间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 本服务出厂窗(DEFAULT_ELICITATION_THROTTLE)" }),
|
|
243
|
+
r("MCP_ELICITATION_TTL_MS", "approval", "number", "elicitation 无人应答释放窗(ms)", { derivedDefaultNote: "缺席 ⇒ 本服务出厂窗(DEFAULT_ELICITATION_THROTTLE)" }),
|
|
243
244
|
r("MEMORY_AUTO_CONSOLIDATION", "memory", "enum", "记忆自动整理部署席(on=每个建议任务后整库整理跑,记忆内容自动外流到整理模型;off=显式关,胜过引擎缺省;未设=读引擎具名缺省)", { enumValues: ["on", "off"], derivedDefaultNote: "缺席 ⇒ 引擎具名缺省(**本版装的引擎是开**)——不配不等于关;前置=整理阀门开着(MEMORY_CONSOLIDATION_DRIVER=on,或未设而出厂缺省派生成开)", danger: SEC, resolve: (c) => c.memoryConsolidationDriver.autoRunOnRecommendation, booleanEnum: { true: "on", false: "off" } }),
|
|
244
245
|
r("MEMORY_CAPTURE_POLICY", "memory", "enum", "记忆 capture 姿态(open=声明按面值;governed=按 per-principal verdict;capture-required=恒采)", { enumValues: ["open", "governed", "capture-required"], staticDefault: "open", danger: SEC, configKey: "memoryCapturePolicy" }),
|
|
245
246
|
r("MEMORY_CONSOLIDATION_DRIVER", "memory", "enum", "记忆折叠阀门(off=零装配零探针,两 admin 口 404 族;on=座解析 + 两 admin 口 + 引擎面整理两席)", { enumValues: ["on", "off"], derivedDefaultNote: "缺席 ⇒ 起服时派生:整理座不落主模型(MODEL_CHEAP_ID 在场且异于 MODEL_ID,或目录给 consolidate / summarize / flash 档位绑了别的模型)⇒ on;落主模型 ⇒ off + 起服 warn memory_consolidation_default_off_no_cheap_model;结构上跑不起来(无记忆引擎 / pg·tidb 记忆后端 / MEMORY_PROVENANCE=off)⇒ 恒 off 零提示。显式 on 而落主模型 ⇒ 照开 + warn memory_consolidation_on_main_model(按主模型计费)", danger: BILL, resolve: (_c, _env, boot) => (boot.memoryConsolidationValve === undefined ? undefined : boot.memoryConsolidationValve.enabled ? "on" : "off") }),
|
|
@@ -416,6 +417,7 @@ export const CONFIG_CATALOG = [
|
|
|
416
417
|
r("SCHEDULER_SESSION_WAKEUP", "orchestration", "boolean", "session 唤醒 host-signal(无常驻 daemon 的壳注 false)", { staticDefault: "true", configKey: "schedulerSessionWakeup" }),
|
|
417
418
|
r("SCHEDULER_STORE_PATH", "orchestration", "path", "scheduler 共享店路径(server 写者与壳 daemon 读者必须一致)", { derivedDefaultNote: "缺席 ⇒ SEMA_CONFIG_DIR(~/.sema)/scheduled_tasks.json", configKey: "schedulerStorePath" }),
|
|
418
419
|
r("SCRATCHPAD_SWEEP_TTL_MS", "limitsHttp", "number", "per-session scratchpad 目录 sweep TTL(ms;≤0=关,<1m 夹到 1m 点名)", { staticDefault: String(7 * 24 * 60 * 60 * 1000), danger: DATA, configKey: "scratchpadSweepTtlMs" }),
|
|
420
|
+
r("SEALED_KEY_DIR", "store", "path", "模型钥匙保管目录(sealed 形的 X25519 钥匙对 `<publicKeyId>.json` 与停驻行封签子目录 `checkpoint-ctx-keys/`;文件 0600、目录 0700)。sealed 条目绑定钥匙身份(publicKeyId),共享同一份 config.d 的每台引擎都须持钥:数据根不同的多台引擎指向同一目录 ⇒ 同一把钥。绝对路径(相对路径拒启);设了 ⇒ 唯一保管目录,起服在它上面建不了保管店 ⇒ 拒启", { danger: [...SEC, ...DATA], derivedDefaultNote: "缺席 ⇒ <数据根>/sealed-key(建不了只告警,sealed 模型逐个 no_key_store)", configKey: "sealedKeyDir" }),
|
|
419
421
|
r("SELECT_ENVIRONMENT_TOOL", "orchestration", "boolean", "SelectEnvironment 工具挂载(另需 k8s+镜像目录活链)", { staticDefault: "true", configKey: "selectEnvironmentTool" }),
|
|
420
422
|
r("SELF_ORCHESTRATION_ENABLED", "orchestration", "boolean", "run_workflow 自编排(硬化 vm 沙箱)", { derivedDefaultNote: "posture 三态:缺席 = 单用户 ⇒ on / 多租户 ⇒ off", configKey: "selfOrchestrationEnabled" }),
|
|
421
423
|
r("SELF_ORCHESTRATION_MODELS", "orchestration", "csv", "workflow agent({modelName}) 显式词表(恒赢;缺席单用户=目录键全集/多租户 fail-closed)", { configKey: "selfOrchestrationModels" }),
|
|
@@ -449,7 +451,7 @@ export const CONFIG_CATALOG = [
|
|
|
449
451
|
r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
|
|
450
452
|
r("SESSION_EVENTS_MAX_PROBES", "limitsHttp", "number", "session SSE 共享探针数帽", { derivedDefaultNote: "缺席 ⇒ 512" }),
|
|
451
453
|
r("SESSION_EVENTS_WARM_MS", "limitsHttp", "number", "session SSE 温探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 2000" }),
|
|
452
|
-
r("SESSION_SNAPSHOT_BUCKET", "store", "string", "快照 blob 桶(兼 SendUserFile 私桶)", { derivedDefaultNote:
|
|
454
|
+
r("SESSION_SNAPSHOT_BUCKET", "store", "string", "快照 blob 桶(兼 SendUserFile 私桶)", { derivedDefaultNote: `缺席 ⇒ ${SESSION_SNAPSHOT_BUCKET_DEFAULT}` }),
|
|
453
455
|
r("SESSION_SNAPSHOT_PREFIX", "store", "string", "快照 blob key 前缀", { derivedDefaultNote: "缺席 ⇒ blobs/(MinioBlobBackend 默认)" }),
|
|
454
456
|
r("SESSION_SNAPSHOT_TTL_SEC", "store", "number", "快照 presign TTL(秒;坏值省略走默认,绝不 NaN 签名)", { derivedDefaultNote: "缺席 ⇒ 3600(MinioBlobBackend 默认)" }),
|
|
455
457
|
r("SIGHUP_IDLE_GRACE_MS", "limitsHttp", "number", "SIGHUP 空闲关停窗(ms;共享引擎形防误杀)", { staticDefault: "120000", configKey: "sighupIdleGraceMs" }),
|
|
@@ -509,10 +511,10 @@ export const CONFIG_CATALOG = [
|
|
|
509
511
|
r("WORKSPACE_CACHE_SIZE", "orchestration", "string", "k8s 构建缓存卷的大小(PVC 申请量,k8s quantity 形如 20Gi / 500Mi;形不合拒启;只在 WORKSPACE_CACHE=volume 时读)", { staticDefault: WORKSPACE_CACHE_SIZE_DEFAULT }),
|
|
510
512
|
r("WORKSPACE_FILE_MAX_BYTES", "limitsHttp", "number", "已随 workspace 浏览面退役(S-15):值照常校验/落值,应用于空(重启也不生效)", { envTiming: "inert", configKey: "workspaceFileMaxBytes", center: cLimits("workspaceFileMaxBytes", { timing: "inert" }) }),
|
|
511
513
|
r("WORKSPACE_PERSIST", "orchestration", "enum", "k8s 工作区跨轮保留:space(缺省)= 每条 run 结束把 /workspace 快照进本会话住客的空间、下一条 run 开始恢复最近一份(上一条还在保存 ⇒ 下一条等它存完,最多等一个上传时限,见 USAGE §9.3b);off = 每轮新建。space(含缺省)却没配快照存储(MINIO_* 三键)⇒ 不生效,起服一行 workspace_persist_unavailable 点名缺键;前提:会话店读得出会话住客(local / SQL 会话店),内存会话店 × space(含缺省,且配了快照存储)⇒ 拒启(出路 WORKSPACE_PERSIST=off);词表外拒启", { enumValues: WORKSPACE_PERSIST_WORDS, staticDefault: WORKSPACE_PERSIST_DEFAULT, danger: DATA }),
|
|
512
|
-
r("WORKSPACE_SNAPSHOT_BUCKET", "orchestration", "string", "k8s 工作区快照桶", { derivedDefaultNote:
|
|
514
|
+
r("WORKSPACE_SNAPSHOT_BUCKET", "orchestration", "string", "k8s 工作区快照桶(server 不建桶:配了对象存储时起服核它在不在,缺〔404 + NoSuchBucket〕⇒ 窗内重试、到窗拒启并点名本旋钮;证明不了〔连不上 / 403 / 5xx〕不拒,走 fail-open 兜底口;运行期读 latest 得 NoSuchBucket ⇒ 该 run 连沙箱失败,不读成空间头一回)", { derivedDefaultNote: `缺席 ⇒ ${WORKSPACE_SNAPSHOT_BUCKET_DEFAULT}` }),
|
|
513
515
|
r("WORKSPACE_SNAPSHOT_EXCLUDE", "orchestration", "csv", "k8s 工作区快照的排除表(相对工作区根的 tar 排除模式,逗号分隔,如 build,.gradle,node_modules/.cache;每项一个 --exclude=./<项>,run 末快照与停驻挂起同一张表 —— 排除的目录恢复后不在,构建产物本就可再生;项形闭:相对路径段,不含 .. / 前导 / / 空白 / 引号,整体通配(只由 * / ? 组成的项,* 会让快照一个顶层项都不剩)也拒,坏项拒启并点名第几项;不读项目 .gitignore)", { derivedDefaultNote: "缺席 = 不排除(上传命令与旧版逐字同)" }),
|
|
514
516
|
r("WORKSPACE_SNAPSHOT_KEEP", "orchestration", "number", `k8s 工作区每个空间(会话住客)保留的快照份数(整数 [${WORKSPACE_SNAPSHOT_KEEP_RANGE.min}, ${WORKSPACE_SNAPSHOT_KEEP_RANGE.max}];写新快照后删最老的;域外拒启)`, { staticDefault: String(WORKSPACE_SNAPSHOT_KEEP_DEFAULT), danger: DATA }),
|
|
515
|
-
r("WORKSPACE_SNAPSHOT_PREFIX", "orchestration", "string", "k8s 工作区快照 key
|
|
517
|
+
r("WORKSPACE_SNAPSHOT_PREFIX", "orchestration", "string", "k8s 工作区快照 key 前缀:`/` 分隔的一段或多段普通 key 段(kata、libA/ws;每段以字母或数字开头,其后字母数字 . _ -);空段 / . / .. / 前导或尾随 / / 其它字符 ⇒ 拒启并点名第几段(config.workspace_snapshot_prefix_invalid)", { derivedDefaultNote: `缺席 ⇒ ${WORKSPACE_SNAPSHOT_PREFIX_DEFAULT}` }),
|
|
516
518
|
r("WORKSPACE_SNAPSHOT_TIMEOUT_MS", "orchestration", "number", `k8s 工作区快照的时限(毫秒,整数 [${WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.min}, ${WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.max}];域外拒启):一个数管四处 —— run 末上传与连沙箱时恢复的墙钟上界、空间结束时回收座一次调用的绝对截止(卷与对象都在内)、下一条 run 等上一条存完的最长等待(提交围栏的存活上界);大文件读写的墙钟也是它。大工作区(构建目录 GB 级)撞上界 ⇒ 调大它或配 WORKSPACE_SNAPSHOT_EXCLUDE`, { staticDefault: String(WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT) }),
|
|
517
519
|
r("WORKSPACE_SNAPSHOT_TTL_SEC", "orchestration", "number", "k8s 工作区快照 presign TTL(秒)"),
|
|
518
520
|
r("WORKTREE_ALLOWED_ROOTS", "orchestration", "csv", "worktree 隔离纵深白名单(缺省 = [repoRoot])", { danger: SEC }),
|
|
@@ -19,7 +19,7 @@ export async function migrateLocalConfigDirectory(root) {
|
|
|
19
19
|
return { ...(await migrateConfigDirectory(dir, { documents: LOCAL_READER_DOCUMENTS, io: nodeConfigDirectoryIo })), dir };
|
|
20
20
|
}
|
|
21
21
|
catch (err) {
|
|
22
|
-
return { kind: "threw", dir, reason:
|
|
22
|
+
return { kind: "threw", dir, reason: err instanceof Error ? err.message : String(err) };
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
export function logConfigDirMigration(log, m) {
|
|
@@ -34,7 +34,7 @@ import type { ServiceConfigFlat } from "./config-types.js";
|
|
|
34
34
|
*/
|
|
35
35
|
export declare const LIVENESS_HEARTBEAT_MS = 30000;
|
|
36
36
|
/** 闭集:新增一条不变量必须在这里加词(拼错 = 编译红,而不是运行期悄悄不判)。 */
|
|
37
|
-
export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "operator-single-user" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "require-execution-lane" | "center-one-identity" | "workflow-park-decision-domain";
|
|
37
|
+
export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "operator-single-user" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "anthropic-credential-exclusive" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "require-execution-lane" | "center-one-identity" | "workflow-park-decision-domain";
|
|
38
38
|
/** 表读的键集(取自平铺配置型 ⇒ 键名/类型永不与真配置漂)。 */
|
|
39
39
|
export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "operatorSingleUser" | "sensitiveWritePatterns" | "leaderEnabled" | "selfOrchestrationEnabled" | "workflowRunStoreBackend" | "remoteExec" | "requireExecutionLane" | "dbBackend" | "tidb" | "pg" | "envModel" | "gatewayBaseUrl" | "gatewayFallbackUrls" | "anthropic" | "models" | "modelRefusals" | "tiers" | "authBridgeIssuer" | "webAdmin">>;
|
|
40
40
|
export interface CrossInvariantInput {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertGuardPatternsUsable } from "./deployment-governance.js";
|
|
2
2
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
3
3
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
4
|
+
import { anthropicCredentialRefusal } from "./model-route-gap.js";
|
|
4
5
|
import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
|
|
5
6
|
import { defaultModelRefusal, refusedNameMintedRefusal } from "./model-entry-refusal.js";
|
|
6
7
|
import { WEB_ADMIN_ENV, httpUrlFlaws } from "@sema-agent/settings-schema";
|
|
@@ -261,6 +262,13 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
261
262
|
.find((r) => r !== undefined) ??
|
|
262
263
|
modelRouteUrlRefusal("ANTHROPIC_BASE_URL", next.anthropic?.baseUrl ?? "", "ANTHROPIC_API_KEY"),
|
|
263
264
|
},
|
|
265
|
+
{
|
|
266
|
+
id: "anthropic-credential-exclusive",
|
|
267
|
+
keys: ["anthropic"],
|
|
268
|
+
restartReason: "anthropic-credential",
|
|
269
|
+
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:anthropic-credential-exclusive" },
|
|
270
|
+
check: ({ next }) => (next.anthropic === undefined ? undefined : anthropicCredentialRefusal(next.anthropic)),
|
|
271
|
+
},
|
|
264
272
|
{
|
|
265
273
|
id: "model-route-endpoint",
|
|
266
274
|
keys: ["gatewayBaseUrl", "gatewayFallbackUrls", "anthropic", "models"],
|
package/dist/config-lkg.d.ts
CHANGED
|
@@ -82,42 +82,4 @@ export declare function migrateLkgPayload(effective: Record<string, unknown>): {
|
|
|
82
82
|
} | {
|
|
83
83
|
refused: string;
|
|
84
84
|
};
|
|
85
|
-
/** 7.98.0 及以前的默认 LKG 路径(按当时的写法逐字保留 —— 这是盘上已有文件的名字,不是现行分箱规则)。 */
|
|
86
|
-
export declare function legacyV1LkgPath(config: {
|
|
87
|
-
readonly agentConfigDir?: string;
|
|
88
|
-
}, worker: string | undefined): string;
|
|
89
|
-
export type LegacyLkgMigration = {
|
|
90
|
-
readonly outcome: "none";
|
|
91
|
-
}
|
|
92
|
-
/** `retireError`:新箱已写好,只是旧文件没退役成(改名 / 留副本失败)—— 只响一行,不影响已写好的箱。 */
|
|
93
|
-
| {
|
|
94
|
-
readonly outcome: "migrated";
|
|
95
|
-
readonly from: string;
|
|
96
|
-
readonly retiredTo: string;
|
|
97
|
-
readonly retireError?: string;
|
|
98
|
-
} | {
|
|
99
|
-
readonly outcome: "not-migrated";
|
|
100
|
-
readonly path: string;
|
|
101
|
-
readonly reason: string;
|
|
102
|
-
};
|
|
103
|
-
/**
|
|
104
|
-
* formatVersion 1 LKG 的一次性迁移(规则一句:**身份推得出来才迁,推不出来就响亮拒**)。判序:
|
|
105
|
-
* 1. `legacyPath` ≠ `binPath` 时**先看本身份的箱**:箱里已有文件 ⇒ `none`(箱自己说话,旧文件是好是坏都不再相关,
|
|
106
|
-
* 不许一个坏旧文件把有效的箱挡成 env 兜底 —— 7.99.0 修复验证 N4)。箱查不了(非 ENOENT)⇒ 响亮拒。
|
|
107
|
-
* 2. 旧文件没有(ENOENT / ENOTDIR)或不是 formatVersion 1 信封 ⇒ `none`。旧默认路径上却读不了 / 不是 JSON ⇒
|
|
108
|
-
* `not-migrated`(响亮,不当它不存在);`CONFIG_LKG_PATH` 同路径时这两形由 loadLkg 自己报(不重复打一行)。
|
|
109
|
-
* 3. v1 信封只记 worker:唯一推得出身份的形是拉取令牌(身份 = worker,`pullTokenIdentity`)。此刻身份正是它、worker
|
|
110
|
-
* 对得上、载荷过 {@link migrateLkgPayload}(迁成 11 的形;迁不动 ⇒ 响亮拒、不写箱)与 {@link lkgPayloadJudged} ⇒ **先写后退役**(修复验证 N9):先按新格式把它写进 `binPath`(原子写,
|
|
111
|
-
* 0600),写成了才把旧文件改名 `<legacyPath>.migrated-v1`。写失败 ⇒ `not-migrated`,旧文件原样留在原位,下一次
|
|
112
|
-
* 照常可迁;写成之后改名失败 ⇒ 仍是 `migrated`(`retireError` 响一行),箱已在 ⇒ 下一次按判序 1 不再看旧文件。
|
|
113
|
-
* 同路径(`CONFIG_LKG_PATH`)时新箱的写入会原子替换旧文件本身,所以旧字节在写之前留一份 `.migrated-v1` 副本
|
|
114
|
-
* (副本失败同样只进 `retireError`:载荷正要写进新箱,不是丢数据)。
|
|
115
|
-
* 4. 其余一律 `not-migrated`:拒因点名旧文件路径与出路(删掉它;中心下一份干净候选会写本身份自己的 LKG),不改名、不读回。
|
|
116
|
-
*/
|
|
117
|
-
export declare function migrateV1Lkg(o: {
|
|
118
|
-
legacyPath: string;
|
|
119
|
-
binPath: string;
|
|
120
|
-
worker: string | undefined;
|
|
121
|
-
identity: string;
|
|
122
|
-
}): Promise<LegacyLkgMigration>;
|
|
123
85
|
//# sourceMappingURL=config-lkg.d.ts.map
|
package/dist/config-lkg.js
CHANGED
|
@@ -4,7 +4,7 @@ import { formatConfigPath } from "@sema-agent/settings-schema";
|
|
|
4
4
|
import { join, dirname, resolve } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { judgeConfigCandidate } from "./web-admin-client/candidate-gate.js";
|
|
7
|
-
import { centerStateBin
|
|
7
|
+
import { centerStateBin } from "./center-credential.js";
|
|
8
8
|
import { configRootOf } from "./sema-config-dir.js";
|
|
9
9
|
export function defaultSkillCacheDir(config) {
|
|
10
10
|
return join(configRootOf(config), "skill-cache");
|
|
@@ -167,86 +167,4 @@ function lkgPayloadShapeProblem(effective, etag) {
|
|
|
167
167
|
}
|
|
168
168
|
return undefined;
|
|
169
169
|
}
|
|
170
|
-
export function legacyV1LkgPath(config, worker) {
|
|
171
|
-
const key = (worker ?? "global").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64);
|
|
172
|
-
return join(configRootOf(config), `config-lkg-${key}.json`);
|
|
173
|
-
}
|
|
174
|
-
export async function migrateV1Lkg(o) {
|
|
175
|
-
const refuse = (why) => ({
|
|
176
|
-
outcome: "not-migrated",
|
|
177
|
-
path: o.legacyPath,
|
|
178
|
-
reason: `pre-identity LKG (formatVersion 1) at ${o.legacyPath} not migrated: ${why}. Way out: delete that file — the next clean candidate from the center writes this credential identity's own LKG (${o.binPath})`,
|
|
179
|
-
});
|
|
180
|
-
const sameFile = o.legacyPath === o.binPath;
|
|
181
|
-
if (!sameFile) {
|
|
182
|
-
try {
|
|
183
|
-
await fs.access(o.binPath);
|
|
184
|
-
return { outcome: "none" };
|
|
185
|
-
}
|
|
186
|
-
catch (err) {
|
|
187
|
-
const code = errnoOf(err);
|
|
188
|
-
if (code !== "ENOENT")
|
|
189
|
-
return refuse(`this credential identity's bin ${o.binPath} could not be checked (${code})`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
let text;
|
|
193
|
-
try {
|
|
194
|
-
text = await fs.readFile(o.legacyPath, "utf8");
|
|
195
|
-
}
|
|
196
|
-
catch (err) {
|
|
197
|
-
const code = errnoOf(err);
|
|
198
|
-
return code === "ENOENT" || code === "ENOTDIR" || sameFile ? { outcome: "none" } : refuse(`it could not be read (${code})`);
|
|
199
|
-
}
|
|
200
|
-
let parsed;
|
|
201
|
-
try {
|
|
202
|
-
parsed = JSON.parse(text);
|
|
203
|
-
}
|
|
204
|
-
catch (err) {
|
|
205
|
-
return sameFile ? { outcome: "none" } : refuse(`it is not valid JSON (${err instanceof Error ? err.name : "parse error"})`);
|
|
206
|
-
}
|
|
207
|
-
if (parsed === null || typeof parsed !== "object" || !("formatVersion" in parsed) || parsed.formatVersion !== 1)
|
|
208
|
-
return { outcome: "none" };
|
|
209
|
-
const v1 = parsed;
|
|
210
|
-
if (v1.worker !== (o.worker ?? "global"))
|
|
211
|
-
return refuse(`it belongs to worker ${JSON.stringify(v1.worker)}`);
|
|
212
|
-
if (o.identity !== pullTokenIdentity(o.worker)) {
|
|
213
|
-
return refuse("its envelope records only the worker, and only a pull-token credential's identity (= the worker) can be derived from that — the current credential is not this worker's pull token, so the file cannot prove whose credential fetched it");
|
|
214
|
-
}
|
|
215
|
-
if (v1.effective === null || typeof v1.effective !== "object" || Array.isArray(v1.effective))
|
|
216
|
-
return refuse("its payload is unusable (bad effective)");
|
|
217
|
-
const payload = migrateLkgPayload(v1.effective);
|
|
218
|
-
if ("refused" in payload)
|
|
219
|
-
return refuse(`its payload cannot be migrated to the current settings form (${payload.refused})`);
|
|
220
|
-
const judged = lkgPayloadJudged(payload.effective, v1.etag);
|
|
221
|
-
if ("problem" in judged)
|
|
222
|
-
return refuse(`its payload is unusable (${judged.problem})`);
|
|
223
|
-
const retiredTo = `${o.legacyPath}.migrated-v1`;
|
|
224
|
-
let retireError;
|
|
225
|
-
if (sameFile) {
|
|
226
|
-
try {
|
|
227
|
-
await fs.copyFile(o.legacyPath, retiredTo);
|
|
228
|
-
}
|
|
229
|
-
catch (err) {
|
|
230
|
-
retireError = `the formatVersion 1 bytes could not be kept as ${retiredTo} (${errnoOf(err)})`;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
try {
|
|
234
|
-
await saveLkg(o.binPath, o.worker, o.identity, payload.effective, undefined);
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
return refuse(`writing it into this credential identity's bin failed (${String(err)}) — the old file is left in place and the migration retries on the next LKG read`);
|
|
238
|
-
}
|
|
239
|
-
if (!sameFile) {
|
|
240
|
-
try {
|
|
241
|
-
await fs.rename(o.legacyPath, retiredTo);
|
|
242
|
-
}
|
|
243
|
-
catch (err) {
|
|
244
|
-
retireError = `the old file could not be renamed to ${retiredTo} (${errnoOf(err)}) — harmless: the bin now exists, so the old file is never read again`;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return { outcome: "migrated", from: o.legacyPath, retiredTo, ...(retireError !== undefined ? { retireError } : {}) };
|
|
248
|
-
}
|
|
249
|
-
function errnoOf(err) {
|
|
250
|
-
return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : "unknown";
|
|
251
|
-
}
|
|
252
170
|
//# sourceMappingURL=config-lkg.js.map
|
package/dist/config-types.d.ts
CHANGED
|
@@ -518,11 +518,12 @@ export interface ServiceConfigFlat {
|
|
|
518
518
|
/** Optional Anthropic Messages API ROUTE (cloud). Selected for models whose `provider` is
|
|
519
519
|
* "anthropic" (routing is by model.provider); the local gateway serves every other provider. */
|
|
520
520
|
anthropic?: {
|
|
521
|
-
/** `x-api-key`
|
|
522
|
-
*
|
|
521
|
+
/** `ANTHROPIC_API_KEY` —— sent as the `x-api-key` header ONLY. One name, one header ([ref]): mutually exclusive with
|
|
522
|
+
* `authToken` — both set refuses boot (`loadConfig`, invariant `anthropic-credential-exclusive`) and refuses brain construction
|
|
523
|
+
* for an embedder-built config. Which header rides is `judgeAnthropicCredential`'s answer (`model-route-gap.ts`). */
|
|
523
524
|
apiKey?: string;
|
|
524
525
|
/** Anthropic-compatible-ecosystem `ANTHROPIC_AUTH_TOKEN` (many such providers use Bearer, not x-api-key):
|
|
525
|
-
* sent as `Authorization: Bearer <token
|
|
526
|
+
* sent as `Authorization: Bearer <token>` ONLY. Mutually exclusive with `apiKey` (see there) — no precedence arm. */
|
|
526
527
|
authToken?: string;
|
|
527
528
|
/** Default https://api.anthropic.com (overridden by model.baseUrl). */
|
|
528
529
|
baseUrl?: string;
|
|
@@ -1091,6 +1092,13 @@ export interface ServiceConfigFlat {
|
|
|
1091
1092
|
* service and a run-local on the same box agree on one data dir (boot-locked single-instance). Only the
|
|
1092
1093
|
* `local` backend reads it. */
|
|
1093
1094
|
localDataRoot?: string;
|
|
1095
|
+
/**
|
|
1096
|
+
* `SEALED_KEY_DIR` —— 模型钥匙保管目录(sealed 形的 X25519 钥匙对 `<publicKeyId>.json`,连同停驻行封签那一域的子目录 `checkpoint-ctx-keys/`)。
|
|
1097
|
+
* **缺席不铸键** ⇒ 保管目录 = `<localDataRoot>/sealed-key`(今天的行为;建不了只是一行 warn,sealed 模型逐个 `no_key_store`)。
|
|
1098
|
+
* 给了 ⇒ 绝对路径(相对路径 loadConfig 拒启),该目录是**唯一**保管目录;起服在它上面建不了保管店 ⇒ 拒启(fail-closed:运维点名了目录)。
|
|
1099
|
+
* 谁需要:读同一份 `config.d` 而数据根不同的多台引擎(终端 × 桌面端默认形)—— sealed 条目绑定的是钥匙身份(`publicKeyId` → 钥匙对),指到同一目录 ⇒ 同一把钥。
|
|
1100
|
+
*/
|
|
1101
|
+
sealedKeyDir?: string;
|
|
1094
1102
|
/**
|
|
1095
1103
|
* [ref] 件B(`RUN_STORE_STRICT_HYDRATE`,普查表第 29 行的**终局格**)—— file 后端的**可选** fail-closed
|
|
1096
1104
|
* 启动门。`true` ⇒ `FileRunStore.hydrate()` 列不出 `runs/` 时**拒启**(error 级遗言带绝对路径 + 异常
|
|
@@ -2255,7 +2263,7 @@ export interface ServiceConfigFlat {
|
|
|
2255
2263
|
configLocalOwnerUid?: number;
|
|
2256
2264
|
}
|
|
2257
2265
|
/** 组:store(持久化)—— DB 引擎三态、session 后端、SQL coords、快照 BLOB / SendUserFile 对象存储。 */
|
|
2258
|
-
export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "sessionCacheTtlSec" | "storeProbeIntervalMs" | "storeLockWaitMs" | "dbBackend" | "dbBackendExplicit" | "localDataRoot" | "runStoreStrictHydrate" | "tidb" | "pg" | "dbQueryTimeoutMs" | "snapshotBlobStore" | "snapshotBlobSqlMaxBytes" | "snapshotBlobAllowSql" | "fileHistory" | "fileHistoryCapture" | "sendUserFile">;
|
|
2266
|
+
export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "sessionCacheTtlSec" | "storeProbeIntervalMs" | "storeLockWaitMs" | "dbBackend" | "dbBackendExplicit" | "localDataRoot" | "sealedKeyDir" | "runStoreStrictHydrate" | "tidb" | "pg" | "dbQueryTimeoutMs" | "snapshotBlobStore" | "snapshotBlobSqlMaxBytes" | "snapshotBlobAllowSql" | "fileHistory" | "fileHistoryCapture" | "sendUserFile">;
|
|
2259
2267
|
/** 组:modelPlane(模型面)—— 网关坐标、Anthropic 路线、韧性旋钮、主/廉价 model entry、role 表、降级梯。 */
|
|
2260
2268
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "envModel" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelRefusals" | "droppedCatalogEntries" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atMention" | "cascadeLadder" | "degrade">;
|
|
2261
2269
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|