@sema-agent/server 7.59.0 → 7.60.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.
Files changed (40) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +8 -1
  4. package/dist/boot/stores.d.ts +1 -0
  5. package/dist/boot/stores.js +4 -1
  6. package/dist/http/routes/approvals-assistant.js +3 -0
  7. package/dist/http/routes/capabilities.js +2 -0
  8. package/dist/http/routes/diagnostics.js +1 -0
  9. package/dist/http/server.d.ts +6 -0
  10. package/dist/main.js +10 -4
  11. package/dist/plugins/approval-ask-store-sql.d.ts +9 -9
  12. package/dist/plugins/approval-ask-store-sql.js +1 -1
  13. package/dist/plugins/device-store-sql.d.ts +11 -7
  14. package/dist/plugins/device-store-sql.js +6 -6
  15. package/dist/plugins/file-history-store-sql.d.ts +15 -3
  16. package/dist/plugins/file-history-store-sql.js +32 -29
  17. package/dist/plugins/image-bake-store-sql.js +1 -1
  18. package/dist/plugins/permission-rule-store-file.d.ts +4 -4
  19. package/dist/plugins/permission-rule-store-file.js +4 -4
  20. package/dist/plugins/permission-rule-store-sql.d.ts +15 -11
  21. package/dist/plugins/permission-rule-store-sql.js +7 -11
  22. package/dist/plugins/retention-lane-store-sql.d.ts +2 -2
  23. package/dist/plugins/retention-store-sql.d.ts +4 -12
  24. package/dist/plugins/retention-store-sql.js +4 -0
  25. package/dist/plugins/session-policy-store-sql.d.ts +9 -2
  26. package/dist/plugins/shared-memory-store-sql.d.ts +25 -19
  27. package/dist/plugins/shared-memory-store-sql.js +17 -15
  28. package/dist/plugins/sql-driver.d.ts +78 -26
  29. package/dist/plugins/sql-driver.js +97 -14
  30. package/dist/plugins/store-backend.d.ts +6 -2
  31. package/dist/rules-consent.d.ts +54 -12
  32. package/dist/rules-consent.js +4 -4
  33. package/dist/sql-engine-posture.d.ts +49 -0
  34. package/dist/sql-engine-posture.js +13 -0
  35. package/dist/tool-approval.d.ts +18 -0
  36. package/dist/tool-approval.js +1 -0
  37. package/dist/trace/core-keyset-guard.d.ts +2 -2
  38. package/dist/trace/project.d.ts +25 -0
  39. package/dist/trace/project.js +26 -0
  40. package/package.json +2 -2
@@ -1,16 +1,13 @@
1
- import { type RuleOwner, type ImportPreview, type RedeemedBatchMember, type ImportedSettingsLayer, type PersistedAllowRule, type RemoveResult, type EditedRuleTextPrecheck, type RuleScope, type RuleOffer } from "@sema-agent/core";
2
- import type { PermissionRuleStoreProvider, RuleApprovalRecordStore } from "@sema-agent/core";
1
+ import { type RuleOwner, type ImportPreview, type RedeemedBatchMember, type ImportedSettingsLayer, type PersistedAllowRule, type EffectivePermissionRule, type RemoveResult, type EditedRuleTextPrecheck, type RuleScope, type RuleOffer } from "@sema-agent/core";
2
+ import type { DurableRulePartitionProvider, PermissionRuleStoreProvider, RuleApprovalRecordStore } from "@sema-agent/core";
3
3
  import { type RuleImportTicket, type RuleTicketPurpose, type RuleTicketReclaimResult, type RuleTicketRedeemResult, type RuleTicketSnapshot } from "./plugins/permission-rule-store-sql.js";
