@sema-agent/server 7.84.0 → 7.85.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/USAGE.md CHANGED
@@ -669,8 +669,12 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
669
669
  「读工作区外的文件」从被拒变成放行。**要钉回收敛姿态:显式 `READ_FACE=roots`。**
670
670
  · **多租户**(`REQUIRE_PRINCIPAL=true`):缺席仍**不铸**、不传给引擎(引擎默认 `roots`)——**一个字节不变**。
671
671
  · `READ_DENY_PATTERNS` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE` 三键的「缺席不铸」
672
- **不变**;**内建读拒表照旧生效** —— `open` 放开的是「工作区外」这道围栏,凭证 / shell 历史 / 浏览器 /
673
- 钱包那几档 deny 在 `open` 面上照样拦(两者在引擎里是并列机制)。
672
+ **不变**。⚠️ **但「不铸」在引擎里的读法自 core 7.22.0(内置读 deny 表出厂 OFF 的裁定)起翻了面**:`READ_DENY_BUILTIN_TIERS`
673
+ 未设 = **OFF**(内建表一行不生效),此前是「除 shell-history 外四档全开」。⇒ 一台没配过这根 env 的机器
674
+ 升级后**读得到**根内的 `.npmrc` / `.ssh` / `.aws` / `.claude/settings.json` 这类名字,且整个递归读族
675
+ 零卡执行。**要老姿态就显式写** `READ_DENY_BUILTIN_TIERS=credentials,browser,wallet,agent-config`
676
+ (运维面全文见 `docs/DEPLOY-PREREQS.md` 的 core 7.22.0 段)。`open` 与这张表仍是**两个并列机制**:
677
+ `open` 放开的是「工作区外」那道围栏,内建表判的是名字 —— 只是后者现在默认空着。
674
678
  · 「这一档到底是谁定的」由 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace` 段答:
675
679
  `{face, source, note}`,`source` ∈ `env`(本机 env 显式)/ `center`(组织下发)/ `posture`(单机派生)/
676
680
  `engine-default`(没钉,引擎当家),`note` 是指路句。
@@ -290,6 +290,13 @@ export type ReadRootCandidateProjection = ReadRootGrantCandidate;
290
290
  * 🔴 形不合 ⇒ **按缺席处置**;超限 ⇒ **整只丢**(见 {@link MAX_READ_ROOT_CANDIDATE_DIR})。
291
291
  * 🔴 **缺席不是断言**:它同时覆盖「这一族 ask 没有任何目录能清掉」(敏感路径 deny 行、读边界压根读不懂
292
292
  * 的命令、递归遍历、非读边界 ask……)与「老引擎」两形 —— 消费端只读在场,永远不读缺席。
