@sema-agent/server 7.128.0 → 7.129.1
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 +140 -0
- package/USAGE.md +114 -18
- package/dist/approval-card.d.ts +53 -26
- package/dist/approval-card.js +40 -11
- package/dist/approval-crash-convergence.js +4 -4
- package/dist/bake-runner/main.js +2 -1
- package/dist/boot/execution-env.js +1 -0
- package/dist/boot/memory-consolidation.d.ts +46 -16
- package/dist/boot/memory-consolidation.js +15 -8
- package/dist/boot/side-query-lane.d.ts +7 -20
- package/dist/boot/side-query-lane.js +0 -24
- package/dist/boot/stage-01-config.js +3 -0
- package/dist/boot/stage-06-runners.js +1 -1
- package/dist/boot/stores.js +2 -2
- package/dist/boot/web-admin.js +17 -10
- package/dist/boot/webfetch-summarize-lane.d.ts +10 -7
- package/dist/boot/webfetch-summarize-lane.js +2 -3
- package/dist/brain.js +6 -9
- package/dist/capabilities/builtin-tools.d.ts +2 -2
- package/dist/capabilities/engine-capabilities.d.ts +14 -0
- package/dist/capabilities/engine-capabilities.js +12 -0
- package/dist/capability-facts.d.ts +3 -1
- package/dist/capability-facts.js +1 -0
- package/dist/config-catalog.js +5 -1
- package/dist/config-types.d.ts +7 -3
- package/dist/config.js +7 -1
- package/dist/egress-redirect.d.ts +12 -0
- package/dist/egress-redirect.js +7 -0
- package/dist/entry-mode-clamp-notice.d.ts +30 -0
- package/dist/entry-mode-clamp-notice.js +13 -0
- package/dist/exec-bounds.d.ts +69 -0
- package/dist/exec-bounds.js +37 -0
- package/dist/fleet/fleet-bus.d.ts +7 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-llm.js +6 -2
- package/dist/host-lsp-manager.d.ts +3 -3
- package/dist/http/admission.js +5 -2
- package/dist/http/resume-legs.js +46 -27
- package/dist/http/route-ctx.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +6 -4
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/session-sync.js +3 -4
- package/dist/http/routes/sessions.js +4 -17
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +4 -3
- package/dist/http/wire-types.d.ts +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/observability/secret-env-scrub.d.ts +37 -3
- package/dist/observability/secret-env-scrub.js +2 -2
- package/dist/parked-decide.js +5 -2
- package/dist/plugins/memory-embedder.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +180 -9
- package/dist/plugins/one-time-migrations.js +315 -3
- package/dist/plugins/pg-pool.js +3 -1
- package/dist/plugins/remote-env-e2b.d.ts +31 -9
- package/dist/plugins/remote-env-e2b.js +77 -26
- package/dist/plugins/remote-env-host.d.ts +17 -20
- package/dist/plugins/remote-env-host.js +20 -30
- package/dist/plugins/remote-env-k8s.d.ts +12 -4
- package/dist/plugins/remote-env-k8s.js +22 -31
- package/dist/plugins/session-policy-store-sql.d.ts +16 -4
- package/dist/plugins/session-policy-store-sql.js +17 -20
- package/dist/plugins/session-rules-column.d.ts +16 -0
- package/dist/plugins/session-rules-column.js +6 -0
- package/dist/plugins/tidb-pool.js +4 -1
- package/dist/read-face-posture.d.ts +2 -1
- package/dist/request-value-closure.js +1 -1
- package/dist/retired-card-words.d.ts +17 -0
- package/dist/retired-card-words.js +49 -0
- package/dist/rule-entry-list.js +2 -0
- package/dist/run-local.js +10 -3
- package/dist/server-secret-env.d.ts +77 -85
- package/dist/server-secret-env.js +40 -26
- package/dist/session-policy-wire.d.ts +61 -124
- package/dist/session-policy-wire.js +59 -121
- package/dist/session-sync.d.ts +8 -6
- package/dist/session-sync.js +6 -5
- package/dist/spec-fields.d.ts +3 -2
- package/dist/standing-answer-grant.d.ts +17 -9
- package/dist/standing-answer-grant.js +11 -8
- package/dist/task-settings.d.ts +6 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +14 -7
- package/dist/tool-approval.js +23 -18
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/project.d.ts +7 -4
- package/dist/trace/project.js +6 -75
- package/dist/trace/tool-output-media.d.ts +2 -1
- package/dist/trace/tool-output-media.js +3 -25
- package/dist/web-admin-client/apply-effective.js +28 -11
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/read-face.d.ts +73 -20
- package/dist/web-admin-client/read-face.js +76 -36
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +1 -1
- package/dist/web-admin-client/types.d.ts +3 -3
- package/package.json +3 -3
- package/dist/mcp-tool-axes.d.ts +0 -62
- package/dist/mcp-tool-axes.js +0 -47
|
@@ -15,6 +15,7 @@ import { judgeModelRouteEndpoint } from "../model-route-endpoint.js";
|
|
|
15
15
|
import { gateIntentUnserviceableReason } from "../approval.js";
|
|
16
16
|
import { refusedGroups } from "./apply-ledger.js";
|
|
17
17
|
import { judgeConfigCandidate } from "./candidate-gate.js";
|
|
18
|
+
import { declareServerWithheldEnv, withdrawServerWithheldEnv } from "../server-secret-env.js";
|
|
18
19
|
const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
|
|
19
20
|
function envDerivedModel(config) {
|
|
20
21
|
const m = config.envModel;
|
|
@@ -97,17 +98,21 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
97
98
|
return false;
|
|
98
99
|
}
|
|
99
100
|
const mp = staged.modelPlaneGroup.stages.models;
|
|
100
|
-
if (mp !== undefined
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
if (mp !== undefined) {
|
|
102
|
+
declareCandidateWithheldEnv(mp.modelApiKeyEnv, staged.version);
|
|
103
|
+
if (opts.swapPlane !== undefined) {
|
|
104
|
+
try {
|
|
105
|
+
opts.swapPlane({ models: mp.models, tiers: mp.activeTiers });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
withdrawServerWithheldEnv();
|
|
109
|
+
logger?.warn("web_admin_model_plane_swap_refused", {
|
|
110
|
+
version: staged.version,
|
|
111
|
+
error: err instanceof Error ? err.message : String(err),
|
|
112
|
+
note: "the Runner refused the candidate model plane (swapDeps screens the whole candidate atomically — the current generation stays in force untouched); candidate refused whole, re-judged next poll (/health shows target≠applied while it persists)",
|
|
113
|
+
});
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
commitStaged(config, staged);
|
|
@@ -141,6 +146,18 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
141
146
|
logger?.info("web_admin_projects", { projects: Object.keys(staged.projects) });
|
|
142
147
|
return true;
|
|
143
148
|
}
|
|
149
|
+
function declareCandidateWithheldEnv(modelApiKeyEnv, version) {
|
|
150
|
+
try {
|
|
151
|
+
declareServerWithheldEnv(modelApiKeyEnv);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
const entries = Object.entries(modelApiKeyEnv)
|
|
155
|
+
.map(([model, envName]) => `${model}→${envName}`)
|
|
156
|
+
.join(", ");
|
|
157
|
+
throw new Error(`model catalog (effective v${version}): a model entry's apiKeyEnv names a variable the engine cannot withhold from model-driven shells — ` +
|
|
158
|
+
`the candidate is refused whole and the current generation keeps serving; fix that entry's apiKeyEnv (entries: ${entries}). Engine: ${err instanceof Error ? err.message : String(err)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
144
161
|
function commitStaged(config, staged) {
|
|
145
162
|
const mp = staged.modelPlaneGroup.stages.models;
|
|
146
163
|
if (mp) {
|
|
@@ -51,6 +51,6 @@ export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHt
|
|
|
51
51
|
export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, logEffectiveDiff, MODEL_PLANE_DOMAINS, type ModelPlaneDomain, type ModelPlaneOf, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
|
|
52
52
|
export { restartReasons, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type EngineRestartSignal, } from "./restart-signal.js";
|
|
53
53
|
export { applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
54
|
-
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
|
|
54
|
+
export { applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline, type ReadFaceSeat } from "./read-face.js";
|
|
55
55
|
export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterModel, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
|
|
56
56
|
//# sourceMappingURL=facade.d.ts.map
|
|
@@ -2,5 +2,5 @@ export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHt
|
|
|
2
2
|
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
|
|
3
3
|
export { restartReasons, modelPlaneChanged, } from "./restart-signal.js";
|
|
4
4
|
export { applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
5
|
-
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
5
|
+
export { applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
6
6
|
//# sourceMappingURL=facade.js.map
|
|
@@ -1,22 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #A4([ref] 窗三件批 件1;[ref]/[ref] 承诺件)—— READ 容纳面四键的 **web-admin 消费腿**。
|
|
3
|
+
*
|
|
4
|
+
* 部署 env 的四键(`READ_FACE` / `READ_DENY_PATTERNS` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`,见 config.ts)
|
|
5
|
+
* 此前是这条轴上的唯一入口:一个用 web-admin 管理整片 worker 的组织,想统一收紧 READ 面只能逐机改 env。本模块把
|
|
6
|
+
* `EffectiveConfig.readFace`(见 types.ts {@link CenterReadFace})投影到同样的四个 `ServiceConfig` 键上。
|
|
7
|
+
*
|
|
8
|
+
* ── 生效时机:**逐席由引擎答**(S-174 起 face;S-1560 / core 7.51.0 起四席全热)──────────────────────────
|
|
9
|
+
* 哪些席能热换,不是本仓的意见:`KEY_MAP` 每行的 `cfgKey` 就是 core 的席名,能不能热换由 core 的 `isSwappableDepSeat` 亲答
|
|
10
|
+
* ({@link CENTER_READ_FACE_LIVE_KEYS})。本模块按这**一个**判据分相({@link CenterReadFaceApply}):
|
|
11
|
+
* · `boot` 相 —— 四键全落(Runner 还没建,每一席都还会 bake);
|
|
12
|
+
* · `live` 相(refresh 拍)—— **只落引擎有活体席的键**,回执 {@link CenterReadFaceReport.changedSeats} 报这一拍换了值的席,
|
|
13
|
+
* 调用方据它向全部 boot Runner `swapDeps` 一次。引擎**没有**活体席的键 live 相**刻意不落**:写进 `config` 会让
|
|
14
|
+
* `/health` / wiring 读面宣称一个引擎并没有在执行的 READ 姿态(而 `read-face` restart 片仍在为它要一次重启)—— 那正是这条
|
|
15
|
+
* 轴上最危险的谎(读面说收紧了、执行面没有)。
|
|
16
|
+
* 落配、`applied` 日志、换席候选、重启片的指纹读的是**同一个**判据 —— core 把某席移进 / 移出闭集,四处自动一起走
|
|
17
|
+
* (core 7.51.0 把读拒三席移了进去,而 live 相的落配当时是一句手写的「deny 三键只在 boot 相落」,与另三处的派生读法分家:
|
|
18
|
+
* 重启片说「热了」、落配说「不落」,组织收紧读拒规则 ⇒ 既不生效也不要求重启。S-1560 收成一个判据)。
|
|
19
|
+
*
|
|
20
|
+
* ── 三条规则,每条都有失败方向的论证 ──────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* ① **env 显式恒赢,逐键判**(operator-knob 律)。env 是**这台机器**的部署主权表达,center 是组织下发;
|
|
23
|
+
* 冲突时保留 env 并留一条点名 warn。为什么不照 6 闸的「center 覆盖 env」:那六闸是**限额/治理**面
|
|
24
|
+
* (组织比单机更知道该收多紧),而 READ 容纳面描述的是**这台机器的磁盘上有什么** —— 运维在这台机器上
|
|
25
|
+
* 写下 `READ_FACE=roots` 时知道的事情,组织的下发面不知道。静默盖掉它是本仓「部署级旋钮」纪律的反面。
|
|
26
|
+
* 逐键而非整域:env 只写了 face 时,center 的三个 deny 键照常落(否则一个 env 键会把整片组织策略挡掉)。
|
|
27
|
+
* 判据 = **键在场**(`!== undefined`):`readDenyBuiltinTiers: []` 是 env `none` 的显式空姿态,
|
|
28
|
+
* 不是「没设过」—— 用真值判会把「一档内建都不要」这个显式表态判成缺席。
|
|
29
|
+
* 🔴 判在**基线**上(本机 env / posture,center 之前的值),不是判「键现在在不在场」:center 自己上一拍写下的值
|
|
30
|
+
* 也让键在场,拿它当 env 就会把组织自己的第二次发布挡在门外。
|
|
31
|
+
*
|
|
32
|
+
* ② **坏值整域拒 + 响亮**([ref] 安全轴)。任何一个成员过不了引擎自己的校验器 ⇒ 整个 readFace 域不落,
|
|
33
|
+
* 保留 env 前值 + warn 点名。两个方向都要:半应用一份 READ 姿态(收紧了一半)会让运维以为策略生效;
|
|
34
|
+
* 而 throw 会让一次坏发布把常驻进程炸掉(env 腿 throw 是对的 —— 那是启动期、由本机运维自己写的值)。
|
|
35
|
+
* 与 `stageApprovalRequire` 的域级拒绝先例同形。
|
|
36
|
+
*
|
|
37
|
+
* ③ **校验一律交给引擎**(`compileReadDeny` / `resolveReadDenyBuiltins`)。宿主不自造第二份判定语义,
|
|
38
|
+
* 与 env 腿逐字同款;且校验跑在**合成后的最终姿态**上(env 键 + center 键混合形),否则
|
|
39
|
+
* 「env 选了档位、center 删了一个不在该档里的行名」这类跨腿坏组合会漏过。
|
|
40
|
+
*/
|
|
41
|
+
import { type SwappableDeps } from "@sema-agent/core";
|
|
1
42
|
import type { ServiceConfig } from "../config-types.js";
|
|
2
43
|
import type { ReadFaceSource } from "../read-face-posture.js";
|
|
3
44
|
import type { Logger } from "../observability/logger.js";
|
|
4
45
|
import type { CenterReadFace, EffectiveConfig } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* `
|
|
14
|
-
|
|
15
|
-
export interface ReadFaceBaseline {
|
|
16
|
-
readonly readFace?: "open" | "roots";
|
|
46
|
+
/** 四个 center 成员落到的 `ServiceConfig` 键 —— 同时就是 core 的席名(`SWAPPABLE_DEP_SEATS` 的成员名)。 */
|
|
47
|
+
export type ReadFaceSeat = "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude";
|
|
48
|
+
type ReadFaceSeatValues = {
|
|
49
|
+
readonly [K in ReadFaceSeat]?: ServiceConfig[K];
|
|
50
|
+
};
|
|
51
|
+
/** 本进程的**前 center** READ 姿态(env / posture / 引擎默认)—— 四键的值 + `readFace` 的来源。三处读它:
|
|
52
|
+
* ① env-wins 判据(基线上谁被 env 占住);② `live` 相的撤回臂(一次发布**撤回**某键时,活席要还原到它,而不是把上一份发布的
|
|
53
|
+
* 值无限期 latch 住 —— 那是这条轴上的 fail-OPEN 方向:组织把 `open` / 一条 deny 规则撤回之后,worker 还在旧姿态上);
|
|
54
|
+
* ③ 换席抛时的整体回滚(由 {@link buildReadFaceBaseline} 在调用前现取一份)。boot 相不需要传:那一刻 `config` 上的值就是基线。 */
|
|
55
|
+
export type ReadFaceBaseline = ReadFaceSeatValues & {
|
|
17
56
|
readonly readFaceSource: ReadFaceSource;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
57
|
+
};
|
|
58
|
+
/** 取 `config` 此刻的 READ 姿态快照(纯数据;数组按引用 —— 本模块只整值替换、从不原地改)。 */
|
|
59
|
+
export declare function buildReadFaceBaseline(config: ServiceConfig): ReadFaceBaseline;
|
|
60
|
+
/** 把 `config` 的 READ 姿态整体写回一份快照(换席抛时的回滚;快照里缺席的键写回 `undefined`)。 */
|
|
61
|
+
export declare function restoreReadFaceBaseline(config: ServiceConfig, snap: ReadFaceBaseline): void;
|
|
62
|
+
export declare const CENTER_READ_FACE_LIVE_KEYS: ReadonlySet<keyof CenterReadFace>;
|
|
63
|
+
/** 本次调用落在哪一相(见文件头注「逐席由引擎答」)。判别联合而不是可选字段:`baseline` 只在
|
|
20
64
|
* `live` 相有意义,写成可选就等于允许一次「没有基线的 live 调用」——那正是撤回臂会静默失效的形。 */
|
|
21
65
|
export type CenterReadFaceApply = {
|
|
22
66
|
readonly phase: "boot";
|
|
@@ -24,20 +68,29 @@ export type CenterReadFaceApply = {
|
|
|
24
68
|
readonly phase: "live";
|
|
25
69
|
readonly baseline: ReadFaceBaseline;
|
|
26
70
|
};
|
|
27
|
-
/** 调用回执。`envHeld` 同旧返回值(喂 `RestartSliceCtx.readFaceEnvHeld`);`
|
|
28
|
-
*
|
|
71
|
+
/** 调用回执。`envHeld` 同旧返回值(喂 `RestartSliceCtx.readFaceEnvHeld`);`changedSeats` 是 live 相的换席信号 ——
|
|
72
|
+
* 这一拍**值真的变了**的活体席(按 {@link ReadFaceSeat} 席名)。非空 ⇒ 调用方必须把这些席的值 `swapDeps` 给全部 boot
|
|
73
|
+
* Runner 一次,否则 config 与 Runner 分家。boot 相无人消费(Runner 还没建)。 */
|
|
29
74
|
export interface CenterReadFaceReport {
|
|
30
75
|
readonly envHeld: readonly (keyof CenterReadFace)[];
|
|
31
|
-
readonly
|
|
76
|
+
readonly changedSeats: readonly ReadFaceSeat[];
|
|
32
77
|
}
|
|
33
78
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
79
|
+
* 换席候选:`changed`(回执的 {@link CenterReadFaceReport.changedSeats})里每一席取 `seats` 里的值。
|
|
80
|
+
* `seats` 由调用方从部署姿态座席族的唯一属主取(`boot/runner-deps.ts` `buildDeploymentPostureSeats`)—— 席的**值**是座席族
|
|
81
|
+
* 铸的(例如 `readDenyPatterns` 并入 LKG 派生行),本模块只决定**换哪几席**。键在场即指令:值为 `undefined` 的席也进候选
|
|
82
|
+
* (core 据此把那一席清回引擎缺省 —— 撤回臂靠它兑现)。
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildReadFaceSwapCandidate(seats: Pick<SwappableDeps, ReadFaceSeat>, changed: readonly ReadFaceSeat[]): SwappableDeps;
|
|
85
|
+
/**
|
|
86
|
+
* 应用 center 的 readFace 域(boot 相:调用点在 boot 拉取之后、`createRunnerDeps` 之前;live 相:refresh 拍两条 commit 腿)。
|
|
87
|
+
* 缺席 ⇒ 零触碰(live 相:撤回臂把活体席还原到基线)。规则见文件头注 ①②③。
|
|
36
88
|
*
|
|
37
|
-
* 返回值 = **本机 env 显式占住的 center 键名**(与本次发布内容无关的部署事实)。调用方把它喂给
|
|
89
|
+
* 返回值 `envHeld` = **本机 env 显式占住的 center 键名**(与本次发布内容无关的部署事实)。调用方把它喂给
|
|
38
90
|
* `RestartSliceCtx.readFaceEnvHeld`,`read-face` restart 片于是只指纹「真会 boot-bake 的那部分」——
|
|
39
91
|
* 否则 center 改一个本机永远用不上的键,整片 fleet 会为一次对自己毫无影响的发布滚动重启一遍
|
|
40
92
|
* (codex 交叉复审 finding 2,验真后采纳的那一半)。
|
|
41
93
|
*/
|
|
42
94
|
export declare function applyCenterReadFace(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, apply?: CenterReadFaceApply): CenterReadFaceReport;
|
|
95
|
+
export {};
|
|
43
96
|
//# sourceMappingURL=read-face.d.ts.map
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { compileReadDeny, isSwappableDepSeat, resolveReadDenyBuiltins } from "@sema-agent/core";
|
|
2
|
+
export function buildReadFaceBaseline(config) {
|
|
3
|
+
return {
|
|
4
|
+
...(config.readFace !== undefined ? { readFace: config.readFace } : {}),
|
|
5
|
+
readFaceSource: config.readFaceSource,
|
|
6
|
+
...(config.readDenyPatterns !== undefined ? { readDenyPatterns: config.readDenyPatterns } : {}),
|
|
7
|
+
...(config.readDenyBuiltinTiers !== undefined ? { readDenyBuiltinTiers: config.readDenyBuiltinTiers } : {}),
|
|
8
|
+
...(config.readDenyBuiltinExclude !== undefined ? { readDenyBuiltinExclude: config.readDenyBuiltinExclude } : {}),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function restoreReadFaceBaseline(config, snap) {
|
|
12
|
+
config.readFace = snap.readFace;
|
|
13
|
+
config.readFaceSource = snap.readFaceSource;
|
|
14
|
+
config.readDenyPatterns = snap.readDenyPatterns;
|
|
15
|
+
config.readDenyBuiltinTiers = snap.readDenyBuiltinTiers;
|
|
16
|
+
config.readDenyBuiltinExclude = snap.readDenyBuiltinExclude;
|
|
17
|
+
}
|
|
2
18
|
const KEY_MAP = [
|
|
3
|
-
{ centerKey: "face", cfgKey: "readFace", envHolds: (
|
|
4
|
-
{ centerKey: "denyPatterns", cfgKey: "readDenyPatterns", envHolds: (
|
|
5
|
-
{ centerKey: "denyBuiltinTiers", cfgKey: "readDenyBuiltinTiers", envHolds: (
|
|
6
|
-
{ centerKey: "denyBuiltinExclude", cfgKey: "readDenyBuiltinExclude", envHolds: (
|
|
19
|
+
{ centerKey: "face", cfgKey: "readFace", envHolds: (b) => b.readFaceSource === "env" },
|
|
20
|
+
{ centerKey: "denyPatterns", cfgKey: "readDenyPatterns", envHolds: (b) => b.readDenyPatterns !== undefined },
|
|
21
|
+
{ centerKey: "denyBuiltinTiers", cfgKey: "readDenyBuiltinTiers", envHolds: (b) => b.readDenyBuiltinTiers !== undefined },
|
|
22
|
+
{ centerKey: "denyBuiltinExclude", cfgKey: "readDenyBuiltinExclude", envHolds: (b) => b.readDenyBuiltinExclude !== undefined },
|
|
7
23
|
];
|
|
8
|
-
|
|
24
|
+
const LIVE_SEATS = new Set(KEY_MAP.filter((r) => isSwappableDepSeat(r.cfgKey)).map((r) => r.cfgKey));
|
|
25
|
+
export const CENTER_READ_FACE_LIVE_KEYS = new Set(KEY_MAP.filter((r) => LIVE_SEATS.has(r.cfgKey)).map((r) => r.centerKey));
|
|
26
|
+
function putCandidateSeat(candidate, key, value) {
|
|
27
|
+
candidate[key] = value;
|
|
28
|
+
}
|
|
29
|
+
export function buildReadFaceSwapCandidate(seats, changed) {
|
|
30
|
+
const candidate = {};
|
|
31
|
+
for (const seat of changed)
|
|
32
|
+
putCandidateSeat(candidate, seat, seats[seat]);
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
function putSeat(config, key, value) {
|
|
36
|
+
config[key] = value;
|
|
37
|
+
}
|
|
38
|
+
function sameSeatValue(a, b) {
|
|
39
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
40
|
+
}
|
|
9
41
|
class ReadFaceDomainError extends Error {
|
|
10
42
|
}
|
|
11
43
|
function normalizeDenyPatterns(raw) {
|
|
@@ -44,18 +76,25 @@ function normalizeNameList(field, raw) {
|
|
|
44
76
|
});
|
|
45
77
|
}
|
|
46
78
|
export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot" }) {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
79
|
+
const base = apply.phase === "live" ? apply.baseline : buildReadFaceBaseline(config);
|
|
80
|
+
const envHeldKeys = KEY_MAP.filter((r) => r.envHolds(base)).map((r) => r.centerKey);
|
|
81
|
+
const lands = (seat) => apply.phase === "boot" || LIVE_SEATS.has(seat);
|
|
82
|
+
const before = buildReadFaceBaseline(config);
|
|
83
|
+
const report = () => ({
|
|
84
|
+
envHeld: envHeldKeys,
|
|
85
|
+
changedSeats: KEY_MAP.filter((r) => LIVE_SEATS.has(r.cfgKey) && !sameSeatValue(before[r.cfgKey], config[r.cfgKey])).map((r) => r.cfgKey),
|
|
86
|
+
});
|
|
87
|
+
const revertToBaseline = (r) => {
|
|
88
|
+
if (apply.phase !== "live" || !lands(r.cfgKey))
|
|
52
89
|
return;
|
|
53
|
-
config.
|
|
54
|
-
|
|
90
|
+
putSeat(config, r.cfgKey, base[r.cfgKey]);
|
|
91
|
+
if (r.cfgKey === "readFace")
|
|
92
|
+
config.readFaceSource = base.readFaceSource;
|
|
55
93
|
};
|
|
56
94
|
const domain = eff.readFace;
|
|
57
95
|
if (domain === undefined || domain === null) {
|
|
58
|
-
|
|
96
|
+
for (const r of KEY_MAP)
|
|
97
|
+
revertToBaseline(r);
|
|
59
98
|
return report();
|
|
60
99
|
}
|
|
61
100
|
if (typeof domain !== "object" || Array.isArray(domain)) {
|
|
@@ -66,7 +105,7 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
66
105
|
return report();
|
|
67
106
|
}
|
|
68
107
|
const d = domain;
|
|
69
|
-
const envHeld = KEY_MAP.filter((r) => d[r.centerKey] !== undefined && r.envHolds(
|
|
108
|
+
const envHeld = KEY_MAP.filter((r) => d[r.centerKey] !== undefined && r.envHolds(base)).map((r) => `${r.cfgKey} (readFace.${r.centerKey})`);
|
|
70
109
|
let staged;
|
|
71
110
|
try {
|
|
72
111
|
const normalized = {
|
|
@@ -80,10 +119,12 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
80
119
|
if (normalized.readDenyBuiltinTiers !== undefined || normalized.readDenyBuiltinExclude !== undefined) {
|
|
81
120
|
resolveReadDenyBuiltins(builtinsArg(normalized.readDenyBuiltinTiers, normalized.readDenyBuiltinExclude));
|
|
82
121
|
}
|
|
83
|
-
staged = Object.fromEntries(KEY_MAP.filter((r) => !r.envHolds(
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
122
|
+
staged = Object.fromEntries(KEY_MAP.filter((r) => !r.envHolds(base)).map((r) => [r.cfgKey, normalized[r.cfgKey]]).filter(([, v]) => v !== undefined));
|
|
123
|
+
const baseSeats = base;
|
|
124
|
+
const finalOf = (k) => staged[k] ?? (lands(k) ? baseSeats[k] : config[k]);
|
|
125
|
+
const finalPatterns = finalOf("readDenyPatterns");
|
|
126
|
+
const finalTiers = finalOf("readDenyBuiltinTiers");
|
|
127
|
+
const finalExclude = finalOf("readDenyBuiltinExclude");
|
|
87
128
|
if (finalPatterns !== undefined)
|
|
88
129
|
compileReadDeny(finalPatterns);
|
|
89
130
|
if (finalTiers !== undefined || finalExclude !== undefined)
|
|
@@ -96,20 +137,19 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
96
137
|
});
|
|
97
138
|
return report();
|
|
98
139
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
config.
|
|
111
|
-
|
|
112
|
-
config.readDenyBuiltinExclude = staged.readDenyBuiltinExclude;
|
|
140
|
+
const landed = [];
|
|
141
|
+
for (const r of KEY_MAP) {
|
|
142
|
+
if (!lands(r.cfgKey))
|
|
143
|
+
continue;
|
|
144
|
+
const v = staged[r.cfgKey];
|
|
145
|
+
if (v === undefined) {
|
|
146
|
+
revertToBaseline(r);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
putSeat(config, r.cfgKey, v);
|
|
150
|
+
if (r.cfgKey === "readFace")
|
|
151
|
+
config.readFaceSource = "center";
|
|
152
|
+
landed.push(r.cfgKey);
|
|
113
153
|
}
|
|
114
154
|
if (envHeld.length > 0) {
|
|
115
155
|
logger?.warn("web_admin_read_face_env_wins", {
|
|
@@ -117,13 +157,13 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
117
157
|
note: "this machine's explicit env READ-face keys WIN over the published readFace domain (deployment sovereignty) — the published values for these keys were NOT applied; unset the env key if the org publication should own it",
|
|
118
158
|
});
|
|
119
159
|
}
|
|
120
|
-
if (
|
|
160
|
+
if (landed.length > 0) {
|
|
121
161
|
logger?.info("web_admin_read_face", {
|
|
122
|
-
applied:
|
|
123
|
-
...(staged.readFace !== undefined ? { face: staged.readFace } : {}),
|
|
162
|
+
applied: landed,
|
|
163
|
+
...(staged.readFace !== undefined && landed.includes("readFace") ? { face: staged.readFace } : {}),
|
|
124
164
|
note: apply.phase === "boot"
|
|
125
165
|
? "boot phase — every seat still bakes into the runner deps that are about to be assembled"
|
|
126
|
-
: "live phase — the
|
|
166
|
+
: "live phase — the landed seats are hot-swapped into every boot Runner (core swapDeps seats); a seat the engine cannot hot-swap is not written here and keeps asking for the `read-face` restart slice",
|
|
127
167
|
});
|
|
128
168
|
}
|
|
129
169
|
return report();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type A2aServerSpec, type McpServerSpec } from "@sema-agent/core";
|
|
2
2
|
import type { CenterMcp, ScopedA2aPeer } from "../config-types.js";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
4
|
import type { LoadedSkill } from "../capabilities/skills.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readMcpSourceLabel } from "../mcp-source-label.js";
|
|
2
2
|
import { readMcpToolFaces } from "../mcp-tool-faces.js";
|
|
3
|
-
import { readMcpToolAxes } from "../mcp-tool-axes.js";
|
|
4
3
|
import { skillContentHash } from "@sema-agent/settings-schema";
|
|
4
|
+
import { readMcpToolAxes } from "@sema-agent/core";
|
|
5
5
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
|
|
6
6
|
import { noteMcpAssertionHeaderNotSent } from "../mcp-assertion-header.js";
|
|
7
7
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
@@ -157,9 +157,9 @@ export interface CenterMcpServer {
|
|
|
157
157
|
* `McpServerSpec.toolAxes`。
|
|
158
158
|
*
|
|
159
159
|
* 🔴 类型是 `unknown` 而不是那个结构,**刻意的**(与 {@link CenterMcpServer.toolFaces} 逐字同姿):本类型是两条部署腿的**承载形**
|
|
160
|
-
* (本地 config.d 经 `z.unknown()` 解析透明地过来、远端 /effective 是直读 JSON)
|
|
161
|
-
* `readMcpToolAxes`(
|
|
162
|
-
* server 不进 + 一行 `web_admin_mcp_tool_axes_invalid`。
|
|
160
|
+
* (本地 config.d 经 `z.unknown()` 解析透明地过来、远端 /effective 是直读 JSON),形判与投影的单一属主是**引擎**根导出的
|
|
161
|
+
* `readMcpToolAxes`(core 7.51.2 #1763;`resolveMcpServers` 在铸 spec 那一刻调它;产出的型是引擎的 `McpServerSpec["toolAxes"]`;本仓自铸的那一只
|
|
162
|
+
* 7.129.0 退役)。坏形 ⇒ 整条 server 不进 + 一行 `web_admin_mcp_tool_axes_invalid`。
|
|
163
163
|
* (同批解析透明键 `authForm` / `entryKind` 的语义归连接器,本层仍只承载。)
|
|
164
164
|
*/
|
|
165
165
|
toolAxes?: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.129.1",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@sema-agent/core": "7.
|
|
67
|
+
"@sema-agent/core": "7.51.2",
|
|
68
68
|
"@sema-agent/settings-schema": "12.2.1",
|
|
69
69
|
"e2b": "^2.28.0",
|
|
70
70
|
"libsodium-wrappers": "^0.8.4",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"sharp": "^0.35.3"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@sema-agent/sdk": "
|
|
83
|
+
"@sema-agent/sdk": "23.0.0",
|
|
84
84
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
85
85
|
"@types/node": "22.10.2",
|
|
86
86
|
"@types/pg": "^8.20.0",
|
package/dist/mcp-tool-axes.d.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `McpServerSpec.toolAxes` —— 部署侧为一台 MCP 服务器的**每只工具**声明的风险轴(`effect` / `egress` /
|
|
3
|
-
* `irreversibility`),键 = 该 server 的**原始(未加命名空间)**工具名。声明面 = settings-schema 12.2.x 的 MCP 条目
|
|
4
|
-
* 解析透明键(`z.unknown()`);引擎侧 = `McpServerSpec.toolAxes`(喂审批门与计划模式围栏的信任根轴)。
|
|
5
|
-
*
|
|
6
|
-
* ## 一只读器、一处投影
|
|
7
|
-
* 两条部署腿(本地 `config.d/mcp.json` 经 `LocalConfigProvider` / 中心 `/effective.mcp` 经远端读腿)都落到
|
|
8
|
-
* `web-admin-client/skills-mcp.ts resolveMcpServers` 这一只铸 spec 的函数(boot 与 run-local 共用),所以本模块
|
|
9
|
-
* 的形判与投影**只有这一处**;本地承载腿(`config-provider.ts`)只搬运、不判形 —— 与 `mcp-tool-faces.ts`
|
|
10
|
-
* 同一种「判据单点」的形。
|
|
11
|
-
*
|
|
12
|
-
* ## 词表来自哪(本模块一个词都不枚举)
|
|
13
|
-
* `effect` / `irreversibility` 两个闭集词轴的判据是 `tool-axis-words.ts` 的**共用谓词**(`isToolEffect` /
|
|
14
|
-
* `isToolAxisIrreversibility`;那一份词表的两向门钉死在引擎的型上:引擎加词 / 删词 ⇒ 编译红)。`egress` 是裸布尔,
|
|
15
|
-
* 没有词表。条目的**成员集**同样不抄一份:`AXIS_MEMBERS` 以 `satisfies Record<keyof ToolAxisEntry, true>` 取引擎型的键,
|
|
16
|
-
* 引擎给轴条目加新成员 ⇒ 这里编译红(而不是把一个合法的新成员当未知键拒、或当已知键漏判)。
|
|
17
|
-
*
|
|
18
|
-
* ## 为什么本层只判形,不判语义组合
|
|
19
|
-
* 轴值之间的语义关系(例如 `effect:"read"` 与 `egress:true` 并存)归引擎的摄入侧判 —— 与 `toolFaces` 同一条分工:
|
|
20
|
-
* 本层不复述引擎的规则表,只做引擎的校验器**假定成立**的那一件事:JSON 结构合不合 TS 型。原样透传一份任意 JSON
|
|
21
|
-
* 不是「解析透明」,是把类型错误变成运行期的更难读的失败。
|
|
22
|
-
*
|
|
23
|
-
* ## 坏形的方向:整条 server 不进(fail-closed;与 `toolFaces` 同方向)
|
|
24
|
-
* `toolAxes` 是**双向**声明:`effect:"read"` / `egress:false` / `irreversibility:"never"` 能让一次调用免审或降档(放宽),
|
|
25
|
-
* `egress:true` / `irreversibility:"always"` 又能把一次调用的风险抬高(收窄)。所以对任何一个坏形,「丢掉那个键、留下
|
|
26
|
-
* server」都落在安全轴上的静默一侧 —— 要么让一条写错的信任根声明静默失效(运维以为写了保护,引擎读到缺席),要么让它
|
|
27
|
-
* 静默地以残缺形生效(留 `effect:"read"` 丢坏形 `egress`,拼出引擎判为更宽的形)。仓规([ref])不许安全轴上有静默兜底,
|
|
28
|
-
* 判据因此回到本仓对收窄键成文的那一条:**一个键在场就必须合法,否则这条 server 整条不进**。每只工具的条目**整只**判,
|
|
29
|
-
* 而**任一只**判不出形 ⇒ 整条 server 不进(「留下好的那几只」同样是让坏的那一只带着缺失的声明挂载)。
|
|
30
|
-
* ① 谁需要 —— 在部署侧为 MCP / 连接器工具声明效应轴的运维;
|
|
31
|
-
* ② 谁受伤 —— 写错的那台 server:它**整条**不挂(兄弟 server 照挂,名字仍被部署占着、请求腿补不了位)。代价是真的
|
|
32
|
-
* (少一套工具),但它**响亮且立刻**;反方向的代价是一条静默失效或静默走样的信任根声明;
|
|
33
|
-
* ③ 拿什么补偿 —— `web_admin_mcp_tool_axes_invalid{server,problem,note}` 一条点名 warn,`problem` 指到工具名与哪一段不合
|
|
34
|
-
* (只带工具名与成员名,不带值);运维照着改那一行重发布即可。
|
|
35
|
-
*
|
|
36
|
-
* ## 与请求腿的边界(刻意不同的信任根)
|
|
37
|
-
* 请求腿(`task-mcp.ts`)对调用方自带的 `toolAxes` 另有一份**调用方信任根**的读法(单用户闸:调用方对自己的选择负责,
|
|
38
|
-
* 坏成员逐键诚实剔除而不 400)。部署腿声明的是**运维对每一个租户、每一次任务**的担保,没有「调用方自己承担」这回事,
|
|
39
|
-
* 所以坏形必须响亮地拒。两个读器今天并存、方向不同,是两个信任根各自的读法;本模块不替请求腿改判。
|
|
40
|
-
*/
|
|
41
|
-
import type { McpServerSpec } from "@sema-agent/core";
|
|
42
|
-
type ToolAxes = NonNullable<McpServerSpec["toolAxes"]>;
|
|
43
|
-
/**
|
|
44
|
-
* 一条 server 条目的 `toolAxes` 判形结果:
|
|
45
|
-
* · `{ ok: true, toolAxes }` —— 合形;`toolAxes` 缺席 = 这台 server 没有声明,或声明的全是空(`{}` / `{ t: {} }`:对引擎同义于缺席),不铸键;
|
|
46
|
-
* · `{ ok: false, problem }` —— 判不出形,**整条 server 不进**(方向与理由见模块头注),`problem` 是给运维的一句点名
|
|
47
|
-
* (只带工具名与成员名,不带值)。
|
|
48
|
-
*/
|
|
49
|
-
export type McpToolAxesRead = {
|
|
50
|
-
ok: true;
|
|
51
|
-
toolAxes?: ToolAxes;
|
|
52
|
-
} | {
|
|
53
|
-
ok: false;
|
|
54
|
-
problem: string;
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* 从一条部署侧 server 条目的原始 `toolAxes` 读出引擎 spec 的 `toolAxes` —— **两条部署腿共用的唯一判据与投影点**。
|
|
58
|
-
* 缺席 ⇒ 合形无声明;整只不是对象 / 工具名为空 / **任一只**工具的条目判不出形 ⇒ `ok:false`(调用方整条 server 不收)。
|
|
59
|
-
*/
|
|
60
|
-
export declare function readMcpToolAxes(raw: unknown): McpToolAxesRead;
|
|
61
|
-
export {};
|
|
62
|
-
//# sourceMappingURL=mcp-tool-axes.d.ts.map
|
package/dist/mcp-tool-axes.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
2
|
-
const AXIS_MEMBERS = { effect: true, egress: true, irreversibility: true };
|
|
3
|
-
const AXIS_MEMBER_NAMES = Object.keys(AXIS_MEMBERS);
|
|
4
|
-
function isRecord(v) {
|
|
5
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6
|
-
}
|
|
7
|
-
function describeKind(v) {
|
|
8
|
-
return v === null ? "null" : Array.isArray(v) ? "an array" : typeof v;
|
|
9
|
-
}
|
|
10
|
-
function readOneEntry(raw) {
|
|
11
|
-
if (!isRecord(raw))
|
|
12
|
-
return `the declaration is ${describeKind(raw)}, not an object of axes`;
|
|
13
|
-
for (const member of Object.keys(raw)) {
|
|
14
|
-
if (!AXIS_MEMBER_NAMES.includes(member))
|
|
15
|
-
return `unknown member ${JSON.stringify(member)}`;
|
|
16
|
-
}
|
|
17
|
-
const { effect, egress, irreversibility } = raw;
|
|
18
|
-
if (effect !== undefined && !isToolEffect(effect))
|
|
19
|
-
return "member effect is not a word the engine knows";
|
|
20
|
-
if (egress !== undefined && typeof egress !== "boolean")
|
|
21
|
-
return "member egress is not a boolean";
|
|
22
|
-
if (irreversibility !== undefined && !isToolAxisIrreversibility(irreversibility))
|
|
23
|
-
return "member irreversibility is not a word the override face accepts";
|
|
24
|
-
return {
|
|
25
|
-
...(effect !== undefined ? { effect } : {}),
|
|
26
|
-
...(egress !== undefined ? { egress } : {}),
|
|
27
|
-
...(irreversibility !== undefined ? { irreversibility } : {}),
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export function readMcpToolAxes(raw) {
|
|
31
|
-
if (raw === undefined)
|
|
32
|
-
return { ok: true };
|
|
33
|
-
if (!isRecord(raw))
|
|
34
|
-
return { ok: false, problem: `toolAxes is ${describeKind(raw)}, not an object of per-tool axis declarations` };
|
|
35
|
-
const entries = [];
|
|
36
|
-
for (const [tool, rawEntry] of Object.entries(raw)) {
|
|
37
|
-
if (tool.length === 0)
|
|
38
|
-
return { ok: false, problem: "toolAxes has an empty tool name" };
|
|
39
|
-
const entry = readOneEntry(rawEntry);
|
|
40
|
-
if (typeof entry === "string")
|
|
41
|
-
return { ok: false, problem: `the declared axes for tool ${JSON.stringify(tool)} do not fit: ${entry}` };
|
|
42
|
-
if (Object.keys(entry).length > 0)
|
|
43
|
-
entries.push([tool, entry]);
|
|
44
|
-
}
|
|
45
|
-
return entries.length > 0 ? { ok: true, toolAxes: Object.fromEntries(entries) } : { ok: true };
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=mcp-tool-axes.js.map
|