4
4
  /**
5
- * 本车道**真正**依赖的三面(**结构**接口,不是 SQL )
6
- *
7
- * 🔴 为什么不直接吃 `PermissionRuleStores`(SQL ):同意车道是**纯协议逻辑**,它对「行躺在 MySQL 还是
8
- * PG 还是别处」一无所知也不该知道。吃结构接口有两个真收益:①单元测试能用 core 自己的
9
- * `InMemoryPermissionRuleStore` / `InMemoryRuleApprovalRecordStore` 驱动**同一段**代码(而不是为了测协议
10
- * 去拉一个数据库);②将来若真出了 File 形规则店,本文件一个字不用改。SQL 束结构上满足本接口。
5
+ * 规则分区**之外**的两只记录面 —— 它们是 server 自铸的(审批记录的 SQL/File 形、CC 导入票),
6
+ * 与 [ref] 的分区族不是同一件东西,所以在这里单列一次:一个 backend 交出来的束
7
+ * ({@link PermissionRuleStoreBundle})与同意车道消费的束({@link RuleConsentStores})都含这两面,
8
+ * 差的只是规则那一格躺的是 **durable 分区后端**还是**合成好的统一店**。
11
9
  */
12
- export interface RuleConsentStores {
13
- provider: PermissionRuleStoreProvider;
10
+ export interface RuleConsentRecordStores {
14
11
  /** core 的记录店面 + 一个**可选**的加项:`discardPendingRecord`(超帽拒绝时收掉 `prepareCcImport`
15
12
  * 已经落盘的那一条 pending 记录)。没有这个加项的后端只是留一条孤儿行,不影响任何判决
16
13
  * (语义与「已确认的记录永不被收」的硬条款见 SQL 侧同名方法)。 */
@@ -51,6 +48,22 @@ export interface RuleConsentStores {
51
48
  reclaim(ticketId: string, principal: string, purpose: RuleTicketPurpose, minClaimAgeMs: number): Promise<RuleTicketReclaimResult>;
52
49
  };
53
50
  }
51
+ /**
52
+ * 本车道**真正**依赖的三面(**结构**接口,不是 SQL 类)。
53
+ *
54
+ * 🔴 为什么不直接吃 `PermissionRuleStores`(SQL 束):同意车道是**纯协议逻辑**,它对「行躺在 MySQL 还是
55
+ * PG 还是别处」一无所知也不该知道。吃结构接口有两个真收益:①单元测试能用 core 自己的
56
+ * `InMemoryDurableRulePartition` / `InMemoryRuleApprovalRecordStore` 驱动**同一段**代码(而不是为了测协议
57
+ * 去拉一个数据库);②将来若真出了 File 形规则店,本文件一个字不用改。SQL 束结构上满足本接口。
58
+ *
59
+ * 🔴 `provider` 是 [ref] **合成后**的统一店(`createPermissionRuleStoreProvider` 的产物),不是
60
+ * backend 交出来的 durable 分区 —— core 的 `removePersistedRule` / `RuleConsentDeps.provider` 两口收的
61
+ * 都是它,而合成点只有 `main.ts` 一处:引擎接缝(`RunnerDeps.permissionRuleStore`)与本车道读的必须是
62
+ * **同一只**对象,否则「卡上给的候选」与「引擎放行时看到的规则」会分别站在两只店上。
63
+ */
64
+ export interface RuleConsentStores extends RuleConsentRecordStores {
65
+ provider: PermissionRuleStoreProvider;
66
+ }
54
67
  /**
55
68
  * 一个 backend 交出来的**完整**规则店束 = 同意车道要的三面 + boot 期审计要的窄读口。
56
69
  *
@@ -59,7 +72,13 @@ export interface RuleConsentStores {
59
72
  * 结构接口是这条 seam 唯一站得住的形——两个实现各自满足它,消费点(main 装配 / 同意车道 / 撤销面
60
73
  * 路由)一个字都不用知道行躺在哪里。
61
74
  */
62
- export interface PermissionRuleStoreBundle extends RuleConsentStores {
75
+ export interface PermissionRuleStoreBundle extends RuleConsentRecordStores {
76
+ /** [ref] 的 **durable 分区后端**(`user` + `project` 两源)。**不是**引擎接缝本身:
77
+ * `RunnerDeps.permissionRuleStore` 与 {@link RuleConsentStores.provider} 是
78
+ * `createPermissionRuleStoreProvider({ durable })` 合成出来的统一店,合成在 `main.ts` 一处发生一次。
79
+ * 分区构成(接不接 org / session)是**部署**决定,不是 backend 决定 —— 让 backend 各自合成,等于
80
+ * 日后接 org 分区时要挨个改后端,而两个后端合成得不一样时没有任何人看得出来。 */
81
+ durable: DurableRulePartitionProvider;
63
82
  /** [ref] §3:库里已有几只规则桶(一 owner 一桶)。boot 期休眠行审计的**唯一**依赖;
64
83
  * 为什么数桶不数规则、以及读失败必须响亮,见 `boot/permission-rules-audit.ts` 与两个实现处的注。 */
65
84
  countBuckets(): Promise<number>;
@@ -491,7 +510,30 @@ export interface PersistedRuleWireRow {
491
510
  /** 去规范化的命令(core 的匹配器信的就是它 + `match`,不是重新解析 `rule`)。 */
492
511
  command: string;
493
512
  adds: PersistedAllowRule["adds"];
513
+ /** [ref](core 7.5.0):这一行来自哪个源。**由 `scope` 派生**(`global`⟺`user`、`project`⟺
514
+ * `project`、`session`⟺`session`),不是行上另存的第二个字节 —— core 的 `ruleSourceOf` 是整条映射。
515
+ * 今天本部署只接 durable 一格分区 ⇒ 值域实为 `user | project`。
516
+ *
517
+ * 🔴 为什么上 wire 而不是丢掉:统一读把这一格**交到手里了**,治理列举知道而不说 = 一次 wire 谎言的
518
+ * 反面(客户端只能自己去 decode `scope` 猜),而 org 分区接线的那天它会立刻变得承重。 */
519
+ source: EffectivePermissionRule["source"];
520
+ /** [ref]:这一行的**站位** —— `live` 或 `shadowed-by-org`(org deny 盖到它的命令模式上)。
521
+ * 今天无 org 分区 ⇒ 恒 `live`;这一格正是「接了 org 分区之后治理面必须看得见的那件事」。 */
522
+ status: EffectivePermissionRule["status"];
494
523
  }
524
+ /**
525
+ * 一条**可导入**的规则行 = 列举行**减去两格派生键**。
526
+ *
527
+ * 🔴 为什么导入输入面不收 `source`/`status`:两格都是**派生**的 —— `source` 由 `scope` 派生
528
+ * (core 的 `ruleSourceOf` 是整条映射,d.ts 逐字「A row never carries a second source byte that could
529
+ * drift from its scope」),`status` 由 org 分区的遮蔽判据派生。一个调用方递进来的 `source` 只会与它
530
+ * 自己的 `scope` 漂,而漂的落点是治理面上「这行到底归谁管」。同族纪律 core 自己在卡面投影上也写过
531
+ * (「derived HERE, at projection time … never accepted from a caller」)。
532
+ *
533
+ * 这不是两份平行形状:`Omit` 让「导入面 = 列举面 − 派生键」是一条**机器判据**,列举行加员时导入面
534
+ * 自动跟随、`LOCAL_IMPORT_ROW_SHAPE_PIN`(路由侧)当场编译红。
535
+ */
536
+ export type ImportableRuleRow = Omit<PersistedRuleWireRow, "source" | "status">;
495
537
  /** 列举结果。`rev` = 这只桶的 OCC 版本,游标绑它(见路由侧的游标注)。 */
496
538
  export interface PersistedRuleListing {
497
539
  rows: PersistedRuleWireRow[];
@@ -531,7 +573,7 @@ export interface RuleConsentLane {
531
573
  /** 导入道:原子消费票 → confirm → redeemRuleBatch。 */
532
574
  redeemImport(principal: string, ticket: string): Promise<RuleImportRedeemed>;
533
575
  /** [ref] A7 直通道:逐行校验 → 预览 + pending 记录 + 票(零可导入行 ⇒ 无票)。 */
534
- prepareLocalImport(principal: string, rows: readonly PersistedRuleWireRow[]): Promise<LocalImportPrepared>;
576
+ prepareLocalImport(principal: string, rows: readonly ImportableRuleRow[]): Promise<LocalImportPrepared>;
535
577
  /** [ref] A7 直通道:三态兑付(首兑 / 逐字回放 / 崩溃窗重驱),终局落进票行。 */
536
578
  redeemLocalImport(principal: string, ticket: string): Promise<LocalImportRedeemed>;
537
579
  /**
@@ -4,7 +4,7 @@ import { recordFailOpen } from "./observability/fail-open.js";
4
4
  import { createLogger } from "./observability/logger.js";
5
5
  import { MAX_CWD_CHARS } from "./task-cwd.js";
6
6
  import { CONTROL_AND_BIDI_CHARS, hasBidiControls } from "./text-bidi.js";
7
- import { confirmRuleApproval, parseAllowRuleText, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, removePersistedRule, } from "@sema-agent/core";
7
+ import { confirmRuleApproval, parseAllowRuleText, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, removePersistedRule, effectiveOrThrow, } from "@sema-agent/core";
8
8
  import { buildRulePayloadHash } from "./plugins/permission-rule-store-sql.js";
9
9
  export const RULE_IMPORT_TICKET_TTL_MS = 10 * 60_000;
10
10
  export const RULE_IMPORT_RETRY_AFTER_SEC = 2;
@@ -682,10 +682,10 @@ export function createRuleConsentLane(stores, opts) {
682
682
  }
683
683
  },
684
684
  async listRules(bucket) {
685
- const stored = await storeForBucket(stores.provider, bucket).list();
686
- const rows = stored.rules.map((r) => ({ rule: r.rule, scope: serializeRuleScope(r.scope), tool: r.tool, match: r.match, command: r.command, adds: r.adds }));
685
+ const view = await effectiveOrThrow(storeForBucket(stores.provider, bucket));
686
+ const rows = view.rules.map((r) => ({ rule: r.rule, scope: serializeRuleScope(r.scope), tool: r.tool, match: r.match, command: r.command, adds: r.adds, source: r.source, status: r.status }));
687
687
  rows.sort((a, b) => (a.scope < b.scope ? -1 : a.scope > b.scope ? 1 : a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : 0));
688
- return { rows, rev: stored.rev };
688
+ return { rows, rev: view.rev };
689
689
  },
690
690
  async removeRule(input) {
691
691
  return await removePersistedRule({ rule: input.rule, scope: input.scope, principal: input.principal, provider: stores.provider });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * S-131 —— SQL **引擎姿态**的运维读面(单一推导点,同 `memory-posture.ts` 的形制)。
3
+ *
4
+ * ## 病
5
+ *
6
+ * 「这台 worker 的事务读语义到底是什么」此前**任何读面都答不出来**:隔离级是服务器/会话变量,TiDB 的
7
+ * 事务模式是集群配置,而本仓每一条 `SELECT … FOR UPDATE` 的判据都以「加锁读是当前读」为前提。运维要
8
+ * 排查一次「为什么并发结果不对」,只能去登服务器读变量 —— 而那读的是**服务器**,不是**本仓的连接**。
9
+ *
10
+ * ## 药
11
+ *
12
+ * `sql-driver.ts` 的连接初始化在**每条池连接**上钉死这两件事并**回读复核**(`@contract txn.read-semantics`)。
13
+ * 本模块把那份读数拿出来,一次推导两个读面:operator 面(`GET /v1/diagnostics/wiring` 的 `sqlEngine` 段,
14
+ * 全量)与消费端面(`GET /v1/capabilities` 的 `sql` 位,窄投影)。两面同源 —— 「诊断页说悲观、能力位说
15
+ * 乐观」在结构上不可能。
16
+ *
17
+ * ## 两条诚实纪律(与 memory-posture 同源)
18
+ *
19
+ * ① **取实例真值,不按配置推断**:`facts` 是驱动在真连接上 `SET` 完之后**回读**的值,不是 env 里写了什么。
20
+ * ② **缺席就是缺席**:没有 SQL 后端(env-only / local 文件后端)⇒ `null`,不铸一个看起来像答案的空壳。
21
+ *
22
+ * ## 拒启在哪一头
23
+ *
24
+ * 不在这里 —— 在 `sql-driver.ts` 的连接初始化里(TiDB 乐观模式设不成悲观 / 隔离级复核不符 ⇒ 响亮抛)。
25
+ * 本模块在 boot 期借一条连接**触发**那次初始化,于是那句拒绝落在启动期而不是第一次业务写上。
26
+ */
27
+ import { type SqlEngineFacts } from "./plugins/sql-driver.js";
28
+ import type { Pool as MySqlPool } from "mysql2/promise";
29
+ import type { Pool as PgPool } from "pg";
30
+ /** 消费端窄投影(`GET /v1/capabilities` 的 `sql` 位)。`null` = 本部署没有 SQL 后端。 */
31
+ export interface SqlEngineCapability {
32
+ engine: SqlEngineFacts["engine"];
33
+ isolation: string;
34
+ txnMode: SqlEngineFacts["txnMode"];
35
+ }
36
+ /**
37
+ * Boot 期建立引擎事实:借一条池连接(初始化随之跑),再原样归还。返回一个 **live getter** —— 事实由
38
+ * 驱动持有,重连后它会被下一次初始化刷新,快照会说谎。
39
+ *
40
+ * 抛出 = 拒启:连接初始化拒绝了这个后端(TiDB 乐观模式设不成悲观、隔离级复核不符、服务器太旧读不出
41
+ * `@@transaction_isolation`)。这条错**刻意不吞** —— 本仓所有 `FOR UPDATE` 判据都以它为前提。
42
+ */
43
+ export declare function establishSqlEnginePosture(pools: {
44
+ mysqlPool?: MySqlPool | undefined;
45
+ pgPool?: PgPool | undefined;
46
+ }): Promise<(() => SqlEngineFacts | null) | undefined>;
47
+ /** operator 面 → 消费端面的窄投影(`version` 是运维料,刻意不进能力位:它只对排障有意义)。 */
48
+ export declare function projectSqlEngineCapability(facts: SqlEngineFacts | null | undefined): SqlEngineCapability | null;
49
+ //# sourceMappingURL=sql-engine-posture.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { mysqlDriver, pgDriver } from "./plugins/sql-driver.js";
2
+ export async function establishSqlEnginePosture(pools) {
3
+ const driver = pools.mysqlPool ? mysqlDriver(pools.mysqlPool) : pools.pgPool ? pgDriver(pools.pgPool) : undefined;
4
+ if (driver === undefined)
5
+ return undefined;
6
+ const conn = await driver.connect();
7
+ conn.release();
8
+ return () => driver.facts;
9
+ }
10
+ export function projectSqlEngineCapability(facts) {
11
+ return facts == null ? null : { engine: facts.engine, isolation: facts.isolation, txnMode: facts.txnMode };
12
+ }
13
+ //# sourceMappingURL=sql-engine-posture.js.map
@@ -124,6 +124,24 @@ export interface ToolApprovalFrame {
124
124
  * 本键让 live-only 部署(店缺席、帧无卡)也能读到出身位;安全类标记不得依赖店在场([ref] 结构性
125
125
  * 缺口成文)。消费语义(cli 挂点已预埋):在场 ⇒ 一切自动放行让位(含记住的规则与 bypass 姿态)。 */
126
126
  requiresRealApproval?: true;
127
+ /**
128
+ * **S-125③/[ref]**(core 7.5.0;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**出身**:
129
+ * 谁提的这一问,一个 core 闭集词(`ASK_ORIGINS` 八员 content_question / unresolvable / org_unavailable /
130
+ * org_rule / hook / ask_rule / denial_limit_fallback / policy)。引擎在门上盖章(core d.ts:
131
+ * 「engine-stamped at the gate, never a policy's claim」),`AskRequest.origin` 逐字透传。
132
+ *
133
+ * 🔴 **echo-only,零消费**:自动车道的资格判据全在 core(`classifierMayAnswer` 的闭表),server
134
+ * **不许**据本键自铸第二张资格表 —— 那是同一语义面两个写者(源头修复纪律),与
135
+ * `denialLimitFallback` 顶注那条「不据 autoDenyAfterMs 自铸第二只定时器」同一条禁令。
136
+ *
137
+ * 🔴 **不枚举词表**(`string` 而不是本仓自铸的联合):词表单一属主在 core,在这里抄一份会把 core 加的
138
+ * 新词吞成缺席 —— 与 `wiring_manifest.autoMode.reason` 的透传纪律逐字同规。缺席 = 老引擎 / 非 ask 路径。
139
+ *
140
+ * ⚠️ 与 {@link governanceForced} **不是**一回事,别互相顶替:本键答「哪一类权威提的问」,那一键答
141
+ * 「**本部署运维治理层**是不是这只 ask 的门」。`origin: "policy"` 覆盖的是「任何部署 ToolPolicy 的
142
+ * ask」,治理策略产的与普通策略产的在这一个词上同形 ⇒ 顶替不了(逐字论证见 `governance-ask-marks.ts` 顶注)。
143
+ */
144
+ origin?: string;
127
145
  /**
128
146
  * **E-14**([ref]② Trojan Source 族;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**工具输入**
129
147
  * (`AskRequest.args`)里含 bidi 控制符(LRM/RLM、嵌入/覆写、隔离符;字符类属主 `text-bidi.ts`)。
@@ -837,6 +837,7 @@ export class ToolApprovalCoordinator {
837
837
  message: typeof req.message === "string" ? redactDeep(req.message) : "",
838
838
  ...(governanceForced ? { governanceForced: true } : {}),
839
839
  ...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
840
+ ...(typeof req.origin === "string" && req.origin.length > 0 ? { origin: req.origin } : {}),
840
841
  ...(typeof req.persistedRuleShadowed === "string" && req.persistedRuleShadowed !== ""
841
842
  ? { persistedRuleShadowed: redactSecrets(req.persistedRuleShadowed).slice(0, MAX_RULE_TEXT_CHARS) }
842
843
  : {}),
@@ -28,7 +28,7 @@ type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "descripti
28
28
  type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
29
29
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
30
30
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
31
- type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback";
31
+ type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin";
32
32
  type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
33
33
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
34
34
  type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end";
@@ -45,7 +45,7 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
45
45
  /** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
46
46
  type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
47
47
  type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
48
- type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls";
48
+ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin";
49
49
  type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
50
50
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
51
51
  type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested";
@@ -97,6 +97,7 @@ export type TraceBlock = {
97
97
  errorCode?: string;
98
98
  settledBy?: ApprovalSettledBy;
99
99
  resolution?: AskDenyResolution;
100
+ autoDenied?: true;
100
101
  approver?: string;
101
102
  gatedCallId?: string;
102
103
  eventId?: string;
@@ -142,6 +143,7 @@ export declare function toolEndEventData(ev: {
142
143
  errorCode?: string;
143
144
  settledBy?: ApprovalSettledBy;
144
145
  resolution?: AskDenyResolution;
146
+ autoDenied?: true;
145
147
  approver?: string;
146
148
  gatedCallId?: string;
147
149
  eventId?: string;
@@ -379,6 +381,29 @@ export declare function workspaceChangedEventData(ev: {
379
381
  * 抛)。账本回放/第三方 producer 送来的矛盾对若原样上 wire,按 `armed` 读与按 `reason` 读的消费端会得到
380
382
  * **相反**的答案 —— 这条轴上(@cli `sema doctor permissions` 正读它)两个答案比没答案更坏 ⇒ 整段不铸。
381
383
  * 这不是改语义:它与本函数「畸形值一律按缺席」是同一条规则,而这条不变量是 core 自己写的段内契约。
384
+ * · `mcp[]`(`WiringManifestMcpEntry[]`;core 7.5.0 [ref],S-124 提货件)—— **裁:租户可见,逐条目挑键,
385
+ * `error` 一格不投**。
386
+ * ① **同主体同受众**:它是 per-leg 的读面「我这条腿申报的每台 MCP 服务器,连上了没有、挂了是哪一类、
387
+ * 挂上来几个工具」。同一主体(调用方自己的 run)、同一条轴上,本仓早已把**更粗**的同类事实投给
388
+ * 租户:`GET /v1/sessions/:id/mcp` 的 `connected|failed` 二态。⚠️ 两者**不合并**、也不互相校验 ——
389
+ * 那条端点的真源是 server 自己的部署面台账(server 自铸的二态),本段的真源是 core 这条腿
390
+ * materialize 的连接时快照;同名不同源,合并只会造出一个「哪个才算数」的新问题。
391
+ * ② **值的形**:`name`(spec 声明名)/ `source`(声明方自述的层级标签,core 已 single-line + 长度封顶
392
+ * 逐字回显)/ `status`(三词闭集)/ `errorCode`(core `MCP_FAILURE_CODES` 十词闭集 + `http_<status>`)/
393
+ * `toolCount`(有限数)—— 全是引擎或**声明方**产的闭形值,非用户内容 ⇒ 不脱敏、逐字。
394
+ * ⚠️ `errorCode` **不是纯闭集**:十个具名词之外还有 `http_<status>` 形(core 自陈)⇒ 本仓既不枚举
395
+ * 那十个词、也不给它套形正则(词的单一属主在 core;抄一份会把 core 加的新词与新形一起吞成缺席),
396
+ * 只判非空串,消费端按具名词写的 switch 必须带 default 臂。
397
+ * ③ 🔴 **`error` 有意不投**:它是本投影面上唯一的**远端作者自由文本**。判据与代价逐字在
398
+ * `core-keyset-guard.ts` ⑥b 的 `WiringMcpExcluded` 一行(要点:进来会同时破本段「无自由文本 ⇒
399
+ * 不脱敏」的段内不变量,和 core 7.5.0 [ref] 把远端 MCP 错误文本秘密脱敏收敛成**一个**铸点的单铸律
400
+ * —— server 再脱一遍就是同一语义面的第二个写者)。可操作的因由在 `errorCode`,那正是 core 为这条
401
+ * 消费面造闭集的理由。**带触发条件的裁定**,消费端提真需求时连同 bound/中和归属一起裁一次再翻。
402
+ * ④ **不是 governance 侧信道**:五键描述的是本腿的 MCP 连接尝试,与 lockedConfig/compliance/
403
+ * memoryAdmission/retention 四位无函数关系;core 自己也把它排除在 `configFingerprint` 之外
404
+ * (「connection state is a fact about this leg's attempt, not about the assembly」)。
405
+ * ⚠️ 空数组 ≠ 缺席:core 顶注逐字「an empty array is "this leg declared no servers", absence is an older
406
+ * mint or an external derivation」⇒ 空数组照铸,别折成缺席。
382
407
  * 未来 core 加一段而它没有 audience 标签时,**先在这里补一条裁定再决定挑不挑键** —— 段级完备性钉
383
408
  * (test/wiring-manifest-projection.test.ts)会在那一刻先把人拦下来 —— ⚠️ **但那条钉是夹具驱动的**:它只看
384
409
  * `deriveWiringManifest(EFFECTIVE_FACTS)` 真铸出来的段,facts 不带的可选段(147③ 的 `autoMode` 就是)它一格
@@ -96,6 +96,7 @@ export function toolEndEventData(ev) {
96
96
  ...(typeof ev.errorCode === "string" && ev.errorCode.length <= 128 ? { errorCode: ev.errorCode } : {}),
97
97
  ...(isApprovalSettledBy(ev.settledBy) ? { settledBy: ev.settledBy } : {}),
98
98
  ...(isAskDenyResolution(ev.resolution) ? { resolution: ev.resolution } : {}),
99
+ ...(ev.autoDenied === true ? { autoDenied: true } : {}),
99
100
  ...(typeof ev.approver === "string" && ev.approver !== "" ? { approver: redactSecrets(ev.approver) } : {}),
100
101
  ...(typeof ev.gatedCallId === "string" && ev.gatedCallId !== "" ? { gatedCallId: ev.gatedCallId } : {}),
101
102
  ...identityFields(ev),
@@ -269,6 +270,29 @@ export function wiringManifestEventData(ev) {
269
270
  return undefined;
270
271
  return armed === (reason === "armed") ? { armed, reason } : undefined;
271
272
  })();
273
+ const mcpIn = m.mcp;
274
+ const mcp = (() => {
275
+ if (!Array.isArray(mcpIn))
276
+ return undefined;
277
+ const rows = [];
278
+ for (const raw of mcpIn) {
279
+ const e = sec(raw);
280
+ if (e === undefined)
281
+ continue;
282
+ const name = str(e.name);
283
+ const status = str(e.status);
284
+ if (name === undefined || status === undefined)
285
+ continue;
286
+ rows.push({
287
+ name,
288
+ status,
289
+ ...(num(e.toolCount) !== undefined ? { toolCount: e.toolCount } : {}),
290
+ ...(str(e.source) !== undefined ? { source: e.source } : {}),
291
+ ...(str(e.errorCode) !== undefined ? { errorCode: e.errorCode } : {}),
292
+ });
293
+ }
294
+ return rows;
295
+ })();
272
296
  return {
273
297
  ...(num(m.schemaVersion) !== undefined ? { schemaVersion: m.schemaVersion } : {}),
274
298
  ...(str(legIn.kind) !== undefined ? { leg: { kind: legIn.kind } } : {}),
@@ -282,6 +306,7 @@ export function wiringManifestEventData(ev) {
282
306
  ...(permissionRules !== undefined ? { permissionRules } : {}),
283
307
  ...(modelGate !== undefined ? { modelGate } : {}),
284
308
  ...(autoMode !== undefined ? { autoMode } : {}),
309
+ ...(mcp !== undefined ? { mcp } : {}),
285
310
  ...identityFields(ev),
286
311
  };
287
312
  }
@@ -432,6 +457,7 @@ export function toolResultFieldsOf(d) {
432
457
  ...(isApprovalSettledBy(d.settledBy) ? { settledBy: d.settledBy } : {}),
433
458
  ...(typeof d.approver === "string" && d.approver !== "" ? { approver: d.approver } : {}),
434
459
  ...(isAskDenyResolution(d.resolution) ? { resolution: d.resolution } : {}),
460
+ ...(d.autoDenied === true ? { autoDenied: true } : {}),
435
461
  ...(typeof d.gatedCallId === "string" && d.gatedCallId !== "" ? { gatedCallId: d.gatedCallId } : {}),
436
462
  ...identityFields(d),
437
463
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.59.0",
3
+ "version": "7.60.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.4.0",
57
+ "@sema-agent/core": "7.5.0",
58
58
  "@sema-agent/settings-schema": "1.6.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",