293
+ *
294
+ * 🔴 **逐成员挑键的代价,与它的解药**(core 7.22.0 #862b 真发生):下面这条 `return` 是**逐键重建**的,
295
+ * 而返回型是 core 的 `ReadRootGrantCandidate` ⇒ core 给这只座加可选成员时,新成员被**静默剥掉**且
296
+ * tsc 一声不响(7.22.0 的 `covers` 就是这样被吞过一次:消费端拿到一条看起来正常的「目录出路」,按目录
297
+ * 回授,卡清不掉 —— 而那正是这个成员存在的唯一理由)。解药**不是**改成整只透传(那会让未经校验的远端
298
+ * 形上 wire),而是 `src/trace/core-keyset-guard.ts` ⑭ 的**成员级编译门** `_GuardReadRootCandidate`:
299
+ * core 加成员 ⇒ 那里当场红,由人判该不该投。**改本函数时必须同步那张表。**
293
300
  */
294
301
  export declare function readReadRootCandidate(req: unknown): ReadRootCandidateProjection | undefined;
295
302
  /**
@@ -162,7 +162,7 @@ export function readProbeCause(req) {
162
162
  }
163
163
  export const MAX_READ_ROOT_CANDIDATE_DIR = 1024;
164
164
  const ReadRootCandidateEnvelopeSchema = z.object({
165
- readRootCandidate: z.object({ dir: z.string().min(1), clearsThisAsk: z.literal(true) }).optional(),
165
+ readRootCandidate: z.object({ dir: z.string().min(1), clearsThisAsk: z.literal(true), covers: z.literal("exact").optional() }).optional(),
166
166
  });
167
167
  export function readReadRootCandidate(req) {
168
168
  const parsed = ReadRootCandidateEnvelopeSchema.safeParse(req);
@@ -171,7 +171,7 @@ export function readReadRootCandidate(req) {
171
171
  return undefined;
172
172
  if (raw.dir.length > MAX_READ_ROOT_CANDIDATE_DIR)
173
173
  return undefined;
174
- return { dir: raw.dir, clearsThisAsk: true };
174
+ return { dir: raw.dir, clearsThisAsk: true, ...(raw.covers !== undefined ? { covers: raw.covers } : {}) };
175
175
  }
176
176
  const MAX_EVIDENCE_DOT_ACTOR = 256;
177
177
  const MAX_EVIDENCE_DOTS = 16;
@@ -1,4 +1,4 @@
1
- import { defaultTaskRegistry } from "@sema-agent/core";
1
+ import { autoRunOnRecommendationEffective, defaultTaskRegistry } from "@sema-agent/core";
2
2
  import { posIntEnv } from "../session-watch.js";
3
3
  import { brainSummary } from "../brain.js";
4
4
  import { resolveBindHost } from "../config.js";
@@ -82,6 +82,7 @@ export async function runStage10(ctx) {
82
82
  memoryCompliance: memoryComplianceFaces,
83
83
  memoryOriginFace: memoryOriginFaces,
84
84
  sessionMemoryStatus: sessionMemoryStatusFace,
85
+ memoryAutoConsolidationArmed: autoRunOnRecommendationEffective(runnerDeps),
85
86
  memoryConsolidation: memoryConsolidationFaces,
86
87
  memoryOptOutGrant: memoryOptOutGrantStore,
87
88
  orgMemoryDirectory: orgMemoryAdmission.directory,
@@ -342,7 +342,7 @@ export const CONFIG_CATALOG = [
342
342
  r("QUESTION_TTL_MS", "approval", "number", "AskUserQuestion 无人应答释放窗(ms)", { derivedDefaultNote: "缺席 ⇒ 出厂窗 300000" }),
343
343
  r("RATE_LIMIT_RPM", "limitsHttp", "number", "计费提交每分钟限流(0=关)", { staticDefault: "0", danger: BILL, configKey: "rateLimitPerMin", center: cLimits("rateLimitPerMin") }),
344
344
  r("READ_DENY_BUILTIN_EXCLUDE", "orchestration", "csv", "按行名删内建 deny 条目(档位之后生效;绝不削任务显式 deny)", { danger: SEC, configKey: "readDenyBuiltinExclude", center: cRead("denyBuiltinExclude", "readDenyBuiltinExclude") }),
345
- r("READ_DENY_BUILTIN_TIERS", "orchestration", "csv", "内建 deny 表档位选择(闭集五档 credentials/shell-history/browser/wallet/agent-config;none=显式空姿态)", { danger: SEC, configKey: "readDenyBuiltinTiers", center: cRead("denyBuiltinTiers", "readDenyBuiltinTiers") }),
345
+ r("READ_DENY_BUILTIN_TIERS", "orchestration", "csv", "内建 deny 表档位选择(闭集五档 credentials/shell-history/browser/wallet/agent-config;none=显式空姿态)", { danger: SEC, derivedDefaultNote: "🔴 缺席 ⇒ **OFF**(core 7.22.0 / C-R60 起内建表出厂全关:根内凭据名可读、整个递归读族零卡)。要 7.21.x 姿态就显式写 credentials,browser,wallet,agent-config;空值拒启(手滑与显式意图不许同形)", configKey: "readDenyBuiltinTiers", center: cRead("denyBuiltinTiers", "readDenyBuiltinTiers") }),
346
346
  r("READ_DENY_PATTERNS", "orchestration", "csv", "READ 面额外 deny pattern 表", { danger: SEC, configKey: "readDenyPatterns", center: cRead("denyPatterns", "readDenyPatterns") }),
347
347
  r("READ_FACE", "orchestration", "enum", "READ 容纳面档位(env 逐键恒赢 center —— 部署主权表达)", { enumValues: ["open", "roots"], danger: SEC, derivedDefaultNote: "缺席 ⇒ 按 posture 派生(S-167):单用户 turnkey(REQUIRE_PRINCIPAL 未设)铸 open;多租户不铸键、core 默认 roots。生效来源见 GET /v1/diagnostics/wiring 的 readFace.source 四词", configKey: "readFace", center: cRead("face", "readFace") }),
348
348
  r("REAP_INTERVAL_SEC", "limitsHttp", "number", "reaper 周期(秒)", { staticDefault: "60", configKey: "reapIntervalSec" }),
@@ -1740,7 +1740,11 @@ export interface ServiceConfigFlat {
1740
1740
  * 行名的是**文件**不是配置**目录**(`.sema`/`.claude` 底下还住着 agents/skills/hooks 与记忆库 —— 模型
1741
1741
  * 当作日常材料读写的东西);同族工具的 token **存储**(`.credentials.json`/`.codex/auth.json` …)归
1742
1742
  * `credentials` 档,不归本档 ⇒ 为排查 agent 配置而关掉本档的部署,凭证行照样拦着。
1743
- * 缺席=不铸 ⇒ core 自默认(除 shell-history 外全开)。**仅 deployment 席**:TaskSpec 上没有同名键,
1743
+ * 🔴 **缺席 = 不铸 ⇒ OFF**(core 7.22.0 / [ref] 翻面;7.21.x 及以前是「除 shell-history 外全开」)。
1744
+ * 未配置的部署从内建表里**什么都不拒**,而且 shell 读边界的 deny 判官席 present-iff 编译集有条目 ⇒
1745
+ * 递归读降级臂也随之不接线(`grep -r` 一族零卡)。要老姿态**必须显式写下**
1746
+ * `credentials,browser,wallet,agent-config`。本仓**不复制上游缺省**:这里只记「不铸」,解析归引擎。
1747
+ * **仅 deployment 席**:TaskSpec 上没有同名键,
1744
1748
  * 任务/受治脚本无法据此放宽自己的部署(core 侧的设计,与本仓「部署级旋钮无条件施加」同向)。 */
1745
1749
  readDenyBuiltinTiers?: readonly string[];
1746
1750
  /** [ref] 件 G5:按**行名**(内建表的 canonical pattern)删内建条目,在档位选择**之后**生效。
@@ -549,6 +549,22 @@ export declare function acceptedResumingReceipt(taskId: string, sessionId: strin
549
549
  status: number;
550
550
  body: object;
551
551
  };
552
+ /**
553
+ * 「**证得出**这条 run 活在别的副本上」——「他副本」那一句话的**唯一判据**(S-381 收编)。
554
+ *
555
+ * 成案:这个合取式此前在四处**逐字手抄**(`routes/runs.ts` 的 steer 臂与 interrupt 臂、同文件
556
+ * `sendHandleMiss`、`routes/run-memory-capture.ts`),第五处就要随 `POST /v1/sessions/:id/mcp/reconnect`
557
+ * 出现 —— 而它的每一个合取项都是一条**被复审逐条挣出来的**纪律,不是随手的判空:
558
+ * · `run.instanceId` 缺席(`null` / 建列之前的行 / 替身行的 `undefined`)= **不知道它在哪**,不是「在别处」;
559
+ * · `thisInstanceId` 缺席 = 本副本连自己是谁都说不上,更不能断言别人;
560
+ * · 两者都在场且**不等**才够格说「他副本」(`markResuming` 认领后该列语义 = **当前持有副本**)。
561
+ * 抄第五份 = 第五个写者;收成一只谓词之后,纪律改一次就四处同改。**`status` 不进本谓词**:
562
+ * `sendHandleMiss` 只对 `running` 行说这句话,而 steer 族在 park/终态两臂之后才走到这里 —— 各调用点
563
+ * 的前置臂不同族,合进来会让某一处悄悄改判。
564
+ */
565
+ export declare function runProvenElsewhere(run: {
566
+ instanceId?: string | null;
567
+ }, thisInstanceId: string | undefined): boolean;
552
568
  export declare const RUN_NOT_FOUND_MESSAGE = "run not found \u2014 the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
553
569
  /** [ref] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
554
570
  export declare function runNotFoundMessage(id: string): string;
@@ -24,6 +24,9 @@ export const ROUTE_METHOD_ORDER = ["GET", "POST", "PUT", "DELETE"];
24
24
  export function acceptedResumingReceipt(taskId, sessionId) {
25
25
  return { status: 200, body: { taskId, sessionId, status: "resuming", bindingEnforced: true } };
26
26
  }
27
+ export function runProvenElsewhere(run, thisInstanceId) {
28
+ return typeof run.instanceId === "string" && run.instanceId.length > 0 && thisInstanceId !== undefined && run.instanceId !== thisInstanceId;
29
+ }
27
30
  export const RUN_NOT_FOUND_MESSAGE = "run not found — the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
28
31
  export function runNotFoundMessage(id) {
29
32
  const aStar = /^(?:a|wa)[0-9a-f]{4,}$/i.test(id);
@@ -28,6 +28,7 @@ import { handleNotifyWake, NOTIFY_WAKE_ROUTES } from "./routes/notify-wake.js";
28
28
  import { handleApprovalsAssistant, APPROVALS_ASSISTANT_ROUTES } from "./routes/approvals-assistant.js";
29
29
  import { handleRuns, handleRunVerbs, RUNS_ROUTES, RUN_VERBS_ROUTES } from "./routes/runs.js";
30
30
  import { handleRunMemoryCapture, RUN_MEMORY_CAPTURE_ROUTES } from "./routes/run-memory-capture.js";
31
+ import { handleMcpReconnect, MCP_RECONNECT_ROUTES } from "./routes/mcp-reconnect.js";
31
32
  import { handleSideQuery, SIDE_QUERY_ROUTES } from "./routes/side-query.js";
32
33
  import { handleTasks, TASKS_ROUTES } from "./routes/tasks.js";
33
34
  import { handleLeader, LEADER_ROUTES } from "./routes/leader.js";
@@ -48,6 +49,7 @@ export const ROUTES = [
48
49
  row({ handler: handleWorkflowAgentSteer, decls: WORKFLOW_AGENT_STEER_ROUTES, position: "post-credential", label: "workflow-agent-steer" }),
49
50
  row({ handler: handleRunVerbs, decls: RUN_VERBS_ROUTES, position: "post-credential", label: "run-verbs" }),
50
51
  row({ handler: handleRunMemoryCapture, decls: RUN_MEMORY_CAPTURE_ROUTES, position: "post-credential", label: "run-memory-capture" }),
52
+ row({ handler: handleMcpReconnect, decls: MCP_RECONNECT_ROUTES, position: "post-credential", label: "mcp-reconnect" }),
51
53
  row({ handler: handleLeader, decls: LEADER_ROUTES, position: "post-credential", label: "leader", enabled: (deps) => Boolean(deps.leaderEndpoint) }),
52
54
  row({ handler: handleImages, decls: IMAGES_ROUTES, position: "post-credential", label: "images" }),
53
55
  row({ handler: handleApprovalsAssistant, decls: APPROVALS_ASSISTANT_ROUTES, position: "post-credential", label: "approvals-assistant", enabled: (deps) => Boolean(deps.checkpointStore) }),
@@ -80,6 +80,7 @@ export async function handleCapabilities(req, res, match, ctx) {
80
80
  webSearch: projectWebSearchCapability(deps.webSearchProvider),
81
81
  sessionBackgroundable: Boolean(deps.runStore),
82
82
  runMemoryCaptureOptOut: Boolean(deps.runStore),
83
+ mcpReconnect: Boolean(deps.runStore) && Boolean(deps.sessionStorage?.ownerOf),
83
84
  sharedMemory: Boolean(deps.sharedMemoryStore && deps.orgMemoryDirectory),
84
85
  adoption: Boolean(deps.backend?.adoptionLog) && deps.config.operatorPrincipals.length > 0,
85
86
  memoryBundle: Boolean(deps.memoryBundleExport && deps.memoryBundleImport) && deps.config.operatorPrincipals.length > 0,
@@ -0,0 +1,56 @@
1
+ /**
2
+ * S-381(core 7.22.0 [ref] 的 server 半场;提货单 [ref] ⓪ @server 第 3 条 / 判据帖 [ref] G1)——
3
+ * **会话内 MCP re-dial** 的 wire 面:
4
+ *
5
+ * `POST /v1/sessions/:id/mcp/reconnect` body `{ server: string }`
6
+ * → 200 `{ taskId, sessionId, server, outcome: "accepted" | "refused" | "unsupported", … }`
7
+ *
8
+ * 语义 = core `TaskStream.reconnectMcpServer(server)`:传输断了的那台 MCP server,在**不结束任务**的前提下
9
+ * 重拨一次号。**一台具名 server,必需,没有全部形**(core 原话:全体形会 CLOSE 掉健康连接,而拥有设备/端口的
10
+ * stdio server 恰恰是这个动词存在的理由)。
11
+ *
12
+ * ── 🔴 一次交易,不是一个「刷新」按钮(core 原话,必须进 UI 文案与契约)───────────────────────────────
13
+ * **失败的重拨让 server 留在断线状态** —— 旧连接在拨新号**之前**就被关(对拥有设备/端口的 stdio server 没有
14
+ * 别的顺序可用),所以一次失败的拨号花掉了一条能用的连接:server 保持断开、**它的工具从模型名册撤走**,在飞
15
+ * 调用随之失败。**在一台健康的 server 上提供「重连」按钮 = 提供一次交易**。契约 §G.7 与本面的 `outcome`
16
+ * 三词都按这句话写;壳不许把它渲成无代价的刷新。
17
+ *
18
+ * ── 三态映射(`unsupported` 是**本仓自己的词**,core 无此词)──────────────────────────────────────
19
+ * · core `reconnected` → `accepted`
20
+ * · core `refused` → `refused`
21
+ * · core `not_declared` → `refused`(未知 server 名是**名册事实**,不是引擎能力事实)
22
+ * · core **抛** `mcp.reconnect_unavailable`(本 run 未声明任何 MCP)→ `unsupported`
23
+ * 🔴 **`outcome` 是唯一判别键**:三态**全部走 200**。HTTP 状态只回答「动词有没有到达引擎」——与 steer 回执的
24
+ * 「branch on `delivery`, not the status」同一条律。把 `refused` 挂到 4xx 会让同一件事有两个判别键,而
25
+ * `unsupported` 挂 409 更会把「这条 run 没有 MCP 面」谎报成一个活性问题。
26
+ *
27
+ * ── 下一步动作按 `status`,不解析 `reason`(core [ref] 键表逐字)────────────────────────────────
28
+ * `status.errorCode` / `status.httpStatus` 是机器面(`http_status` 401 是一扇重新授权的门,`spawn_failed` 是
29
+ * 缺二进制);`reason` 是**人读**的一句话,经本仓 `redactSecrets` + 定长截断后投出(它可能嵌远端文本),
30
+ * 消费端**不许**解析它。名册**按 `tools` 的在场性**投成 `toolNames`(present-iff),**不按 `outcome`**。
31
+ *
32
+ * ── 查找与拒绝形(与 steer / capture-optout **同一只**,不新造)───────────────────────────────────
33
+ * 路径是 session,动词挂在 **run 句柄**上 ⇒ 会话属主门(`sessionStorage.ownerOf`,未知与他人**同码同串**
34
+ * 404 无 oracle,与 `POST /v1/sessions/:id/notify` 逐字同形)→ `getActiveTaskId` 取本会话的活 run →
35
+ * `steerableRuns` 取本副本活流。非 live 的四臂一码 409 `steering.not_running`,判据取词表属主
36
+ * (`isParkedRunStatus` / `isTerminalRunStatus`)与 `runProvenElsewhere`(四处手抄已收编成一只谓词)。
37
+ * 显式 operator 可重拨任何租户的会话(监督动作,与 notify / steer 同臂;**不是** bare `isOperator`)。
38
+ *
39
+ * ── 计费/门 ─────────────────────────────────────────────────────────────────────────────────────
40
+ * 零模型工作(不进 billable;不吃 drain / roster-pending);mutating ⇒ `rateLimited`。能力位 `mcpReconnect`
41
+ * = 持久 run 账面 ∧ 会话属主面(两道 501 的同一批 deps 推导出来,「说 yes 但 501」结构上不可能)。
42
+ */
43
+ import type { IncomingMessage, ServerResponse } from "node:http";
44
+ import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
45
+ /** 本面的三词闭集 —— **`unsupported` 是本仓的词**(core 的三词是 `reconnected`/`refused`/`not_declared`)。 */
46
+ export type McpReconnectOutcome = "accepted" | "refused" | "unsupported";
47
+ export declare function handleMcpReconnect(req: IncomingMessage, res: ServerResponse, match: RouteMatch<McpReconnectRouteId>, ctx: RouteCtx): Promise<void>;
48
+ export declare const MCP_RECONNECT_ROUTES: readonly [{
49
+ readonly id: "mcp-reconnect";
50
+ readonly pattern: RegExp;
51
+ readonly label: "/v1/sessions/:id/mcp/reconnect";
52
+ readonly methods: readonly ["POST"];
53
+ }];
54
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
55
+ export type McpReconnectRouteId = RouteIdsOf<typeof MCP_RECONNECT_ROUTES>;
56
+ //# sourceMappingURL=mcp-reconnect.d.ts.map
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-contracts.js";
3
+ import { redactSecrets } from "../../trace/redact.js";
4
+ import { sendJson, sendError } from "../send.js";
5
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
+ import { runProvenElsewhere } from "../route-ctx.js";
7
+ import { assertNever } from "../route-ctx.js";
8
+ const BodySchema = z.object({ server: z.string().min(1).max(190) }).strict();
9
+ const NEUTRALIZED_TEXT_MAX = 200;
10
+ const humanText = (s) => redactSecrets(s).slice(0, NEUTRALIZED_TEXT_MAX);
11
+ function outcomeOf(coreOutcome) {
12
+ switch (coreOutcome) {
13
+ case "reconnected": return "accepted";
14
+ case "refused": return "refused";
15
+ case "not_declared": return "refused";
16
+ default: return assertNever(coreOutcome);
17
+ }
18
+ }
19
+ function projectStatus(s) {
20
+ return {
21
+ name: s.name,
22
+ status: s.status,
23
+ ...(s.errorCode !== undefined ? { errorCode: s.errorCode } : {}),
24
+ ...(s.httpStatus !== undefined ? { httpStatus: s.httpStatus } : {}),
25
+ ...(s.delivered !== undefined ? { delivered: s.delivered } : {}),
26
+ ...(s.serverInfo !== undefined ? { serverInfo: s.serverInfo } : {}),
27
+ ...(s.error !== undefined ? { error: humanText(String(s.error)) } : {}),
28
+ ...(s.transportClosed !== undefined ? { transportClosed: s.transportClosed } : {}),
29
+ };
30
+ }
31
+ function projectListingIncomplete(l) {
32
+ return {
33
+ reason: l.reason,
34
+ pages: l.pages,
35
+ ...(l.budgetMs !== undefined ? { budgetMs: l.budgetMs } : {}),
36
+ ...(l.error !== undefined ? { error: humanText(l.error) } : {}),
37
+ };
38
+ }
39
+ function projectResult(r) {
40
+ return {
41
+ server: r.server,
42
+ outcome: outcomeOf(r.outcome),
43
+ prefix: r.prefix,
44
+ toolCount: r.toolCount,
45
+ added: r.added,
46
+ removed: r.removed,
47
+ ...(r.tools !== undefined ? { toolNames: r.tools.map((t) => t.name) } : {}),
48
+ ...(r.reason !== undefined ? { reason: humanText(r.reason) } : {}),
49
+ ...(r.status !== undefined ? { status: projectStatus(r.status) } : {}),
50
+ ...(r.listingIncomplete !== undefined ? { listingIncomplete: projectListingIncomplete(r.listingIncomplete) } : {}),
51
+ };
52
+ }
53
+ export async function handleMcpReconnect(req, res, match, ctx) {
54
+ const { deps } = ctx;
55
+ switch (match.decl.id) {
56
+ case "mcp-reconnect": {
57
+ if (ctx.helpers.rateLimited(req, res))
58
+ return;
59
+ if (!deps.runStore) {
60
+ sendError(res, 501, "capability.run_store_required", "async runs require a durable run store (DB_BACKEND=mysql|pg|local)");
61
+ return;
62
+ }
63
+ const principal = gatedPrincipal(req, deps.config);
64
+ if (deps.config.requirePrincipal && principal === undefined) {
65
+ sendPrincipalRefusal(res, req, deps.config);
66
+ return;
67
+ }
68
+ const parsed = BodySchema.safeParse((await ctx.helpers.readJson(req)) ?? {});
69
+ if (!parsed.success) {
70
+ sendError(res, 400, "request.body_shape", "body must be { server: string (non-empty, at most 190 characters) } — one NAMED MCP server, required; there is no all-servers form (re-dialing every server would CLOSE the healthy ones, and a stdio server that owns a device cannot be re-dialed while the old connection still holds it)");
71
+ return;
72
+ }
73
+ const server = parsed.data.server;
74
+ const sessionId = ctx.helpers.safeDecode(match.params.sessionId);
75
+ if (sessionId === null) {
76
+ sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
77
+ return;
78
+ }
79
+ if (!deps.sessionStorage?.ownerOf) {
80
+ sendError(res, 501, "capability.session_ownership_required", "the MCP re-dial verb requires the session-ownership face (sessionStorage.ownerOf)");
81
+ return;
82
+ }
83
+ const owner = await deps.sessionStorage.ownerOf(sessionId);
84
+ if (owner === undefined) {
85
+ sendError(res, 404, "not_found.session", "session not found");
86
+ return;
87
+ }
88
+ const isOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
89
+ if (!isOperator && !ctx.helpers.runOwnerOk(req, res, owner, "session"))
90
+ return;
91
+ const activeTaskId = await deps.runStore.getActiveTaskId(sessionId);
92
+ const live = activeTaskId === undefined ? undefined : ctx.registry.steerableRuns.get(activeTaskId);
93
+ if (live) {
94
+ try {
95
+ const result = await live.reconnectMcpServer(server);
96
+ sendJson(res, 200, { taskId: activeTaskId, sessionId, ...projectResult(result) });
97
+ return;
98
+ }
99
+ catch (e) {
100
+ const code = e.code;
101
+ const msg = e instanceof Error ? e.message : String(e);
102
+ switch (code) {
103
+ case "mcp.reconnect_unavailable":
104
+ sendJson(res, 200, { taskId: activeTaskId, sessionId, server, outcome: "unsupported", reason: humanText(msg) });
105
+ return;
106
+ case "steering.invalid_content":
107
+ sendError(res, 422, "steering.invalid_content", msg);
108
+ return;
109
+ case "steering.not_running":
110
+ sendError(res, 409, "steering.not_running", "run just finished — the MCP re-dial verb needs a live run; a resumed continuation takes a fresh first listing of every declared server anyway");
111
+ return;
112
+ default: throw e;
113
+ }
114
+ }
115
+ }
116
+ const run = activeTaskId === undefined ? undefined : await deps.runStore.getRun(activeTaskId);
117
+ if (run && isParkedRunStatus(run.status)) {
118
+ sendError(res, 409, "steering.not_running", "run is waiting on a pending decision — the MCP re-dial verb needs a live turn; a resumed continuation dials every declared server afresh (the re-dial applies to the current turn only and is not persisted), so resume this run first — a re-dial cannot be queued behind the decision");
119
+ return;
120
+ }
121
+ if (run && isTerminalRunStatus(run.status)) {
122
+ sendError(res, 409, "steering.not_running", "run already ended — the MCP re-dial verb needs a live run; the next submit of this session materializes every declared MCP server from scratch");
123
+ return;
124
+ }
125
+ if (run && runProvenElsewhere(run, deps.instanceId)) {
126
+ sendError(res, 409, "steering.not_running", "run is live on another replica — the MCP re-dial verb must reach the replica holding the stream (the MCP connections belong to that process); resend it there");
127
+ return;
128
+ }
129
+ sendError(res, 409, "steering.not_running", "no live steerable face for this session on this replica — there is no active run here to re-dial on (a verify or cascade submission exposes none, and an idle session holds no MCP connections at all)");
130
+ return;
131
+ }
132
+ default:
133
+ return assertNever(match.decl.id);
134
+ }
135
+ }
136
+ export const MCP_RECONNECT_ROUTES = [
137
+ { id: "mcp-reconnect", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/mcp\/reconnect$/, label: "/v1/sessions/:id/mcp/reconnect", methods: ["POST"] },
138
+ ];
139
+ //# sourceMappingURL=mcp-reconnect.js.map
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-contracts.js";
3
3
  import { sendJson, sendError } from "../send.js";
4
4
  import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
5
- import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
5
+ import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage, runProvenElsewhere } from "../route-ctx.js";
6
6
  import { assertNever } from "../route-ctx.js";
