@sema-agent/server 7.129.1 → 7.130.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 +15 -0
- package/USAGE.md +13 -2
- package/dist/boot/resolve-spec.js +8 -4
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +2 -0
- package/dist/capabilities/mcp-probe.js +16 -3
- package/dist/capabilities/scenarios.d.ts +33 -0
- package/dist/capabilities/scenarios.js +15 -0
- package/dist/capabilities/skills.js +1 -1
- package/dist/exec-bounds.d.ts +6 -6
- package/dist/hooks/hook-llm.d.ts +11 -1
- package/dist/hooks/hook-llm.js +10 -13
- package/dist/hooks/hook-runner.d.ts +1 -1
- package/dist/http/routes/capabilities.js +20 -2
- package/dist/http/routes/runs.js +12 -4
- package/dist/http/server.d.ts +8 -0
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/plugins/one-time-migrations.d.ts +12 -1
- package/dist/plugins/one-time-migrations.js +17 -0
- package/dist/plugins/remote-env-adb.js +5 -4
- package/dist/plugins/remote-env-local-docker.js +5 -4
- package/dist/plugins/remote-env-ssh.js +5 -4
- package/dist/runtime-caps-resolver.d.ts +7 -0
- package/dist/runtime-caps-resolver.js +1 -0
- package/dist/session-titler.d.ts +4 -1
- package/dist/session-titler.js +13 -3
- package/dist/stored-request-tool-axes-m19.d.ts +29 -0
- package/dist/stored-request-tool-axes-m19.js +27 -0
- package/dist/task-mcp.d.ts +32 -1
- package/dist/task-mcp.js +66 -33
- package/dist/tool-axis-words.d.ts +14 -17
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/package.json +1 -1
|
@@ -241,6 +241,23 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
241
241
|
"引擎续跑不受影响:续跑入口读行摘要、不按今日闭集重判停驻行上的这两座,行字节引擎怎么写的还是怎么在",
|
|
242
242
|
reportKeys: ["retiredCardWordAskRowsRewritten", "retiredCardWordRowsUnreadable"],
|
|
243
243
|
},
|
|
244
|
+
{
|
|
245
|
+
id: "M19",
|
|
246
|
+
introducedIn: "7.130.0",
|
|
247
|
+
releasedOn: "2026-10-16",
|
|
248
|
+
removableAfter: "2026-11-15",
|
|
249
|
+
removal: "删 `src/stored-request-tool-axes-m19.ts` 整只文件(`storedToolAxesRereadM19` / `readToolAxesAsServer7129M19` / `StoredToolAxesRereadM19`);" +
|
|
250
|
+
"`boot/resolve-spec.ts` 里递给 `resolveRequestMcp` 的那一实参(`opts?.leg === \"fresh\" ? undefined : storedToolAxesRereadM19(…)`)与它的 import;" +
|
|
251
|
+
"`task-mcp.ts` 里 `resolveRequestMcp` / `classifyRequestMcp` / `classifyHonored` / `validateRequestMcpEntries` / `normalizeServer` 的 `storedToolAxes` 形参、" +
|
|
252
|
+
"`toolAxesReread` 事实与运维行 `mcp_tool_axes_read_as_before_upgrade`,以及对本模块的型 import;`tool-axis-words.ts` 头注与两只谓词 JSDoc 里点名 M19 的那几处;" +
|
|
253
|
+
"`test/mcp-tool-axes-m19.test.ts`(窗关那一格 —— 存量坏形整条不进、warn 与通告点名 `field: toolAxes` —— 改钉到 `test/task-mcp.test.ts`);" +
|
|
254
|
+
"本文件的 `migrationWindowOpen`(若再无别的读者)",
|
|
255
|
+
afterRemoval: "没有新的臂,接住它的是今天的读法(删码零行为变化:窗关那一刻起重读臂已经不生效):升级前存下的请求体里引擎判官判不过的 `toolAxes`,续跑重放时这一条 server 整条不进," +
|
|
256
|
+
"运维 warn `mcp_injection_dropped`(`reason: malformed_entry`、`field: toolAxes`)+ 用户面通告 `mcp.injection_dropped`,兄弟照挂;停驻在这台 server 工具审批上的任务" +
|
|
257
|
+
"批准后撞 `resume.tool_unavailable`(拒绝照常可出)—— 出路是拒绝那张卡,用改好的 `toolAxes` 重新提交。会撞上它的是活过窗的升级前停驻行:发布后才升级的部署、" +
|
|
258
|
+
"调长了 `APPROVAL_TERMINAL_BACKSTOP_MS` 的部署、滚动升级里旧副本在窗内新写的行(头注 M19 段)",
|
|
259
|
+
reportKeys: [],
|
|
260
|
+
},
|
|
244
261
|
];
|
|
245
262
|
export function migrationWindowOpen(window, today) {
|
|
246
263
|
return window.removableAfter === null || today <= window.removableAfter;
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
|
|
6
6
|
import { signalNumber } from "./host-platform.js";
|
|
7
|
+
import { commandBoundsOf, livenessCutMessage } from "../exec-bounds.js";
|
|
7
8
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
8
9
|
import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
9
10
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
@@ -191,7 +192,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
191
192
|
w?.();
|
|
192
193
|
};
|
|
193
194
|
let lastChunk = Date.now();
|
|
194
|
-
const
|
|
195
|
+
const liveness = commandBoundsOf({ readTimeoutMs: options?.readTimeoutMs }, undefined).liveness;
|
|
195
196
|
let paused = false;
|
|
196
197
|
let onAbort;
|
|
197
198
|
const teardown = () => {
|
|
@@ -262,7 +263,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
262
263
|
throw failure;
|
|
263
264
|
if (finished)
|
|
264
265
|
break;
|
|
265
|
-
const waitMs =
|
|
266
|
+
const waitMs = liveness !== undefined ? Math.max(1, liveness.ms - (Date.now() - lastChunk)) : 30_000;
|
|
266
267
|
let waitTimer;
|
|
267
268
|
try {
|
|
268
269
|
await new Promise((r) => {
|
|
@@ -274,8 +275,8 @@ export class RemoteAdbExecutionEnv {
|
|
|
274
275
|
if (waitTimer !== undefined)
|
|
275
276
|
clearTimeout(waitTimer);
|
|
276
277
|
}
|
|
277
|
-
if (
|
|
278
|
-
throw new RemoteExecutionError("timeout",
|
|
278
|
+
if (liveness !== undefined && !finished && Date.now() - lastChunk >= liveness.ms) {
|
|
279
|
+
throw new RemoteExecutionError("timeout", livenessCutMessage(liveness));
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
282
|
while (queue.length)
|
|
@@ -5,6 +5,7 @@ import { spawn } from "node:child_process";
|
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
|
|
7
7
|
import { signalNumber } from "./host-platform.js";
|
|
8
|
+
import { commandBoundsOf, livenessCutMessage } from "../exec-bounds.js";
|
|
8
9
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
9
10
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
10
11
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
@@ -188,7 +189,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
188
189
|
let paused = false;
|
|
189
190
|
let wake;
|
|
190
191
|
const signalReady = () => { const w = wake; wake = undefined; w?.(); };
|
|
191
|
-
const
|
|
192
|
+
const liveness = commandBoundsOf({ readTimeoutMs: options?.readTimeoutMs }, undefined).liveness;
|
|
192
193
|
const cap = options?.maxOutputBytes;
|
|
193
194
|
let onAbort;
|
|
194
195
|
const enqueue = (type, data) => {
|
|
@@ -270,7 +271,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
270
271
|
throw failure;
|
|
271
272
|
if (finished)
|
|
272
273
|
break;
|
|
273
|
-
const waitMs =
|
|
274
|
+
const waitMs = liveness !== undefined ? Math.max(1, liveness.ms - (Date.now() - lastChunk)) : 30_000;
|
|
274
275
|
let waitTimer;
|
|
275
276
|
try {
|
|
276
277
|
await new Promise((r) => { wake = r; waitTimer = setTimeout(r, waitMs); });
|
|
@@ -279,13 +280,13 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
279
280
|
if (waitTimer !== undefined)
|
|
280
281
|
clearTimeout(waitTimer);
|
|
281
282
|
}
|
|
282
|
-
if (
|
|
283
|
+
if (liveness !== undefined && !finished && Date.now() - lastChunk >= liveness.ms) {
|
|
283
284
|
finished = true;
|
|
284
285
|
try {
|
|
285
286
|
child.kill("SIGKILL");
|
|
286
287
|
}
|
|
287
288
|
catch { }
|
|
288
|
-
throw new RemoteExecutionError("timeout",
|
|
289
|
+
throw new RemoteExecutionError("timeout", livenessCutMessage(liveness));
|
|
289
290
|
}
|
|
290
291
|
}
|
|
291
292
|
while (queue.length)
|
|
@@ -3,6 +3,7 @@ import { Client } from "ssh2";
|
|
|
3
3
|
import { createSshHostKeyVerifier } from "../ssh-host-key.js";
|
|
4
4
|
import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, kindFromMode } from "./remote-shell.js";
|
|
5
5
|
import { signalNumber } from "./host-platform.js";
|
|
6
|
+
import { commandBoundsOf, livenessCutMessage } from "../exec-bounds.js";
|
|
6
7
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
7
8
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
8
9
|
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
@@ -335,7 +336,7 @@ export class RemoteSshExecutionEnv {
|
|
|
335
336
|
w?.();
|
|
336
337
|
};
|
|
337
338
|
let lastChunk = Date.now();
|
|
338
|
-
const
|
|
339
|
+
const liveness = commandBoundsOf({ readTimeoutMs: options?.readTimeoutMs }, undefined).liveness;
|
|
339
340
|
let streamRef;
|
|
340
341
|
let onAbort;
|
|
341
342
|
let paused = false;
|
|
@@ -422,7 +423,7 @@ export class RemoteSshExecutionEnv {
|
|
|
422
423
|
throw failure;
|
|
423
424
|
if (finished)
|
|
424
425
|
break;
|
|
425
|
-
const waitMs =
|
|
426
|
+
const waitMs = liveness !== undefined ? Math.max(1, liveness.ms - (Date.now() - lastChunk)) : 30_000;
|
|
426
427
|
let waitTimer;
|
|
427
428
|
try {
|
|
428
429
|
await new Promise((r) => {
|
|
@@ -434,8 +435,8 @@ export class RemoteSshExecutionEnv {
|
|
|
434
435
|
if (waitTimer !== undefined)
|
|
435
436
|
clearTimeout(waitTimer);
|
|
436
437
|
}
|
|
437
|
-
if (
|
|
438
|
-
throw new RemoteExecutionError("timeout",
|
|
438
|
+
if (liveness !== undefined && !finished && Date.now() - lastChunk >= liveness.ms)
|
|
439
|
+
throw new RemoteExecutionError("timeout", livenessCutMessage(liveness));
|
|
439
440
|
}
|
|
440
441
|
while (queue.length)
|
|
441
442
|
yield queue.shift();
|
|
@@ -175,6 +175,13 @@ export interface PrincipalEntitlementsClient {
|
|
|
175
175
|
* FAIL-OPEN on error / older center without scenario keys / no principal (undefined = no governance, legacy passthrough).
|
|
176
176
|
* Scenario switching is not a privilege-amplifying face; a center blip must not pin users to `default`. */
|
|
177
177
|
scenarioRuling: (principal: string | undefined) => Promise<ScenarioRuling | undefined>;
|
|
178
|
+
/**
|
|
179
|
+
* {@link PrincipalEntitlementsClient.scenarioRuling} 的**只读**孪生(同 {@link PrincipalEntitlementsClient.peekRuntimeCaps} 的界):同一只缓存 / 同一条抓取 /
|
|
180
|
+
* 同一份 fail-open 判决(`undefined` = 无治理),唯一的差别是 center 硬失败时本腿铸的 fail-closed 降级项**只对读面自己算数**,提交腿不认、照常重抓。
|
|
181
|
+
* 读面(`GET /v1/capabilities/mcp?scenario=` 的场景门)问中心裁定时必须走这一只 —— 经 `scenarioRuling` 问,center 抖动的那一拍会替这个主体的**真提交**
|
|
182
|
+
* 留下 `{allowWorkflows:false, allowFork:false, autoMode:false}` 的短窗 deny(读面成了别人被拒的原因,与 `peekRuntimeCaps` 的病灶同一类)。
|
|
183
|
+
*/
|
|
184
|
+
peekScenarioRuling: (principal: string | undefined) => Promise<ScenarioRuling | undefined>;
|
|
178
185
|
/** (stage7 P5) The resolved per-principal execution-lane ruling for resolveSpec's
|
|
179
186
|
* gateExecutionLane. FAIL-OPEN + audit on error (this phase is "honest-but-bypassable";
|
|
180
187
|
* tightening to fail-closed is a future, separate decision) / older center without the execution key / no principal. */
|
|
@@ -167,6 +167,7 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
167
167
|
resolveRuntimeCaps: async (principal) => (principal ? (await load(principal)).caps : undefined),
|
|
168
168
|
peekRuntimeCaps: async (principal) => (principal ? (await load(principal, false)).caps : undefined),
|
|
169
169
|
scenarioRuling: async (principal) => (principal ? (await load(principal)).scenario : undefined),
|
|
170
|
+
peekScenarioRuling: async (principal) => (principal ? (await load(principal, false)).scenario : undefined),
|
|
170
171
|
executionRuling: async (principal) => (principal ? (await load(principal)).execution : undefined),
|
|
171
172
|
};
|
|
172
173
|
}
|
package/dist/session-titler.d.ts
CHANGED
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* main model; bounded + timed out).
|
|
15
15
|
*
|
|
16
16
|
* FAILURE POSTURE: everything is best-effort — a generation/store failure logs and leaves title null (the
|
|
17
|
-
* list falls back to objectivePreview).
|
|
17
|
+
* list falls back to objectivePreview). The fail-open tag it leaves names the CAUSE FAMILY (one tag, one family):
|
|
18
|
+
* the llm face's failure CODE picks it (`failOpenTagFor`), never the error's wording. An in-process pending set dedupes concurrent submits on the same
|
|
18
19
|
* session; the store's IS NULL guard closes the cross-replica race (double generation costs one extra cheap
|
|
19
20
|
* call at worst, and exactly one title lands).
|
|
20
21
|
*/
|
|
22
|
+
import type { HookLlmFailureCode } from "./hooks/hook-runner.js";
|
|
21
23
|
export interface SessionTitlerDeps {
|
|
22
24
|
/** Secret scrub applied to the RAW llm text before sanitize/persist (workflow audit 2026-07-13: the
|
|
23
25
|
* objective may carry a pasted secret and the model can echo it into the title — titles are persisted,
|
|
@@ -42,6 +44,7 @@ export interface SessionTitlerDeps {
|
|
|
42
44
|
} | {
|
|
43
45
|
ok: false;
|
|
44
46
|
error: string;
|
|
47
|
+
code?: HookLlmFailureCode;
|
|
45
48
|
}>;
|
|
46
49
|
logger?: {
|
|
47
50
|
debug?(msg: string, meta?: unknown): void;
|
package/dist/session-titler.js
CHANGED
|
@@ -8,6 +8,16 @@ const LLM_TIMEOUT_MS = 15_000;
|
|
|
8
8
|
const NO_ROW_RETRY_MS = 20_000;
|
|
9
9
|
const FAIL_CAUSE_KEY_CHARS = 80;
|
|
10
10
|
const SEEN_CAP = 10_000;
|
|
11
|
+
function failOpenTagFor(code) {
|
|
12
|
+
switch (code) {
|
|
13
|
+
case "redirect_refused":
|
|
14
|
+
return "server.session-titler.redirect-refused";
|
|
15
|
+
case "no_content":
|
|
16
|
+
case "hard_timeout":
|
|
17
|
+
case undefined:
|
|
18
|
+
return "server.session-titler.gateway-unreachable";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
export function sanitizeTitle(raw) {
|
|
12
22
|
const oneLine = raw.replace(/[\r\n]+/g, " ").replace(CONTROL_AND_BIDI_STRIP_RE, "").trim();
|
|
13
23
|
const unquoted = oneLine.replace(/^["'「『]+/, "").replace(/["'」』]+$/, "").trim();
|
|
@@ -25,10 +35,10 @@ export function buildTitlePrompt(objective) {
|
|
|
25
35
|
}
|
|
26
36
|
export function createSessionTitler(deps) {
|
|
27
37
|
const seen = new Set();
|
|
28
|
-
const logFailure = (sessionId, error) => {
|
|
38
|
+
const logFailure = (sessionId, error, code) => {
|
|
29
39
|
const safe = redactSecrets(error);
|
|
30
40
|
deps.logger?.debug?.("session_title_failed", { sessionId, error: safe.slice(0, 200) });
|
|
31
|
-
recordFailOpen(
|
|
41
|
+
recordFailOpen(failOpenTagFor(code), safe.slice(0, FAIL_CAUSE_KEY_CHARS));
|
|
32
42
|
};
|
|
33
43
|
const probeOrContinue = async (sessionId, continueWord) => deps
|
|
34
44
|
.probeTitle(sessionId)
|
|
@@ -65,7 +75,7 @@ export function createSessionTitler(deps) {
|
|
|
65
75
|
const r = await deps.llm({ prompt: buildTitlePrompt(objective), timeoutMs: LLM_TIMEOUT_MS, model });
|
|
66
76
|
if (!r.ok) {
|
|
67
77
|
deps.metrics?.inc("session_title_total", { outcome: "llm_failed" });
|
|
68
|
-
logFailure(sessionId, r.error);
|
|
78
|
+
logFailure(sessionId, r.error, r.code);
|
|
69
79
|
return;
|
|
70
80
|
}
|
|
71
81
|
const title = sanitizeTitle(deps.redact ? deps.redact(r.text) : r.text);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性迁移 M19 —— 续跑重放腿上,升级前存下的请求体里引擎判官判不过的 `mcpServers[].toolAxes`,窗内按升级前的读法重读一次。
|
|
3
|
+
* 🔴 M19 · 窗口见 `plugins/one-time-migrations.ts` 的 ONE_TIME_MIGRATIONS 登记表(窗、删法、删后由谁接住只写在那一行)。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么要它(持久数据面)
|
|
6
|
+
* 请求腿读 `toolAxes` 的判官是引擎根导出的 `readMcpToolAxes`(整份一坏全拒、不默认)。升级前的请求腿读者是另一种读法:不是对象(含 `null`)⇒ 整键忽略、
|
|
7
|
+
* server 照挂;是对象 ⇒ 逐成员判形,过不了形的成员剔掉、好的留下。续跑重放腿重放的是**升级前存下的**请求体(`checkpoint_ctx` 里那一份),那时这台 server
|
|
8
|
+
* 是挂着的 —— 一条停驻在它某只工具审批上的任务,升级后若这台 server 整条不进,批准即撞引擎的 `resume.tool_unavailable`、检查点重开、每次重试重放同一份体
|
|
9
|
+
* ⇒ 永远批不过去。持久数据面不能「翻红」,所以给它一次性迁移:窗开着时,续跑重放腿把判官判不过的那一格按升级前的读法重读一次,产物**再交同一只判官**
|
|
10
|
+
* (不过 ⇒ 原臂:整条不进);真挂上的这类 server 由请求腿说一行运维 warn `mcp_tool_axes_read_as_before_upgrade`。窗关之后回到今天的读法,删码零行为变化。
|
|
11
|
+
*
|
|
12
|
+
* ## 可达面(结构上只在续跑重放腿)
|
|
13
|
+
* · 本模块的值只被 `boot/resolve-spec.ts` 引用一处,挂在 `opts.leg` 不是 `"fresh"` 的那一臂(续跑家族经 `http/resume-legs.ts` 的 `resolveResumeSpec`
|
|
14
|
+
* 递 `leg: "resume"`);新提交腿递 `"fresh"` 拿不到重读座,且在腿首已经被同一只判官 400 掉(`assertRequestMcpToolAxes`),读器根本见不到坏形;
|
|
15
|
+
* · 请求腿(`task-mcp.ts`)只 import 本模块的**型**:重读座是 `resolveRequestMcp` 的一个形参,没人递就没有;探测口(`validateRequestMcp`)没有这个形参;
|
|
16
|
+
* · 部署腿(`web-admin-client/skills-mcp.ts` 的 `resolveMcpServers`)是另一只铸 spec 处,不读本模块。
|
|
17
|
+
* 机器锚:`test/mcp-tool-axes-m19.test.ts` 的结构格。
|
|
18
|
+
*/
|
|
19
|
+
import type { McpServerSpec } from "@sema-agent/core";
|
|
20
|
+
type ToolAxes = NonNullable<McpServerSpec["toolAxes"]>;
|
|
21
|
+
/** 重读座:一份原始 `toolAxes` 声明 → 升级前的读法读出的那一份(`undefined` = 升级前读成「没声明」)。产物由调用方再交引擎判官。 */
|
|
22
|
+
export type StoredToolAxesRereadM19 = (declared: unknown) => ToolAxes | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* 此刻(`today` = UTC 日历日 YYYY-MM-DD)M19 的窗开着 ⇒ 交回重读座;关了 ⇒ `undefined`(今天的读法)。窗只读登记表 M19 那一行
|
|
25
|
+
* (`migrationWindowOpen`,与 M10 的认领窗同一只判据)。登记表里没有 M19 ⇒ 响亮抛:重读臂活过了它的登记行,该随那一行一起删。
|
|
26
|
+
*/
|
|
27
|
+
export declare function storedToolAxesRereadM19(today: string): StoredToolAxesRereadM19 | undefined;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=stored-request-tool-axes-m19.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./plugins/one-time-migrations.js";
|
|
2
|
+
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
3
|
+
function readToolAxesAsServer7129M19(declared) {
|
|
4
|
+
if (!declared || typeof declared !== "object" || Array.isArray(declared))
|
|
5
|
+
return undefined;
|
|
6
|
+
const axes = {};
|
|
7
|
+
for (const [tool, ax] of Object.entries(declared)) {
|
|
8
|
+
if (!ax || typeof ax !== "object" || Array.isArray(ax))
|
|
9
|
+
continue;
|
|
10
|
+
const a = ax;
|
|
11
|
+
const entry = {
|
|
12
|
+
...(isToolEffect(a.effect) ? { effect: a.effect } : {}),
|
|
13
|
+
...(typeof a.egress === "boolean" ? { egress: a.egress } : {}),
|
|
14
|
+
...(isToolAxisIrreversibility(a.irreversibility) ? { irreversibility: a.irreversibility } : {}),
|
|
15
|
+
};
|
|
16
|
+
if (Object.keys(entry).length > 0)
|
|
17
|
+
axes[tool] = entry;
|
|
18
|
+
}
|
|
19
|
+
return Object.keys(axes).length > 0 ? axes : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function storedToolAxesRereadM19(today) {
|
|
22
|
+
const window = ONE_TIME_MIGRATIONS.find((w) => w.id === "M19");
|
|
23
|
+
if (window === undefined)
|
|
24
|
+
throw new Error("stored-request-tool-axes-m19: the one-time migration registry has no M19 row (the replay-leg toolAxes re-read window) — the re-read arm outlived its registry row; delete it with that row");
|
|
25
|
+
return migrationWindowOpen(window, today) ? readToolAxesAsServer7129M19 : undefined;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=stored-request-tool-axes-m19.js.map
|
package/dist/task-mcp.d.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { type CompliancePosture, type EngineNotice, type LockedKey, type McpInjectionDropReason, type McpServerSpec } from "@sema-agent/core";
|
|
26
26
|
import { type McpIdentityHeadersIssue, type RequestMcpServerSpec } from "./mcp-assertion-header.js";
|
|
27
|
+
import type { StoredToolAxesRereadM19 } from "./stored-request-tool-axes-m19.js";
|
|
27
28
|
/** Bound on caller-supplied servers (anti-DoS; a real local `.mcp.json` has a handful). */
|
|
28
29
|
export declare const MAX_REQUEST_MCP_SERVERS = 32;
|
|
29
30
|
/**
|
|
@@ -114,6 +115,7 @@ export interface McpInjectionDropSeat {
|
|
|
114
115
|
* Shape-check the raw `body.mcpServers`. `null` → a non-array (caller error; the route may 400). Otherwise the
|
|
115
116
|
* valid subset + the names of dropped (malformed/over-cap) entries + [ref]'s `badContentOrigin`
|
|
116
117
|
* (**降级**掉的内容出处声明,与 `dropped` 是两回事:那条 server **留着**,丢的只是它的声明)。
|
|
118
|
+
* 这一口没有 M19 的重读座(探测口与单测读到的 `toolAxes` 恒是判官的读数);重读只在续跑重放腿经 {@link resolveRequestMcp} 发生。
|
|
117
119
|
*/
|
|
118
120
|
export declare function validateRequestMcp(raw: unknown): ValidatedRequestMcp | null;
|
|
119
121
|
type ValidatedRequestMcp = {
|
|
@@ -208,6 +210,32 @@ export declare function requestEntryIdentityRefusal(entry: unknown): {
|
|
|
208
210
|
errorCode: "request.body_shape" | "request.field_invalid";
|
|
209
211
|
message: string;
|
|
210
212
|
} | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* S-1604 —— `mcpServers[].toolAxes` 的**值域门**(fresh 腿限定,同步 400):判官是引擎根导出的 `readMcpToolAxes`(与部署腿铸 spec 处同一只),
|
|
215
|
+
* 判出 `ok:false` ⇒ `400 request.field_invalid`,点名 `mcpServers[i].toolAxes`、server 名与引擎的原因句(点名工具与成员,不带值)。
|
|
216
|
+
*
|
|
217
|
+
* ## 为什么拒而不是剔
|
|
218
|
+
* `toolAxes` 是调用方对这台 server 每只工具的**双向**风险轴声明(能压低一只工具的效应让审批门放行,也能抬高)。静默剔掉坏成员 = 调用方拿到 2xx、
|
|
219
|
+
* 以为整份声明生效,实际只有另一半在力 —— 一份没有生效的信任根声明。引擎的读法本就是「整份一坏全拒、好的那几只也不留、什么都不默认」,
|
|
220
|
+
* 请求腿修前自铸的读者与它相反;从此只有一只判官,两条腿同读。
|
|
221
|
+
*
|
|
222
|
+
* ## 位置与 leg 分流(与 {@link assertRequestMcpContentOrigin} / {@link assertRequestMcpIdentityHeaders} 同款理由,不发明第二套)
|
|
223
|
+
* 排在腿首、任何副作用之前;**只在 fresh 腿**:`checkpoint_ctx` 存的是原始 `TaskRequestBody`,耐久 resume 逐字重放它,从这里逃出去的 `HttpError`
|
|
224
|
+
* 会被折成 `409 resume_blocked_by_policy`,把一条升级前 park 的任务永久砖死。resume 腿走读器({@link normalizeServer}):同一只判官判出坏形 ⇒ 这一条 server
|
|
225
|
+
* 不进 + `malformed_entry` 点名(运维 warn + 用户面通告,不抛);一次性迁移 M19 的窗开着时先按升级前读法重读一次(见那只读器的注)。
|
|
226
|
+
* 判据是纯值域、与租户车道无关(多租户腿本来就忽略整只数组,告诉调用方「你这条写错了」只多不少)。
|
|
227
|
+
* 探测口读同一只 {@link requestEntryToolAxesRefusal},同一句拒。
|
|
228
|
+
*/
|
|
229
|
+
export declare function assertRequestMcpToolAxes(bodyMcp: unknown): void;
|
|
230
|
+
/**
|
|
231
|
+
* 一条**原始**请求条目上 `toolAxes` 的拒面(码 + 句子);没有问题 / 条目不是对象 / 没写 `toolAxes` ⇒ `undefined`。提交腿的门({@link assertRequestMcpToolAxes})与探测口
|
|
232
|
+
* (`mcp-probe.ts` 的 `entryValueIssue`)两扇拒绝门同读这一只,所以两处是**同一句**。判官是引擎的 `readMcpToolAxes`,句子里的原因句逐字取它的 `problem`
|
|
233
|
+
* (只剥控制与双向字符:工具名是调用方写的);不回显值 —— 引擎的句子点名工具与成员、不带值。`at` = 条目的键路径(`mcpServers[3]`)。
|
|
234
|
+
*/
|
|
235
|
+
export declare function requestEntryToolAxesRefusal(entry: unknown, at: string): {
|
|
236
|
+
errorCode: "request.field_invalid";
|
|
237
|
+
message: string;
|
|
238
|
+
} | undefined;
|
|
211
239
|
/**
|
|
212
240
|
* The ONE predicate for "this MCP server name belongs to the deployment": the scenario baseline (what mounted for
|
|
213
241
|
* this scenario) ∪ every name the deployment DECLARED (`CenterMcp.reservedNames` — mounted or not, any scenario).
|
|
@@ -247,6 +275,9 @@ export declare function resolveRequestMcp(baseline: McpServerSpec[] | undefined,
|
|
|
247
275
|
/** L-167:本条腿的**用户面**投递口。`sessionId` = 通告的路由半场(缺席 ⇒ 一条都不铸,见
|
|
248
276
|
* {@link mintInjectionDropNotices});`emit` 收下 core 铸好的通告,由调用腿决定投到哪(见 `boot/resolve-spec.ts`)。
|
|
249
277
|
* 整段缺席(桩、单测、非 HTTP 腿)⇒ 只走日志终点,行为逐字与本件之前相同。 */
|
|
250
|
-
notice?: McpInjectionDropSeat
|
|
278
|
+
notice?: McpInjectionDropSeat,
|
|
279
|
+
/** 一次性迁移 M19 的重读座(窗口见 `plugins/one-time-migrations.ts` 登记表):只在续跑重放腿、只在窗开着时由 `boot/resolve-spec.ts` 递进来;
|
|
280
|
+
* 缺席(新提交腿 / 窗关 / 桩)⇒ 判官判不过的 `toolAxes` 照原臂整条不进。 */
|
|
281
|
+
storedToolAxes?: StoredToolAxesRereadM19): McpServerSpec[] | undefined;
|
|
251
282
|
export {};
|
|
252
283
|
//# sourceMappingURL=task-mcp.d.ts.map
|
package/dist/task-mcp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mcpInjectionDroppedNotice, resolveComplianceDenies } from "@sema-agent/core";
|
|
1
|
+
import { mcpInjectionDroppedNotice, readMcpToolAxes, resolveComplianceDenies } from "@sema-agent/core";
|
|
2
2
|
import { isUsableWireName } from "./wire-name-bound.js";
|
|
3
3
|
import { HttpError, createCodedHttpError } from "./security.js";
|
|
4
4
|
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
@@ -6,8 +6,8 @@ import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } f
|
|
|
6
6
|
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
7
7
|
import { declaresMcpAssertionHeader, describeMcpAssertionHeaderNotAString, describeMcpIdentityHeadersConflict, mcpIdentityHeadersIssue, noteMcpAssertionHeaderIgnored, noteMcpAssertionHeaderNotSent, readMcpIdentityHeaders, toCoreMcpServerSpec } from "./mcp-assertion-header.js";
|
|
8
8
|
import { echoKeyName } from "./request-key-closure.js";
|
|
9
|
+
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
9
10
|
import { readMcpToolFaces } from "./mcp-tool-faces.js";
|
|
10
|
-
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
11
11
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
12
12
|
export const MAX_REQUEST_MCP_DROP_NAMES = MAX_REQUEST_MCP_SERVERS;
|
|
13
13
|
export const MCP_SERVER_ENTRY_KEYS = ["allowTools", "contentOrigin", "elicitation", "name", "source", "toolAxes", "toolFaces", "transport"];
|
|
@@ -34,7 +34,7 @@ function declaredName(e) {
|
|
|
34
34
|
? String(e.name)
|
|
35
35
|
: MALFORMED_NAME;
|
|
36
36
|
}
|
|
37
|
-
function normalizeServer(e, noteBadOrigin) {
|
|
37
|
+
function normalizeServer(e, noteBadOrigin, storedToolAxes) {
|
|
38
38
|
if (!e || typeof e !== "object")
|
|
39
39
|
return bad("entry");
|
|
40
40
|
const o = e;
|
|
@@ -76,23 +76,20 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
76
76
|
}
|
|
77
77
|
if (typeof o.elicitation === "boolean")
|
|
78
78
|
spec.elicitation = o.elicitation;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
...(typeof a.egress === "boolean" ? { egress: a.egress } : {}),
|
|
88
|
-
...(isToolAxisIrreversibility(a.irreversibility) ? { irreversibility: a.irreversibility } : {}),
|
|
89
|
-
};
|
|
90
|
-
if (Object.keys(entry).length > 0)
|
|
91
|
-
axes[tool] = entry;
|
|
79
|
+
const axes = readMcpToolAxes(o.toolAxes);
|
|
80
|
+
let judged = axes;
|
|
81
|
+
let toolAxesReread;
|
|
82
|
+
if (!axes.ok && storedToolAxes !== undefined) {
|
|
83
|
+
const again = readMcpToolAxes(storedToolAxes(o.toolAxes));
|
|
84
|
+
if (again.ok) {
|
|
85
|
+
judged = again;
|
|
86
|
+
toolAxesReread = axes.problem.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
92
87
|
}
|
|
93
|
-
if (Object.keys(axes).length > 0)
|
|
94
|
-
spec.toolAxes = axes;
|
|
95
88
|
}
|
|
89
|
+
if (!judged.ok)
|
|
90
|
+
return bad("toolAxes");
|
|
91
|
+
if (judged.toolAxes !== undefined)
|
|
92
|
+
spec.toolAxes = judged.toolAxes;
|
|
96
93
|
if (o.contentOrigin !== undefined) {
|
|
97
94
|
if (isContentOriginWord(o.contentOrigin))
|
|
98
95
|
spec.contentOrigin = o.contentOrigin;
|
|
@@ -105,29 +102,32 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
105
102
|
return bad("toolFaces");
|
|
106
103
|
if (faces.faces !== undefined)
|
|
107
104
|
spec.toolFaces = faces.faces;
|
|
108
|
-
return { ok: true, spec };
|
|
105
|
+
return { ok: true, spec, ...(toolAxesReread !== undefined ? { toolAxesReread } : {}) };
|
|
109
106
|
}
|
|
110
107
|
export function validateRequestMcp(raw) {
|
|
111
|
-
if (raw
|
|
112
|
-
return validateRequestMcpEntries([]);
|
|
113
|
-
if (!Array.isArray(raw))
|
|
108
|
+
if (raw !== undefined && raw !== null && !Array.isArray(raw))
|
|
114
109
|
return null;
|
|
115
|
-
|
|
110
|
+
const { ok, dropped, droppedTotal, badContentOrigin } = validateRequestMcpEntries(Array.isArray(raw) ? raw : []);
|
|
111
|
+
return { ok, dropped, droppedTotal, badContentOrigin };
|
|
116
112
|
}
|
|
117
|
-
function validateRequestMcpEntries(raw) {
|
|
113
|
+
function validateRequestMcpEntries(raw, storedToolAxes) {
|
|
118
114
|
const ok = [];
|
|
119
115
|
const dropped = [];
|
|
120
116
|
const badContentOrigin = [];
|
|
117
|
+
const toolAxesReread = [];
|
|
121
118
|
for (const e of raw.slice(0, MAX_REQUEST_MCP_SERVERS)) {
|
|
122
|
-
const v = normalizeServer(e, (server, value) => badContentOrigin.push({ server, value: describeBadContentOrigin(value) }));
|
|
123
|
-
if (v.ok)
|
|
119
|
+
const v = normalizeServer(e, (server, value) => badContentOrigin.push({ server, value: describeBadContentOrigin(value) }), storedToolAxes);
|
|
120
|
+
if (v.ok) {
|
|
124
121
|
ok.push(v.spec);
|
|
122
|
+
if (v.toolAxesReread !== undefined)
|
|
123
|
+
toolAxesReread.push({ server: v.spec.name, problem: v.toolAxesReread });
|
|
124
|
+
}
|
|
125
125
|
else
|
|
126
126
|
dropped.push({ server: declaredName(e), reason: "malformed_entry", field: v.field });
|
|
127
127
|
}
|
|
128
128
|
for (const e of raw.slice(MAX_REQUEST_MCP_SERVERS, MAX_REQUEST_MCP_SERVERS + MAX_REQUEST_MCP_DROP_NAMES))
|
|
129
129
|
dropped.push({ server: declaredName(e), reason: "over_cap" });
|
|
130
|
-
return { ok, dropped, droppedTotal: Math.max(0, raw.length - ok.length), badContentOrigin };
|
|
130
|
+
return { ok, dropped, droppedTotal: Math.max(0, raw.length - ok.length), badContentOrigin, toolAxesReread };
|
|
131
131
|
}
|
|
132
132
|
export function assertRequestMcpUnlocked(bodyMcp, lockedKeys) {
|
|
133
133
|
if (!lockedKeys.has("mcp"))
|
|
@@ -180,6 +180,29 @@ export function requestEntryIdentityRefusal(entry) {
|
|
|
180
180
|
const value = (entry.transport).assertionHeader;
|
|
181
181
|
return { errorCode: "request.field_invalid", message: describeMcpAssertionHeaderNotAString(name, value) };
|
|
182
182
|
}
|
|
183
|
+
export function assertRequestMcpToolAxes(bodyMcp) {
|
|
184
|
+
if (!Array.isArray(bodyMcp))
|
|
185
|
+
return;
|
|
186
|
+
for (let i = 0; i < bodyMcp.length; i++) {
|
|
187
|
+
const refusal = requestEntryToolAxesRefusal(bodyMcp[i], `mcpServers[${i}]`);
|
|
188
|
+
if (refusal !== undefined)
|
|
189
|
+
throw createCodedHttpError(400, refusal.errorCode, refusal.message);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export function requestEntryToolAxesRefusal(entry, at) {
|
|
193
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
194
|
+
return undefined;
|
|
195
|
+
const read = readMcpToolAxes(entry.toolAxes);
|
|
196
|
+
if (read.ok)
|
|
197
|
+
return undefined;
|
|
198
|
+
const server = echoKeyName("", declaredName(entry));
|
|
199
|
+
return {
|
|
200
|
+
errorCode: "request.field_invalid",
|
|
201
|
+
message: `${at}.toolAxes (server "${server}") is not a usable declaration — ${read.problem.replace(CONTROL_AND_BIDI_STRIP_RE, "")}. ` +
|
|
202
|
+
`The whole declaration is refused: the tools that were fine are not kept and nothing is defaulted, because a toolAxes declaration is your own risk-axis judgement for this server's tools ` +
|
|
203
|
+
`(it can move a tool down or up), and a half-applied one would silently differ from what you declared. Fix the entry, or omit the key to declare nothing.`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
183
206
|
export function deploymentClosedMcpNames(baseline, reservedNames) {
|
|
184
207
|
return new Set([...(baseline ?? []).map((s) => s.name), ...reservedNames]);
|
|
185
208
|
}
|
|
@@ -190,20 +213,20 @@ export function mergeRequestMcp(baseline, gated, reservedNames) {
|
|
|
190
213
|
const add = gated.filter((s) => !closed.has(s.name));
|
|
191
214
|
return add.length === 0 ? baseline : [...(baseline ?? []), ...add];
|
|
192
215
|
}
|
|
193
|
-
export function resolveRequestMcp(baseline, bodyMcp, config, logger, notice) {
|
|
216
|
+
export function resolveRequestMcp(baseline, bodyMcp, config, logger, notice, storedToolAxes) {
|
|
194
217
|
if (bodyMcp === undefined || bodyMcp === null)
|
|
195
218
|
return baseline;
|
|
196
|
-
const { mcp, drops } = classifyRequestMcp(baseline, bodyMcp, config, logger);
|
|
219
|
+
const { mcp, drops } = classifyRequestMcp(baseline, bodyMcp, config, logger, storedToolAxes);
|
|
197
220
|
mintInjectionDropNotices(drops, notice);
|
|
198
221
|
return mcp;
|
|
199
222
|
}
|
|
200
|
-
function classifyRequestMcp(baseline, bodyMcp, config, logger) {
|
|
223
|
+
function classifyRequestMcp(baseline, bodyMcp, config, logger, storedToolAxes) {
|
|
201
224
|
if (bodyMcp !== undefined && bodyMcp !== null && !Array.isArray(bodyMcp)) {
|
|
202
225
|
logger?.warn("mcp_injection_ignored", { reason: "mcpServers not an array" });
|
|
203
226
|
return { mcp: baseline, drops: [] };
|
|
204
227
|
}
|
|
205
228
|
const entries = Array.isArray(bodyMcp) ? bodyMcp : [];
|
|
206
|
-
const outcome = mcpInjectionHonored(config) ? classifyHonored(baseline, entries, config, logger) : classifyGateClosed(baseline, entries);
|
|
229
|
+
const outcome = mcpInjectionHonored(config) ? classifyHonored(baseline, entries, config, logger, storedToolAxes) : classifyGateClosed(baseline, entries);
|
|
207
230
|
if (outcome.drops.length > 0)
|
|
208
231
|
logger?.warn("mcp_injection_dropped", { dropped: outcome.drops, droppedTotal: outcome.droppedTotal });
|
|
209
232
|
return { mcp: outcome.mcp, drops: outcome.drops };
|
|
@@ -215,8 +238,8 @@ function classifyGateClosed(baseline, entries) {
|
|
|
215
238
|
droppedTotal: entries.length,
|
|
216
239
|
};
|
|
217
240
|
}
|
|
218
|
-
function classifyHonored(baseline, entries, config, logger) {
|
|
219
|
-
const v = validateRequestMcpEntries(entries);
|
|
241
|
+
function classifyHonored(baseline, entries, config, logger, storedToolAxes) {
|
|
242
|
+
const v = validateRequestMcpEntries(entries, storedToolAxes);
|
|
220
243
|
if (v.badContentOrigin.length > 0) {
|
|
221
244
|
logger?.warn("mcp_content_class_dropped", {
|
|
222
245
|
entries: v.badContentOrigin,
|
|
@@ -236,6 +259,16 @@ function classifyHonored(baseline, entries, config, logger) {
|
|
|
236
259
|
if (issue !== undefined && mounted.has(declaredName(e)))
|
|
237
260
|
noteMcpAssertionHeaderIgnored(logger, declaredName(e), issue);
|
|
238
261
|
}
|
|
262
|
+
for (const r of v.toolAxesReread) {
|
|
263
|
+
if (!mounted.has(r.server))
|
|
264
|
+
continue;
|
|
265
|
+
logger?.warn("mcp_tool_axes_read_as_before_upgrade", {
|
|
266
|
+
server: r.server,
|
|
267
|
+
migration: "M19",
|
|
268
|
+
problem: r.problem,
|
|
269
|
+
note: "this replayed request was stored before the upgrade and its toolAxes declaration does not pass the engine's reader; while one-time migration M19 is open it is read the way the pre-upgrade server read it (members that did not fit are dropped, a declaration that is not an object counts as none) and the server stays mounted — once the window closes the same request leaves this server unmounted",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
239
272
|
return {
|
|
240
273
|
mcp: mergeRequestMcp(baseline, v.ok.map(toCoreMcpServerSpec), reservedNames),
|
|
241
274
|
drops: [...v.dropped, ...blocked.map((server) => ({ server, reason: "name_reserved_by_deployment" }))],
|
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* S-249(DT-13)—— per-tool
|
|
2
|
+
* S-249(DT-13)—— per-tool **风险轴覆写**的值域词表与谓词(读者:**A2A 请求腿**;以及一次性迁移 M19 的升级前读法 `stored-request-tool-axes-m19.ts`,随 M19 的登记行退役)。
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
5
|
-
* `task-mcp.ts` 与 `task-a2a.ts` 各自在请求腿上逐键校验同一个三成员结构
|
|
6
|
-
* (
|
|
7
|
-
* (两文件 × 两个词轴),没有一份钉在 core 的型上。后果不是「今天判错了」,而是**判据比声明窄**这件事
|
|
8
|
-
* 没有任何门看得见 —— core 哪天给 `ToolEffect` 加一个词(或把 `irreversibility` 的两词改成三词,它在
|
|
9
|
-
* `AskOrigin` 那一面**已经**是 `never|maybe|always` 三词了),两条请求腿会把那个**合法**的新词
|
|
10
|
-
* **静默剔除**(MCP 腿)或**整条 server 判假**(A2A 腿),而 tsc 一声不吭。
|
|
4
|
+
* ## 历史与现状
|
|
5
|
+
* 修前 `task-mcp.ts` 与 `task-a2a.ts` 各自在请求腿上逐键校验同一个三成员结构(`effect` / `egress` / `irreversibility`),两个**闭集词表**是手抄的字面串,
|
|
6
|
+
* 没有一份钉在 core 的型上;S-249 把词表收到本文件并**两向钉死在 core 的型上**(core 加词 ⇒ 正向门红;本表留了 core 已没有的词 ⇒ 反向门红),两条腿读同一只谓词。
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* 之后 MCP 面的 `toolAxes` 判官整个换给了**引擎**:core 7.51.2 起根导出 `readMcpToolAxes`(整份声明一坏全拒、不默认),部署腿(`web-admin-client/skills-mcp.ts`)
|
|
9
|
+
* 与请求腿(`task-mcp.ts`)都问它,本仓不再有 MCP 面的自铸读者、也不再 import 本文件的谓词。**A2A 面引擎没有对应的判官**(core 暂无 `readA2aToolAxes`),
|
|
10
|
+
* 所以 A2A 请求腿(`task-a2a.ts`)仍用本文件的谓词;引擎一旦给 A2A 也立一只判官,本文件随之退役。另一个读者是 M19 的迁移读者:续跑重放腿把升级前存下的
|
|
11
|
+
* `mcpServers[].toolAxes` 按升级前那只读者的读法重读一次(产物再交引擎的判官),那段读法当年就读本文件的谓词,逐字搬回;它不是 MCP 面的第二只判官。
|
|
15
12
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* A2A
|
|
13
|
+
* 下面的同型钉(A2A 的轴型与 MCP 的轴型逐字同声明)仍有意义:A2A 的词表跟着引擎 MCP 面的轴型走,哪一边加词、改词,另一边在这里当场红。
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **只管值域,不管处置**:A2A 腿对坏值的处置是整条拒(MCP 面是引擎判官的「整份全拒」;两面方向一致),M19 的迁移读者剔坏成员(升级前的读法),判据与理由写在各自的调用点。
|
|
19
16
|
*/
|
|
20
17
|
import type { McpServerSpec, ToolEffect } from "@sema-agent/core";
|
|
21
|
-
/** 一条 per-tool 轴覆写的形。取 MCP 那一面作代表 ——
|
|
18
|
+
/** 一条 per-tool 轴覆写的形。取 MCP 那一面作代表 —— 下面的同型钉证明 A2A 面与它逐字同声明。 */
|
|
22
19
|
export type ToolAxisEntry = NonNullable<McpServerSpec["toolAxes"]>[string];
|
|
23
20
|
/** 轴覆写上那个**两词**的不可逆档(⚠️ 与 `AskOrigin` 面的**三词** `never|maybe|always` 不是同一个词表:
|
|
24
21
|
* 覆写面只收两极,`maybe` 是引擎按工具自己的声明定的,调用方说不出口)。 */
|
|
25
22
|
export type ToolAxisIrreversibility = NonNullable<ToolAxisEntry["irreversibility"]>;
|
|
26
|
-
/** 值是不是一个合法的 `effect` 词(
|
|
23
|
+
/** 值是不是一个合法的 `effect` 词(A2A 请求腿与 M19 的迁移读者读它)。 */
|
|
27
24
|
export declare function isToolEffect(v: unknown): v is ToolEffect;
|
|
28
|
-
/** 值是不是一个合法的**覆写面** `irreversibility` 词(
|
|
25
|
+
/** 值是不是一个合法的**覆写面** `irreversibility` 词(A2A 请求腿与 M19 的迁移读者读它)。 */
|
|
29
26
|
export declare function isToolAxisIrreversibility(v: unknown): v is ToolAxisIrreversibility;
|
|
30
27
|
//# sourceMappingURL=tool-axis-words.d.ts.map
|
|
@@ -27,7 +27,7 @@ export declare function applySkillsManifest(baseline: LoadedSkill[], manifest: {
|
|
|
27
27
|
* Resolve center MCP servers (`/effective.mcp`) into core `McpServerSpec`s + the scenarios each applies to.
|
|
28
28
|
* env-NAME refs → real values from THIS service's env (secret boundary unchanged); a server with any missing
|
|
29
29
|
* ref is skipped (`resolveRefs` → null). `enabled:false` skipped. A server whose `toolFaces` / `toolAxes` declaration is
|
|
30
|
-
* malformed is skipped whole (both are trust-root declarations — see `mcp-tool-faces.ts` / `
|
|
30
|
+
* malformed is skipped whole (both are trust-root declarations — see `mcp-tool-faces.ts` / the engine's `readMcpToolAxes`). Restart-to-apply
|
|
31
31
|
* (filtered per scenario in `resolveSpec` via `mcpForScenario`).
|
|
32
32
|
*/
|
|
33
33
|
export declare function resolveMcpServers(mcp: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.130.0",
|
|
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",
|