7
7
  const BodySchema = z.object({ reason: z.string().min(1).max(500).optional() }).strict();
8
8
  export async function handleRunMemoryCapture(req, res, match, ctx) {
@@ -79,7 +79,7 @@ export async function handleRunMemoryCapture(req, res, match, ctx) {
79
79
  sendError(res, 409, "steering.not_running", "run already ended — the memory-capture opt-out verb needs a live run; submit the next turn of this session with memoryCapture: 'off' (the declaration writes the same one-way session record)");
80
80
  return;
81
81
  }
82
- const provenElsewhere = typeof run.instanceId === "string" && run.instanceId.length > 0 && deps.instanceId !== undefined && run.instanceId !== deps.instanceId;
82
+ const provenElsewhere = runProvenElsewhere(run, deps.instanceId);
83
83
  if (provenElsewhere) {
84
84
  sendError(res, 409, "steering.not_running", "run is live on another replica — the memory-capture opt-out verb must reach the replica holding the stream; resend it there, or declare memoryCapture: 'off' on the next submit");
85
85
  return;
@@ -27,7 +27,7 @@ import { headerStr, gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } f
27
27
  import { ActorAssertionWire } from "@sema-agent/settings-schema";
28
28
  import { assertNever } from "../route-ctx.js";
29
29
  import { mintAndDriveDurableRun } from "../durable-run-leg.js";
30
- import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
30
+ import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage, runProvenElsewhere } from "../route-ctx.js";
31
31
  import { withPrincipal } from "../../observability/principal-context.js";
32
32
  const ASK_PARKING_RETRY_AFTER_SEC = 2;
33
33
  const ASK_ACTOR_LABEL_MAX = 256;
@@ -88,7 +88,7 @@ function sendHandleMiss(res, run, thisInstanceId) {
88
88
  sendError(res, 404, "not_found.task_owner_settled", "no such background task handle here. Routing hint about its OWNER (not about this handle): the run has already settled, and background handles live in the replica-local registry for the parent's lifetime, so none of its handles are readable any more. Read the run's own outcome via GET /v1/runs/:id");
89
89
  return true;
90
90
  }
91
- if (run.status === "running" && typeof run.instanceId === "string" && run.instanceId.length > 0 && thisInstanceId !== undefined && run.instanceId !== thisInstanceId) {
91
+ if (run.status === "running" && runProvenElsewhere(run, thisInstanceId)) {
92
92
  sendError(res, 404, "not_found.task_owner_elsewhere", "no such background task handle here. Routing hint about its OWNER (not about this handle): the run is executing on another replica, and this one's registry holds none of its handles. Ask the replica that owns the run, or read the run's own outcome via GET /v1/runs/:id");
93
93
  return true;
94
94
  }
@@ -532,7 +532,7 @@ export async function handleRuns(req, res, match, ctx) {
532
532
  return;
533
533
  }
534
534
  if (run.status === "running") {
535
- const provenElsewhere = typeof run.instanceId === "string" && run.instanceId.length > 0 && deps.instanceId !== undefined && run.instanceId !== deps.instanceId;
535
+ const provenElsewhere = runProvenElsewhere(run, deps.instanceId);
536
536
  if (provenElsewhere) {
537
537
  sendNotRunning("run is live on another replica — a live steer must reach the replica holding the stream; resend it there, or use POST /v1/runs/:id/cancel for a run-level stop");
538
538
  }
@@ -731,7 +731,7 @@ export async function handleRuns(req, res, match, ctx) {
731
731
  return;
732
732
  }
733
733
  if (status === "running") {
734
- const provenElsewhere = typeof run.instanceId === "string" && run.instanceId.length > 0 && deps.instanceId !== undefined && run.instanceId !== deps.instanceId;
734
+ const provenElsewhere = runProvenElsewhere(run, deps.instanceId);
735
735
  if (provenElsewhere) {
736
736
  sendError(res, 409, "interrupt.not_held", "run is live on another replica — interrupt must reach the replica holding the stream; resend it there, or use POST /v1/runs/:id/cancel for a run-level stop");
737
737
  }
@@ -544,6 +544,7 @@ export async function handleSessions(req, res, match, ctx) {
544
544
  const s = await entry.status;
545
545
  sendJson(res, 200, {
546
546
  sessionId,
547
+ autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
547
548
  ...(s.captureOptedOut !== undefined ? { captureOptedOut: s.captureOptedOut } : {}),
548
549
  ...(s.committedCount !== undefined ? { committedCount: s.committedCount } : {}),
549
550
  ...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
@@ -417,6 +417,20 @@ export interface ServiceSeamDeps {
417
417
  sessionMemoryStatus?: {
418
418
  status: (sessionId: string) => Promise<SessionMemoryStatus>;
419
419
  };
420
+ /**
421
+ * S-403(core 7.22.0 [ref] W1)—— 本**部署**的自动整理武装事实,`GET /v1/sessions/:id/memory-status`
422
+ * 的 `autoConsolidationArmed` 格。
423
+ *
424
+ * 🔴 **唯一读法**:值必须由 core 的 `autoRunOnRecommendationEffective(RunnerDeps)` 组
425
+ * (契约 `memory.auto_consolidation.arming_has_one_resolver`),在 **boot 期算一次**
426
+ * (`boot/stage-10-http-server.ts` 单点)。`RunnerDeps.autoRunOnRecommendation` 的**缺席不是「关」**
427
+ * (core 7.21.1 起具名缺省是开,且只在 `memoryConsolidation` ∧ 可解整理模型席都接线时才真武装)——
428
+ * 所以「按席在不在场判」与「按席值判」**两个方向都错**,本仓不自铸第二套派生。
429
+ * 🔴 **必填,不是可选**:它是一个纯函数在 boot 期的答案,任何装配点都算得出;做成可选就等于在
430
+ * wire 上多出一个「答不出」的第三态,而这一格读错的方向是**把一台正在外流记忆的部署报成没在外流**。
431
+ * 装配漏接 ⇒ tsc 红(本行就是通知);测试桩在 `test/helpers/boot.ts` 显式给 false。
432
+ */
433
+ memoryAutoConsolidationArmed: boolean;
420
434
  /** [ref] 件②(core 5.59.0 `MemoryEngine.listExternalOriginEntries` / `listOriginClearances` /
421
435
  * `clearEntryOrigin`):**外源标记人面**。启用 `GET /v1/memory/origin/external`、
422
436
  * `GET /v1/memory/origin/clearances`、`POST /v1/memory/origin/entries/:entryId/clear`(三口 operator-only)。
@@ -340,7 +340,11 @@ export interface TaskRequestBody {
340
340
  * (`request-value-closure.ts` 的 `singleMemberOffFieldIssue`,与 `memoryCapture` 同表)。
341
341
  * 记忆面暗的部署(引擎未接线 / 多租户)上 `memory` 整键本就缺席,本声明无处落也无事可关。
342
342
  * Rides the persisted body onto resume legs(与 `memoryWrite` 同姿势)。
343
- * ⏳ 子代地板(`ParentSpecSeat.memoryOff`:父关 ⇒ 子代/孙代同关)候 core 7.22.0,**本版不做**。 */
343
+ * ✅ **子代地板自 core 7.22.0 到货,本仓零接线**:core 的 `parentSpecSeatOf` 从**这条 run 自己的**
344
+ * `spec.memory.enabled === false` 铸 `ParentSpecSeat.memoryOff`,`applyParentSpecSeat` 作**无条件
345
+ * REMOVAL** 施加(改写成 `{ …子代自己的 scopes, enabled:false }`,永不删键)⇒ 父关 ⇒ 子代 / 孙代 /
346
+ * workflow 子 spec / 跨 run 唤醒与保留 resume 一律无记忆面。🚧 **tier-3 耐久复活不带**(core 已记残余)。
347
+ * 链路的机器锚:`test/memory-off-request.test.ts` ⑥ 段(亲读 core dist 的铸点与施加点)。 */
344
348
  memory?: "off";
345
349
  /** 142-S4 projectId 线程化([ref] §2):这个 run 归属的项目(center 登记簿键,generic lowercase
346
350
  * UUID — core S1 `resolveProjectId` marker 或 S3 无仓 mint 的产物;客户端只透传,零铸造权威)。是「哪个
@@ -23,8 +23,12 @@
23
23
  *
24
24
  * ## 定界
25
25
  * 本模块**只**管 `face` 一档。内建读拒表(`readDenyPatterns` / `readDenyBuiltinTiers` /
26
- * `readDenyBuiltinExclude`)一个字节不动 —— 放开容纳面不等于放开凭证/shell 历史/钱包那几档 deny,
27
- * 两者在 core 里是并列机制(deny 表在 open 面上照样拦)。core 的 `config.read_face_deployment_clamped`
26
+ * `readDenyBuiltinExclude`)一个字节不动 —— 放开容纳面不等于放开那几档 deny,两者在 core 里是**并列
27
+ * 机制**(有活行的 deny 表在 open 面上照样拦)。
28
+ * ⚠️ **但别把这句话读成「凭证/shell 历史/钱包那几档一直在岗」**:core 7.22.0([ref])起内建表的**缺省
29
+ * 选择是空** —— 一台没写 `READ_DENY_BUILTIN_TIERS` 的部署上,那张表**一行都不生效**(要老姿态就显式写
30
+ * `credentials,browser,wallet,agent-config`;运维面全文见 `docs/DEPLOY-PREREQS.md` 的 core 7.22.0 段)。
31
+ * 本模块的定界不变,变的是「并列的那一侧默认开着」这个**前提**。core 的 `config.read_face_deployment_clamped`
28
32
  * (只读挂载钳制)同样不受本件影响。
29
33
  */
30
34
  import { type PostureSource } from "./posture-source.js";
@@ -17,7 +17,7 @@
17
17
  * ⑨⑩([ref],2026-08-31)把两个最大的 wire 面也纳入:TaskSpec 按「server 有无写点」记 Wired/Unwired,
18
18
  * TaskResult 按「转发/摘除」记 Forwarded/Stripped —— 谁改 resolve-spec.ts / runs.ts 的发射点谁同步本文件。
19
19
  */
20
- import type { TaskNotificationPayload, SemaProvenance, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxAppendMessage, TaskSpec, TaskResult, WiringManifest, GateOutcome, GateDisposition, Settlement, PausedCause, WorkflowRun } from "@sema-agent/core";
20
+ import type { TaskNotificationPayload, SemaProvenance, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxAppendMessage, TaskSpec, TaskResult, WiringManifest, GateOutcome, GateDisposition, Settlement, PausedCause, WorkflowRun, ReadRootGrantCandidate, McpReconnectResult, McpServerStatus } from "@sema-agent/core";
21
21
  import type { ToolApprovalFrame } from "../tool-approval.js";
22
22
  /** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。
23
23
  * 🔴 **导出**([ref] U1/U2):本仓「词表闭集,未处置成员=编译红」的钉法只许有**一个**形 ——
@@ -60,7 +60,7 @@ type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peer
60
60
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
61
61
  type _GuardMailboxAppendReverse = AssertAllKeysHandled<Exclude<MailboxAppendProjected, keyof MailboxAppendMessage>>;
62
62
  type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp" | "writeProtection";
63
- type WiringSectionExcluded = "governance" | "configFingerprint" | "autoConsolidation";
63
+ type WiringSectionExcluded = "governance" | "configFingerprint" | "autoConsolidation" | "readDeny";
64
64
  type _GuardWiringSections = AssertAllKeysHandled<Exclude<keyof WiringManifest, WiringSectionProjected | WiringSectionExcluded>>;
65
65
  type _GuardWiringSectionsReverse = AssertAllKeysHandled<Exclude<WiringSectionProjected | WiringSectionExcluded, keyof WiringManifest>>;
66
66
  type WiringSection<K extends keyof WiringManifest> = NonNullable<WiringManifest[K]>;
@@ -103,6 +103,9 @@ type _GuardWiringRulesReverse = AssertAllKeysHandled<Exclude<WiringRulesHandled,
103
103
  type WiringWriteProtectionHandled = "targetView";
104
104
  type _GuardWiringWriteProtection = AssertAllKeysHandled<Exclude<keyof WiringSection<"writeProtection">, WiringWriteProtectionHandled>>;
105
105
  type _GuardWiringWriteProtectionReverse = AssertAllKeysHandled<Exclude<WiringWriteProtectionHandled, keyof WiringSection<"writeProtection">>>;
106
+ type WiringReadDenyHandled = "builtinTiers";
107
+ type _GuardWiringReadDeny = AssertAllKeysHandled<Exclude<keyof WiringSection<"readDeny">, WiringReadDenyHandled>>;
108
+ type _GuardWiringReadDenyReverse = AssertAllKeysHandled<Exclude<WiringReadDenyHandled, keyof WiringSection<"readDeny">>>;
106
109
  type WiringAutoConsolidationHandled = "onRecommendation";
107
110
  type _GuardWiringAutoConsolidation = AssertAllKeysHandled<Exclude<keyof WiringSection<"autoConsolidation">, WiringAutoConsolidationHandled>>;
108
111
  type _GuardWiringAutoConsolidationReverse = AssertAllKeysHandled<Exclude<WiringAutoConsolidationHandled, keyof WiringSection<"autoConsolidation">>>;
@@ -164,6 +167,32 @@ type ParkRowWithheld = "token";
164
167
  type _GuardParkRow = AssertAllKeysHandled<Exclude<ParkRowKeys, ParkRowProjected | ParkRowWithheld>>;
165
168
  type _NoDoubleBookParkRow = AssertAllKeysHandled<Extract<ParkRowProjected, ParkRowWithheld>>;
166
169
  type _GuardParkRowReverse = AssertAllKeysHandled<Exclude<ParkRowProjected | ParkRowWithheld, ParkRowKeys>>;
170
+ type ReadRootCandidateProjected = "dir" | "clearsThisAsk" | "covers";
171
+ type _GuardReadRootCandidate = AssertAllKeysHandled<Exclude<keyof ReadRootGrantCandidate, ReadRootCandidateProjected>>;
172
+ type _GuardReadRootCandidateReverse = AssertAllKeysHandled<Exclude<ReadRootCandidateProjected, keyof ReadRootGrantCandidate>>;
173
+ type AskRiskAxes = NonNullable<AskRequest["riskAxes"]>;
174
+ type RiskAxesProjected = "irreversible" | "egress";
175
+ type _GuardRiskAxes = AssertAllKeysHandled<Exclude<keyof AskRiskAxes, RiskAxesProjected>>;
176
+ type _GuardRiskAxesReverse = AssertAllKeysHandled<Exclude<RiskAxesProjected, keyof AskRiskAxes>>;
177
+ type McpReconnectProjected = "server" | "prefix" | "toolCount" | "added" | "removed" | "outcome" | "tools" | "reason" | "status" | "listingIncomplete";
178
+ type McpReconnectWithheld = "axes" | "dropped" | "warnings";
179
+ type _GuardMcpReconnect = AssertAllKeysHandled<Exclude<keyof McpReconnectResult, McpReconnectProjected | McpReconnectWithheld>>;
180
+ type _NoDoubleBookMcpReconnect = AssertAllKeysHandled<Extract<McpReconnectProjected, McpReconnectWithheld>>;
181
+ type _GuardMcpReconnectReverse = AssertAllKeysHandled<Exclude<McpReconnectProjected | McpReconnectWithheld, keyof McpReconnectResult>>;
182
+ type McpRedialStatusProjected = "name" | "status" | "errorCode" | "httpStatus" | "delivered" | "serverInfo" | "error" | "transportClosed";
183
+ type McpRedialStatusWithheld = "toolNames";
184
+ type _GuardMcpRedialStatus = AssertAllKeysHandled<Exclude<keyof McpServerStatus, McpRedialStatusProjected | McpRedialStatusWithheld>>;
185
+ type _NoDoubleBookMcpRedialStatus = AssertAllKeysHandled<Extract<McpRedialStatusProjected, McpRedialStatusWithheld>>;
186
+ type _GuardMcpRedialStatusReverse = AssertAllKeysHandled<Exclude<McpRedialStatusProjected | McpRedialStatusWithheld, keyof McpServerStatus>>;
187
+ type McpPanelStatusProjected = "name" | "status" | "serverInfo" | "toolNames" | "error";
188
+ type McpPanelStatusWithheld = "errorCode" | "httpStatus" | "delivered" | "transportClosed";
189
+ type _GuardMcpPanelStatus = AssertAllKeysHandled<Exclude<keyof McpServerStatus, McpPanelStatusProjected | McpPanelStatusWithheld>>;
190
+ type _NoDoubleBookMcpPanelStatus = AssertAllKeysHandled<Extract<McpPanelStatusProjected, McpPanelStatusWithheld>>;
191
+ type _GuardMcpPanelStatusReverse = AssertAllKeysHandled<Exclude<McpPanelStatusProjected | McpPanelStatusWithheld, keyof McpServerStatus>>;
192
+ type McpListingIncompleteKeys = keyof NonNullable<McpReconnectResult["listingIncomplete"]>;
193
+ type McpListingIncompleteProjected = "reason" | "pages" | "budgetMs" | "error";
194
+ type _GuardMcpListingIncomplete = AssertAllKeysHandled<Exclude<McpListingIncompleteKeys, McpListingIncompleteProjected>>;
195
+ type _GuardMcpListingIncompleteReverse = AssertAllKeysHandled<Exclude<McpListingIncompleteProjected, McpListingIncompleteKeys>>;
167
196
  export type CoreKeysetGuards = [
168
197
  _GuardNotification,
169
198
  _GuardNotificationReverse,
@@ -188,6 +217,8 @@ export type CoreKeysetGuards = [
188
217
  _GuardWiringSections,
189
218
  _GuardWiringWriteProtection,
190
219
  _GuardWiringWriteProtectionReverse,
220
+ _GuardWiringReadDeny,
221
+ _GuardWiringReadDenyReverse,
191
222
  _GuardWiringAutoConsolidation,
192
223
  _GuardWiringAutoConsolidationReverse,
193
224
  _GuardWiringSectionsReverse,
@@ -242,7 +273,22 @@ export type CoreKeysetGuards = [
242
273
  _GuardWorkflowRunReverse,
243
274
  _GuardParkRow,
244
275
  _NoDoubleBookParkRow,
245
- _GuardParkRowReverse
276
+ _GuardParkRowReverse,
277
+ _GuardReadRootCandidate,
278
+ _GuardReadRootCandidateReverse,
279
+ _GuardRiskAxes,
280
+ _GuardRiskAxesReverse,
281
+ _GuardMcpReconnect,
282
+ _NoDoubleBookMcpReconnect,
283
+ _GuardMcpReconnectReverse,
284
+ _GuardMcpRedialStatus,
285
+ _NoDoubleBookMcpRedialStatus,
286
+ _GuardMcpRedialStatusReverse,
287
+ _GuardMcpPanelStatus,
288
+ _NoDoubleBookMcpPanelStatus,
289
+ _GuardMcpPanelStatusReverse,
290
+ _GuardMcpListingIncomplete,
291
+ _GuardMcpListingIncompleteReverse
246
292
  ];
247
293
  export {};
248
294
  //# sourceMappingURL=core-keyset-guard.d.ts.map
@@ -613,6 +613,18 @@ export declare function workspaceChangedEventData(ev: {
613
613
  * 并且它对调用方**本就不可观测**(整理跑在终端 harvest 之后、fire-and-forget、宿主侧),剥掉它不收走
614
614
  * 任何能力 —— 这正是 `writeProtection` 判据①**不成立**的那一格。
615
615
  * ⚠️ **缺席 = 未武装**(core present-iff),不折 `false`、不读「未知」。
616
+ * 🔴 **S-403(7.85.0)起本条要连着 `core-keyset-guard.ts` ⑥b 判据②的补句读**:同一个事实在会话面
617
+ * (`GET /v1/sessions/:id/memory-status` 的 `autoConsolidationArmed`)是可读的,而这一段仍然剥 ——
618
+ * 「一个事实一张脸」,两处同源、唯一读点。裁定正文在那一处,这里不抄第二份。
619
+ * · `readDeny`(`{ builtinTiers: readonly string[] }`;core 7.22.0 [ref],S-417 提货件)—— **裁:operator
620
+ * 受众,租户面剥**,所以它**不在本构造器里**,在 {@link wiringManifestOperatorEventData}。四条判据逐字在
621
+ * `core-keyset-guard.ts` ⑥b 的 `WiringSectionExcluded` 那一行(与 `governance` / `configFingerprint` /
622
+ * `autoConsolidation` 同表同处,免得同一条裁定有两个写者)。一句话:它答的是**这台部署选了哪几档内置
623
+ * 敏感路径 deny 表**——一条部署治理面的**选择**,与 `writeProtection` 那种「这条腿手里有什么设施」的
624
+ * 自述不同族;而且它是本仓 env `READ_DENY_BUILTIN_TIERS` 的生效读出口,名表面归 operator(S-138)。
625
+ * ⚠️ **缺席 = 没有内置行判这条腿**(core present-iff:实质激活至少一行才铸),**不是**「没有读被 deny
626
+ * 判」—— 部署自己的 `readDenyPatterns` 刻意不上报(模式文本是部署材料,不是枚举)。**空数组永不发出**
627
+ * (「关」与「开到什么都不判」是同一个事实)⇒ 收到空数组按**坏 producer** 处置并留痕,见那里。
616
628
  * 未来 core 加一段而它没有 audience 标签时,**先在这里补一条裁定再决定挑不挑键** —— 段级完备性钉
617
629
  * (test/wiring-manifest-projection.test.ts)会在那一刻先把人拦下来 —— ⚠️ **但那条钉是夹具驱动的**:它只看
618
630
  * `deriveWiringManifest(EFFECTIVE_FACTS)` 真铸出来的段,facts 不带的可选段(147③ 的 `autoMode` 就是)它一格
@@ -554,6 +554,19 @@ export function wiringManifestOperatorEventData(ev) {
554
554
  ...(bool(g.memoryAdmission) !== undefined ? { memoryAdmission: g.memoryAdmission } : {}),
555
555
  ...(bool(g.retention) !== undefined ? { retention: g.retention } : {}),
556
556
  };
557
+ const readDeny = (() => {
558
+ if (m.readDeny === undefined)
559
+ return undefined;
560
+ const seg = sec(m.readDeny);
561
+ if (seg === undefined)
562
+ return dropProjectedFace("wiring_manifest.readDeny is not an object");
563
+ const tiers = seg.builtinTiers;
564
+ if (!Array.isArray(tiers) || tiers.length === 0)
565
+ return dropProjectedFace("wiring_manifest.readDeny.builtinTiers is not a non-empty array (core never emits an empty one)");
566
+ if (!tiers.every((t) => str(t) !== undefined))
567
+ return dropProjectedFace("wiring_manifest.readDeny.builtinTiers has a non-string / empty member");
568
+ return { builtinTiers: [...tiers] };
569
+ })();
557
570
  const autoConsolidation = (() => {
558
571
  if (m.autoConsolidation === undefined)
559
572
  return undefined;
@@ -567,6 +580,7 @@ export function wiringManifestOperatorEventData(ev) {
567
580
  return {
568
581
  ...tenant,
569
582
  ...(Object.keys(governance).length > 0 ? { governance } : {}),
583
+ ...(readDeny !== undefined ? { readDeny } : {}),
570
584
  ...(autoConsolidation !== undefined ? { autoConsolidation } : {}),
571
585
  ...(str(m.configFingerprint) !== undefined ? { configFingerprint: m.configFingerprint } : {}),
572
586
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.84.0",
3
+ "version": "7.85.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",
@@ -54,7 +54,7 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "7.21.1",
57
+ "@sema-agent/core": "7.22.0",
58
58
  "@sema-agent/settings-schema": "2.1.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",