@sema-agent/server 7.64.0 → 7.66.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 +18 -5
- package/dist/boot/governance-seams.js +1 -1
- package/dist/boot/resolve-spec.d.ts +0 -5
- package/dist/boot/resolve-spec.js +3 -4
- package/dist/boot/stores.js +3 -3
- package/dist/capabilities/hands-lane.d.ts +55 -13
- package/dist/capabilities/hands-lane.js +22 -11
- package/dist/config-catalog.js +1 -1
- package/dist/config-center/read-face.js +10 -8
- package/dist/config-center/skills-mcp.d.ts +3 -3
- package/dist/config-center/skills-mcp.js +17 -4
- package/dist/config-center/types.d.ts +11 -0
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +24 -5
- package/dist/config.js +7 -4
- package/dist/http/routes/approvals-assistant.js +18 -0
- package/dist/http/routes/diagnostics.d.ts +0 -11
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/tasks.js +6 -1
- package/dist/http/server.js +1 -1
- package/dist/main.js +2 -4
- package/dist/mcp-tool-faces.d.ts +64 -0
- package/dist/mcp-tool-faces.js +93 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +68 -7
- package/dist/plugins/checkpoint-store-sql.js +90 -25
- package/dist/plugins/local-checkpoint-store.d.ts +19 -1
- package/dist/plugins/local-checkpoint-store.js +6 -1
- package/dist/plugins/pg-pool.js +5 -0
- package/dist/plugins/tidb-pool.js +11 -0
- package/dist/read-face-posture.d.ts +52 -0
- package/dist/read-face-posture.js +20 -0
- package/dist/task-mcp.d.ts +18 -4
- package/dist/task-mcp.js +23 -4
- package/dist/tool-approval.js +2 -2
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/project.d.ts +21 -0
- package/dist/trace/project.js +21 -1
- package/package.json +4 -4
package/USAGE.md
CHANGED
|
@@ -594,10 +594,11 @@ WRITE_PROTECTED_TABLE_REPLACE='[{"name":".envrc","kind":"basename"}]' # 整表
|
|
|
594
594
|
- **leader 车道同席**:与其余部署姿态席同一个取值点(`buildDeploymentPostureSeats`),主 runner / subRunner /
|
|
595
595
|
run-local / leader 五只 Runner 同表。
|
|
596
596
|
|
|
597
|
-
**可选 — READ 面姿态(readFace,7.18.0
|
|
597
|
+
**可选 — READ 面姿态(readFace,7.18.0 起;缺席:单机 ⇒ `open`,多租户 ⇒ 引擎当家 —— 7.65.0 起,见下)**
|
|
598
598
|
|
|
599
599
|
```bash
|
|
600
|
-
READ_FACE=open # 词表 open|roots
|
|
600
|
+
READ_FACE=open # 词表 open|roots;坏词启动期响亮拒
|
|
601
|
+
# 不设:单用户 turnkey(REQUIRE_PRINCIPAL 未设)⇒ open;多租户 ⇒ 不传给引擎(引擎默认 roots)
|
|
601
602
|
READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitive":true}]'
|
|
602
603
|
```
|
|
603
604
|
- **`READ_FACE`**:READ 工具面(读文件/遍历/搜索)的部署级姿态。引擎默认 **`roots`** = 读被夹在任务的
|
|
@@ -609,8 +610,19 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
|
|
|
609
610
|
退出。这些条目**叠在** core 内建 deny 集之上(内建先、追加后,重复折叠),不是替换。
|
|
610
611
|
- **坏形拒启,不静默折默认**:坏 JSON / 非数组 / 缺 `pattern` / 编不过 core 的 `compileReadDeny`
|
|
611
612
|
一律启动期点名拒——一个手滑的 deny 集比没有 deny 集更糟(以为挡了其实没挡)。
|
|
612
|
-
-
|
|
613
|
-
|
|
613
|
+
- **缺席的语义按 posture 分两形(7.65.0 起,S-167)**:
|
|
614
|
+
· **单用户 turnkey**(`REQUIRE_PRINCIPAL` 未设 —— 一台机器一个可信主人,CC 的信任模型):`READ_FACE`
|
|
615
|
+
缺席 ⇒ 铸 **`open`**,工作区外的读默认放行。**这是 7.65.0 的行为变更**:同一台机器升级前后
|
|
616
|
+
「读工作区外的文件」从被拒变成放行。**要钉回收敛姿态:显式 `READ_FACE=roots`。**
|
|
617
|
+
· **多租户**(`REQUIRE_PRINCIPAL=true`):缺席仍**不铸**、不传给引擎(引擎默认 `roots`)——**一个字节不变**。
|
|
618
|
+
· `READ_DENY_PATTERNS` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE` 三键的「缺席不铸」
|
|
619
|
+
**不变**;**内建读拒表照旧生效** —— `open` 放开的是「工作区外」这道围栏,凭证 / shell 历史 / 浏览器 /
|
|
620
|
+
钱包那几档 deny 在 `open` 面上照样拦(两者在引擎里是并列机制)。
|
|
621
|
+
· 「这一档到底是谁定的」由 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace` 段答:
|
|
622
|
+
`{face, source, note}`,`source` ∈ `env`(本机 env 显式)/ `center`(组织下发)/ `posture`(单机派生)/
|
|
623
|
+
`engine-default`(没钉,引擎当家),`note` 是指路句。
|
|
624
|
+
- **显式值恒赢**:优先序 `env` > `center` > `posture` > 引擎默认;引擎抬默认/扩内建集那天,没钉过的
|
|
625
|
+
多租户部署自动跟上(与重试上限同款「引擎当家」纪律)。
|
|
614
626
|
- **leader 车道同席(7.54 起)**:`LEADER_ENABLED` 的 leader 编排(planner / worker / repair / conflict 四类
|
|
615
627
|
任务)与主车道吃**同一份**四键(连同 `MEMORY_DELEGATION_EVIDENCE` / `MEMORY_PROVENANCE` / `MEMORY_CAPTURE_POLICY`
|
|
616
628
|
与委派入口 caps;取值点唯一 = `buildDeploymentPostureSeats`,leader 不另读 env)。此前(≤7.53)那条腿一席都不带:
|
|
@@ -625,7 +637,8 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
|
|
|
625
637
|
保留 env 前值 + `sema_registry_read_face_invalid` warn(半应用一份 READ 姿态比不应用更危险);
|
|
626
638
|
③ **restart-to-apply**——四键在 boot 期被引擎装配捕获,下发改动经 `/health` 的 `restart.reasons`
|
|
627
639
|
带 `read-face` 通告,由编排器重启兑现。当前生效档可从 `GET /v1/capabilities` 的 `readFace` 位读
|
|
628
|
-
(`"open"`/`"roots"`/`null`=本部署未钉,引擎默认当家;deny 表内容属策略内容,**不上**能力面)
|
|
640
|
+
(`"open"`/`"roots"`/`null`=本部署未钉,引擎默认当家;deny 表内容属策略内容,**不上**能力面);
|
|
641
|
+
**来源**(env / center / posture / engine-default)在 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace.source`。
|
|
629
642
|
⚠️ 本地 `config.d/` 车道今天**接不上**这一域(registry-core 的可移植域表里没有它),只有
|
|
630
643
|
`CONFIG_PROVIDER=remote` 的部署走得通。
|
|
631
644
|
|
|
@@ -36,7 +36,7 @@ export function createGovernanceSeams(ctx) {
|
|
|
36
36
|
assertGovernanceAssemblable({
|
|
37
37
|
lockedKeys,
|
|
38
38
|
complianceDenies,
|
|
39
|
-
deploymentMcpConfigured: (config.mcpServers?.length ?? 0) > 0,
|
|
39
|
+
deploymentMcpConfigured: (config.mcpServers?.servers.length ?? 0) > 0,
|
|
40
40
|
webFetchMounted: config.requirePrincipal !== true,
|
|
41
41
|
});
|
|
42
42
|
const lockedConfig = config.lockedConfigKeys !== undefined && config.lockedConfigKeys.length > 0 ? { keys: config.lockedConfigKeys } : undefined;
|
|
@@ -3,7 +3,6 @@ import type { PromptsDomainFaces } from "../prompts-domain-validate.js";
|
|
|
3
3
|
import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
|
|
4
4
|
import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
|
|
5
5
|
import { selectScenario } from "../capabilities/scenarios.js";
|
|
6
|
-
import { type HandsLaneRegistry } from "../capabilities/hands-lane.js";
|
|
7
6
|
import type { ServiceConfig } from "../config.js";
|
|
8
7
|
import { type LiveQuestionFace } from "../deployment-governance.js";
|
|
9
8
|
import { FleetEventBus } from "../fleet/fleet-bus.js";
|
|
@@ -23,10 +22,6 @@ type PrincipalCaps = ReturnType<typeof createPrincipalEntitlementsClient>;
|
|
|
23
22
|
/** `resolveSpec` 原先从 `main()` 闭包里拿到的全部 boot 局部量。 */
|
|
24
23
|
export interface ResolveSpecCtx {
|
|
25
24
|
config: ServiceConfig;
|
|
26
|
-
/** [ref]:hands lane 登记簿。resolveSpec 在产出 TaskSpec 时按场景判别位登记本请求的表态,HTTP 执行点
|
|
27
|
-
* 凭同一只登记簿选 Runner —— **必须与 main.ts 传给 `runnerFor` 的是同一实例**(异实例 = 每个请求都
|
|
28
|
-
* 落回 full,收窄静默失效)。 */
|
|
29
|
-
handsLanes: HandsLaneRegistry;
|
|
30
25
|
logger: Logger;
|
|
31
26
|
metrics: Metrics;
|
|
32
27
|
localRoot: string;
|
|
@@ -6,7 +6,7 @@ import { centerPromptProvider, centerIdentityAssembled } from "../capabilities/c
|
|
|
6
6
|
import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
|
|
7
7
|
import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
|
|
8
8
|
import { gateScenarioRequest, mergeUserSkills, selectScenario } from "../capabilities/scenarios.js";
|
|
9
|
-
import { toolPolicyForHands } from "../capabilities/hands-lane.js";
|
|
9
|
+
import { stampHandsLane, toolPolicyForHands } from "../capabilities/hands-lane.js";
|
|
10
10
|
import { shouldDiscloseNoPersistentMemory, withNoPersistentMemoryNotice } from "../capabilities/memory-notice.js";
|
|
11
11
|
import { applyLongtailDefer } from "../capabilities/tool-defer.js";
|
|
12
12
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
|
|
@@ -36,7 +36,7 @@ import { redactSecrets } from "../trace/redact.js";
|
|
|
36
36
|
import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
37
37
|
import { effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
|
|
38
38
|
export function createResolveSpec(ctx) {
|
|
39
|
-
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver,
|
|
39
|
+
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, noteSessionShellGate, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
|
|
40
40
|
assertGuardPatternsUsable(config);
|
|
41
41
|
const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, { durableEnabled, singleUserAutoAcceptBaseline });
|
|
42
42
|
if (onlySensitiveBaseline)
|
|
@@ -62,8 +62,7 @@ export function createResolveSpec(ctx) {
|
|
|
62
62
|
scratchpadDir: folded.scratchpadDir,
|
|
63
63
|
hostSemanticsLane: folded.hostSemanticsLane,
|
|
64
64
|
});
|
|
65
|
-
|
|
66
|
-
return final;
|
|
65
|
+
return stampHandsLane(final, gated.hands);
|
|
67
66
|
};
|
|
68
67
|
const gateScenarioAndAppend = async (body, auth, opts) => {
|
|
69
68
|
const requested = gateScenarioRequest(await principalCaps?.scenarioRuling(auth?.principal), body.scenario, config.defaultScenario, Object.keys(scenarios), logger);
|
package/dist/boot/stores.js
CHANGED
|
@@ -24,7 +24,7 @@ import { buildMemoryRemoteLaneWarn, memoryEngineBackendFor, memoryEngineRemoteLa
|
|
|
24
24
|
import { assertToolResultProvenanceSchema } from "../plugins/tool-result-store-sql.js";
|
|
25
25
|
import { assertPermissionRuleApprovalSchema, assertPermissionRuleTicketSchema } from "../plugins/permission-rule-store-sql.js";
|
|
26
26
|
import { assertApprovalAskRuleMaterialSchema } from "../plugins/approval-ask-store-sql.js";
|
|
27
|
-
import {
|
|
27
|
+
import { assertCheckpointColumnSchema } from "../plugins/checkpoint-store-sql.js";
|
|
28
28
|
import { assertFileHistorySchema } from "../plugins/file-history-store-sql.js";
|
|
29
29
|
import { resolveStreamApprovalGate } from "../tool-approval.js";
|
|
30
30
|
import { buildMemoryWriteBoundaryAudit, effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
|
|
@@ -56,9 +56,9 @@ export async function openStores(ctx) {
|
|
|
56
56
|
else if (mysqlPool)
|
|
57
57
|
await assertToolResultProvenanceSchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
|
|
58
58
|
if (pgPool)
|
|
59
|
-
await
|
|
59
|
+
await assertCheckpointColumnSchema(async (sql) => ({ rows: (await pgPool.query(sql)).rows }), "pg");
|
|
60
60
|
else if (mysqlPool)
|
|
61
|
-
await
|
|
61
|
+
await assertCheckpointColumnSchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
|
|
62
62
|
if (config.permissionRulesEnabled) {
|
|
63
63
|
if (pgPool)
|
|
64
64
|
await assertPermissionRuleApprovalSchema(async (sql) => ({ rows: (await pgPool.query(sql)).rows }), "pg");
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* 不是静默继承 full(词表闭集纪律)。
|
|
13
13
|
* ② {@link HANDS_EXCLUSIVE_TOOL_NAMES} + {@link handsDenyPolicy} —— 纵深:hands=none 的任务即便被误接到
|
|
14
14
|
* 有手 Runner 上,手独占名单逐一 deny。**不是**主保证(主保证是根本不挂 factory ⇒ 工具面不可见)。
|
|
15
|
-
* ③ {@link
|
|
15
|
+
* ③ {@link stampHandsLane} / {@link handsLaneOf} —— 车道作为 TaskSpec 上的**显式字段**(S-149):
|
|
16
|
+
* resolveSpec 出口铸一次,执行点读一次,**缺席即拒**(不是回落最宽车道)。
|
|
16
17
|
*/
|
|
17
18
|
import { type Runner, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
18
19
|
/** 场景对手带工具面的表态(闭集)。`full` = core 手带 band 照常 mount;`none` = 不挂 executionEnvFactory 的 Runner。 */
|
|
@@ -36,7 +37,9 @@ export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerP
|
|
|
36
37
|
* core 手带 band 的工具名**全集** —— 「挂 executionEnvFactory 才出现、不挂就消失」的那一批。
|
|
37
38
|
*
|
|
38
39
|
* 两段来源,都不是拍脑袋:
|
|
39
|
-
* · `
|
|
40
|
+
* · `handBandEffects()`(core 7.8.0 B1 起的导出面;此前是手抄表常量 `HAND_TOOL_EFFECTS`)—— band 的
|
|
41
|
+
* effect 表,由**静态工具目录**按 `mountedBy.includes("hands")` 派生。名册单源之后这张表不再是一张
|
|
42
|
+
* 手抄名表,加/退工具由目录一处说了算。
|
|
40
43
|
* · 下方锚定补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
|
|
41
44
|
* test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
|
|
42
45
|
* core 加/改 band 成员时红,防漂移)。
|
|
@@ -82,19 +85,58 @@ export declare function tightenWithHandsDeny(base: ToolPolicy | undefined): Tool
|
|
|
82
85
|
/** 装配点的取用形:`full` 原样放行基线,`none` 叠 deny。**穷举 switch**,无缺省臂。 */
|
|
83
86
|
export declare function toolPolicyForHands(hands: ScenarioHands, base: ToolPolicy | undefined): ToolPolicy | undefined;
|
|
84
87
|
/**
|
|
85
|
-
*
|
|
88
|
+
* 执行车道在 TaskSpec 上的**显式字段键**(S-149;修前是一张以 spec **对象身份**为键的 WeakMap 旁表)。
|
|
86
89
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
90
|
+
* 为什么是 symbol 而不是字符串键:`TaskSpec` 是**上游 core 的类型面**,一个字符串键会进
|
|
91
|
+
* `Object.keys` / `JSON.stringify` / core 自己的键审计,等于 server 在别人的契约上私自加一个键;
|
|
92
|
+
* symbol 键随 spread(`{...spec}`)与 rest 解构(`const {objective, ...taskConfig} = spec`)结构性
|
|
93
|
+
* 地跟着走,却对上游的键面**不可见**(结构化克隆与 JSON 同样跳过 symbol 键)。于是「车道是 spec 上
|
|
94
|
+
* 的一个字段、复制 spec 不丢车道」与「不污染上游契约」两件事同时成立。
|
|
91
95
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
96
|
+
* 修前的旁表要求「从 resolveSpec 出来的那一只对象必须一路原样活到执行点」——一条**隐形**的对象身份
|
|
97
|
+
* 契约:任何一次 `{...spec}` 都会静默把车道降级成最宽的 `full`(实测:登记 `none` 的 spec 复制后
|
|
98
|
+
* `laneOf` 答 `full`)。字段化把那条隐形要求整条删掉。
|
|
94
99
|
*/
|
|
95
|
-
export
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
export declare const HANDS_LANE_FIELD: unique symbol;
|
|
101
|
+
/** 已铸车道的 TaskSpec。铸点唯一(`boot/resolve-spec.ts` 的 resolveSpec 出口),读点唯一({@link handsLaneOf})。 */
|
|
102
|
+
export type LanedTaskSpec = TaskSpec & {
|
|
103
|
+
readonly [HANDS_LANE_FIELD]: ScenarioHands;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* 铸车道(**单一属主**:只允许 resolveSpec 的出口调用)。
|
|
107
|
+
*
|
|
108
|
+
* 字段是 `enumerable`(所以复制得走)、`writable:false` + `configurable:false`(所以同一只 spec 被
|
|
109
|
+
* 第二次铸车道会当场 TypeError —— 两个写者是漂移的成因,响亮好过后到者悄悄赢)。
|
|
110
|
+
*/
|
|
111
|
+
export declare function stampHandsLane<T extends TaskSpec>(spec: T, hands: ScenarioHands): T & LanedTaskSpec;
|
|
112
|
+
/**
|
|
113
|
+
* 车道字段缺席的拒因 —— 具名类,便于调用点/测试按类型判,而不是嗅文案。
|
|
114
|
+
*
|
|
115
|
+
* 🔴 **刻意不是 `HttpError`、刻意不铸新的 wire 错误码**:它落到 `http/server.ts` 的兜底 catch,
|
|
116
|
+
* wire 上是 500 `internal.error`(内部文本不回显,只进 `request_error` 日志行)。理由两条:
|
|
117
|
+
* ① 对**消费端**它与任何一次服务端内部故障无差别可操作性(重试一次,仍复现即报运维)—— 附录 A 多一个
|
|
118
|
+
* 码却给不出不同的动作,是没有价值的契约面;
|
|
119
|
+
* ② 本仓的中继码登记表(`test/error-code-catalog-live.test.ts` 的 `RELAYED`,上限 24 条)明写「超了说明
|
|
120
|
+
* 『码不写字面量』正在变成默认写法,该反过来改代码」—— 一个只能靠登记才看得见的新码正是它拦的东西。
|
|
121
|
+
* 需要分辨的是**运维**,而运维读的是日志行(本 message 逐字在 `request_error` 里)。
|
|
122
|
+
*/
|
|
123
|
+
export declare class HandsLaneUnstampedError extends Error {
|
|
124
|
+
constructor();
|
|
98
125
|
}
|
|
99
|
-
|
|
126
|
+
/**
|
|
127
|
+
* 读车道 —— **缺席即拒**([ref] P 类:车道选择是保护型量,fail-closed;`observability/fail-open.ts`
|
|
128
|
+
* 头注把「车道选择」逐字列在「正解是 fail-closed,不是留痕后继续放行」那一族里)。
|
|
129
|
+
*
|
|
130
|
+
* 修前此处是 `?? "full"`:一只没经过本仓 resolver 的 spec 会**静默拿到最宽的车道**(全量可写手带)。
|
|
131
|
+
* 「唯一可达形是测试桩」这个论证不是防线 —— 它是一句关于今天调用图的断言,而防线要防的正是明天多出
|
|
132
|
+
* 来的第二个执行点。缺席现在整只拒(500 + 日志留原句),而不是把一个未经裁定的任务放进有手车道。
|
|
133
|
+
*/
|
|
134
|
+
export declare function handsLaneOf(spec: TaskSpec): ScenarioHands;
|
|
135
|
+
/**
|
|
136
|
+
* 装配点的选路席:`{full, handsless}` 两只 Runner + 车道字段 ⇒ 本任务跑哪一只。
|
|
137
|
+
*
|
|
138
|
+
* 具名工厂而不是在 main.ts 手搓闭包:选路规则(读字段 → 穷举 switch)于是只有**一份实现**,
|
|
139
|
+
* 测试与生产装配拿的是同一只(手搓等价闭包 = 第二真源,正是本件要删的那一类)。
|
|
140
|
+
*/
|
|
141
|
+
export declare function createHandsRunnerSelector(pair: HandsRunnerPair): (spec: TaskSpec) => Runner;
|
|
100
142
|
//# sourceMappingURL=hands-lane.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { handBandEffects, RETIRED_TOOL_NAMES, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
|
|
2
2
|
export function withoutExecutionEnv(deps) {
|
|
3
3
|
const { executionEnvFactory: _handsDropped, ...rest } = deps;
|
|
4
4
|
return rest;
|
|
@@ -12,13 +12,13 @@ export function pickHandsRunner(hands, pair) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
export const HANDS_BAND_TOOL_NAMES = Object.freeze([
|
|
15
|
-
...
|
|
15
|
+
...handBandEffects().keys(),
|
|
16
16
|
"SendMessage",
|
|
17
17
|
"AgentTranscript",
|
|
18
18
|
"Monitor",
|
|
19
19
|
"EnterWorktree",
|
|
20
20
|
"ExitWorktree",
|
|
21
|
-
].filter((n) => !RETIRED_TOOL_NAMES.has(n)));
|
|
21
|
+
].filter((n, i, all) => !RETIRED_TOOL_NAMES.has(n) && all.indexOf(n) === i));
|
|
22
22
|
export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript"]);
|
|
23
23
|
export const HANDS_EXCLUSIVE_TOOL_NAMES = Object.freeze(HANDS_BAND_TOOL_NAMES.filter((n) => !HANDS_DUAL_USE_TOOL_NAMES.includes(n)));
|
|
24
24
|
export function handsDenyPolicy() {
|
|
@@ -36,13 +36,24 @@ export function toolPolicyForHands(hands, base) {
|
|
|
36
36
|
return tightenWithHandsDeny(base);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
export const HANDS_LANE_FIELD = Symbol("sema-server.handsLane");
|
|
40
|
+
export function stampHandsLane(spec, hands) {
|
|
41
|
+
Object.defineProperty(spec, HANDS_LANE_FIELD, { value: hands, enumerable: true, writable: false, configurable: false });
|
|
42
|
+
return spec;
|
|
43
|
+
}
|
|
44
|
+
export class HandsLaneUnstampedError extends Error {
|
|
45
|
+
constructor() {
|
|
46
|
+
super("execution lane is not stamped on this task spec — refusing to run it rather than falling back to the widest (full-hands) lane");
|
|
47
|
+
this.name = "HandsLaneUnstampedError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function handsLaneOf(spec) {
|
|
51
|
+
const lane = spec[HANDS_LANE_FIELD];
|
|
52
|
+
if (lane === undefined)
|
|
53
|
+
throw new HandsLaneUnstampedError();
|
|
54
|
+
return lane;
|
|
55
|
+
}
|
|
56
|
+
export function createHandsRunnerSelector(pair) {
|
|
57
|
+
return (spec) => pickHandsRunner(handsLaneOf(spec), pair);
|
|
47
58
|
}
|
|
48
59
|
//# sourceMappingURL=hands-lane.js.map
|
package/dist/config-catalog.js
CHANGED
|
@@ -321,7 +321,7 @@ export const CONFIG_CATALOG = [
|
|
|
321
321
|
r("READ_DENY_BUILTIN_EXCLUDE", "orchestration", "csv", "按行名删内建 deny 条目(档位之后生效;绝不削任务显式 deny)", { danger: SEC, configKey: "readDenyBuiltinExclude", center: cRead("denyBuiltinExclude", "readDenyBuiltinExclude") }),
|
|
322
322
|
r("READ_DENY_BUILTIN_TIERS", "orchestration", "csv", "内建 deny 表档位选择(闭集四档;none=显式空姿态)", { danger: SEC, configKey: "readDenyBuiltinTiers", center: cRead("denyBuiltinTiers", "readDenyBuiltinTiers") }),
|
|
323
323
|
r("READ_DENY_PATTERNS", "orchestration", "csv", "READ 面额外 deny pattern 表", { danger: SEC, configKey: "readDenyPatterns", center: cRead("denyPatterns", "readDenyPatterns") }),
|
|
324
|
-
r("READ_FACE", "orchestration", "enum", "READ 容纳面档位(env 逐键恒赢 center —— 部署主权表达)", { enumValues: ["open", "roots"], danger: SEC, derivedDefaultNote: "缺席 ⇒
|
|
324
|
+
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") }),
|
|
325
325
|
r("REAP_INTERVAL_SEC", "limitsHttp", "number", "reaper 周期(秒)", { staticDefault: "60", configKey: "reapIntervalSec" }),
|
|
326
326
|
r("REAP_RUN_STALE_SEC", "limitsHttp", "number", "run 活性判陈旧阈(秒)", { staticDefault: "120", configKey: "runStaleSec", center: cLimits("runStaleSec") }),
|
|
327
327
|
r("REMOTE_EXEC", "orchestration", "enum", "执行车道选择(点名而配不齐/认不出 ⇒ 拒启,绝不静默降级到 host/stub)", { enumValues: ["e2b", "k8s", "ssh", "adb", "host", "local-docker", "device"], danger: SEC, derivedDefaultNote: "缺席:CONFIG_PROVIDER=local ⇒ host;其余 ⇒ 进程内 stub", configKey: "remoteExec", resolve: (c) => c.remoteExec?.provider ?? null }),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { compileReadDeny, resolveReadDenyBuiltins } from "@sema-agent/core";
|
|
2
2
|
const KEY_MAP = [
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
{ centerKey: "face", cfgKey: "readFace", envHolds: (c) => c.readFaceSource === "env" },
|
|
4
|
+
{ centerKey: "denyPatterns", cfgKey: "readDenyPatterns", envHolds: (c) => c.readDenyPatterns !== undefined },
|
|
5
|
+
{ centerKey: "denyBuiltinTiers", cfgKey: "readDenyBuiltinTiers", envHolds: (c) => c.readDenyBuiltinTiers !== undefined },
|
|
6
|
+
{ centerKey: "denyBuiltinExclude", cfgKey: "readDenyBuiltinExclude", envHolds: (c) => c.readDenyBuiltinExclude !== undefined },
|
|
7
7
|
];
|
|
8
8
|
class ReadFaceDomainError extends Error {
|
|
9
9
|
}
|
|
@@ -43,7 +43,7 @@ function normalizeNameList(field, raw) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
export function applyCenterReadFace(config, eff, logger) {
|
|
46
|
-
const envHeldKeys = KEY_MAP.filter((
|
|
46
|
+
const envHeldKeys = KEY_MAP.filter((r) => r.envHolds(config)).map((r) => r.centerKey);
|
|
47
47
|
const domain = eff.readFace;
|
|
48
48
|
if (domain === undefined || domain === null)
|
|
49
49
|
return envHeldKeys;
|
|
@@ -55,7 +55,7 @@ export function applyCenterReadFace(config, eff, logger) {
|
|
|
55
55
|
return envHeldKeys;
|
|
56
56
|
}
|
|
57
57
|
const d = domain;
|
|
58
|
-
const envHeld = KEY_MAP.filter((
|
|
58
|
+
const envHeld = KEY_MAP.filter((r) => d[r.centerKey] !== undefined && r.envHolds(config)).map((r) => `${r.cfgKey} (readFace.${r.centerKey})`);
|
|
59
59
|
let staged;
|
|
60
60
|
try {
|
|
61
61
|
const normalized = {
|
|
@@ -69,7 +69,7 @@ export function applyCenterReadFace(config, eff, logger) {
|
|
|
69
69
|
if (normalized.readDenyBuiltinTiers !== undefined || normalized.readDenyBuiltinExclude !== undefined) {
|
|
70
70
|
resolveReadDenyBuiltins(builtinsArg(normalized.readDenyBuiltinTiers, normalized.readDenyBuiltinExclude));
|
|
71
71
|
}
|
|
72
|
-
staged = Object.fromEntries(KEY_MAP.filter((
|
|
72
|
+
staged = Object.fromEntries(KEY_MAP.filter((r) => !r.envHolds(config)).map((r) => [r.cfgKey, normalized[r.cfgKey]]).filter(([, v]) => v !== undefined));
|
|
73
73
|
const finalPatterns = staged.readDenyPatterns ?? config.readDenyPatterns;
|
|
74
74
|
const finalTiers = staged.readDenyBuiltinTiers ?? config.readDenyBuiltinTiers;
|
|
75
75
|
const finalExclude = staged.readDenyBuiltinExclude ?? config.readDenyBuiltinExclude;
|
|
@@ -85,8 +85,10 @@ export function applyCenterReadFace(config, eff, logger) {
|
|
|
85
85
|
});
|
|
86
86
|
return envHeldKeys;
|
|
87
87
|
}
|
|
88
|
-
if (staged.readFace !== undefined)
|
|
88
|
+
if (staged.readFace !== undefined) {
|
|
89
89
|
config.readFace = staged.readFace;
|
|
90
|
+
config.readFaceSource = "center";
|
|
91
|
+
}
|
|
90
92
|
if (staged.readDenyPatterns !== undefined)
|
|
91
93
|
config.readDenyPatterns = staged.readDenyPatterns;
|
|
92
94
|
if (staged.readDenyBuiltinTiers !== undefined)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { A2aServerSpec, McpServerSpec } from "@sema-agent/core";
|
|
2
|
-
import type {
|
|
2
|
+
import type { CenterMcp, ScopedA2aPeer } from "../config-types.js";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
4
|
import type { LoadedSkill } from "../capabilities/skills.js";
|
|
5
5
|
import type { CenterA2aPeer, CenterMcpServer, CenterSkillManifest } from "./types.js";
|
|
@@ -26,10 +26,10 @@ diskCacheDir?: string): Promise<LoadedSkill[]>;
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function resolveMcpServers(mcp: {
|
|
28
28
|
servers: CenterMcpServer[];
|
|
29
|
-
}, logger?: Logger):
|
|
29
|
+
}, logger?: Logger): CenterMcp;
|
|
30
30
|
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
31
31
|
* [ref]:alias 窗撤销,单键命中(同 `skillsForScenario`)。 */
|
|
32
|
-
export declare function mcpForScenario(
|
|
32
|
+
export declare function mcpForScenario(center: CenterMcp | undefined, scenario: string): McpServerSpec[] | undefined;
|
|
33
33
|
/**
|
|
34
34
|
* DESIGN-269 §2.1 —— resolve center A2A peers (`/effective.a2a`) into core `A2aServerSpec`s + the scenarios
|
|
35
35
|
* each applies to. Exact sibling of {@link resolveMcpServers}, same three rules:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readMcpSourceLabel } from "../mcp-source-label.js";
|
|
2
|
+
import { readMcpToolFaces } from "../mcp-tool-faces.js";
|
|
2
3
|
import { skillContentHash } from "@sema-agent/settings-schema";
|
|
3
4
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
|
|
4
5
|
import { fetchSkillContent } from "./http-client.js";
|
|
@@ -79,6 +80,7 @@ function resolveRefs(refs, server, kind, logger, event = "sema_registry_mcp_env_
|
|
|
79
80
|
}
|
|
80
81
|
export function resolveMcpServers(mcp, logger) {
|
|
81
82
|
const out = [];
|
|
83
|
+
const reservedNames = (mcp.servers ?? []).map((s) => s.name);
|
|
82
84
|
for (const s of mcp.servers ?? []) {
|
|
83
85
|
if (s.enabled === false)
|
|
84
86
|
continue;
|
|
@@ -97,13 +99,23 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
101
|
const source = readMcpSourceLabel(s.name, s.source);
|
|
102
|
+
const facesRead = readMcpToolFaces(s.toolFaces);
|
|
103
|
+
if (!facesRead.ok) {
|
|
104
|
+
logger?.warn("sema_registry_mcp_tool_faces_invalid", {
|
|
105
|
+
server: s.name,
|
|
106
|
+
problem: facesRead.problem,
|
|
107
|
+
note: "server SKIPPED — a toolFaces overlay is a NARROWING declaration (its pathTarget is what the sensitive-path / write fences read); mounting the server without it would silently drop the protection you declared. Fix the entry and republish.",
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const toolFaces = facesRead.faces === undefined ? {} : { toolFaces: facesRead.faces };
|
|
100
112
|
if (s.transport.kind === "stdio") {
|
|
101
113
|
const env = resolveRefs(s.transport.envRefs, s.name, "env", logger);
|
|
102
114
|
if (env === null)
|
|
103
115
|
continue;
|
|
104
116
|
out.push({
|
|
105
117
|
scenarios: s.scenarios ?? [],
|
|
106
|
-
spec: { name: s.name, transport: { kind: "stdio", command: s.transport.command, args: s.transport.args, ...(Object.keys(env).length ? { env } : {}) }, ...allow, ...elicit, ...contentOrigin, ...source },
|
|
118
|
+
spec: { name: s.name, transport: { kind: "stdio", command: s.transport.command, args: s.transport.args, ...(Object.keys(env).length ? { env } : {}) }, ...allow, ...elicit, ...contentOrigin, ...source, ...toolFaces },
|
|
107
119
|
});
|
|
108
120
|
}
|
|
109
121
|
else {
|
|
@@ -112,15 +124,16 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
112
124
|
continue;
|
|
113
125
|
out.push({
|
|
114
126
|
scenarios: s.scenarios ?? [],
|
|
115
|
-
spec: { name: s.name, transport: { kind: "http", url: s.transport.url, ...(Object.keys(headers).length ? { headers } : {}), ...(s.transport.principalHeader ? { principalHeader: s.transport.principalHeader } : {}) }, ...allow, ...elicit, ...contentOrigin, ...source },
|
|
127
|
+
spec: { name: s.name, transport: { kind: "http", url: s.transport.url, ...(Object.keys(headers).length ? { headers } : {}), ...(s.transport.principalHeader ? { principalHeader: s.transport.principalHeader } : {}) }, ...allow, ...elicit, ...contentOrigin, ...source, ...toolFaces },
|
|
116
128
|
});
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
if (out.length > 0)
|
|
120
132
|
logger?.info("sema_registry_mcp", { servers: out.map((s) => s.spec.name) });
|
|
121
|
-
return out;
|
|
133
|
+
return { servers: out, reservedNames };
|
|
122
134
|
}
|
|
123
|
-
export function mcpForScenario(
|
|
135
|
+
export function mcpForScenario(center, scenario) {
|
|
136
|
+
const servers = center?.servers;
|
|
124
137
|
if (!servers || servers.length === 0)
|
|
125
138
|
return undefined;
|
|
126
139
|
const hit = servers.filter((s) => s.scenarios.length === 0 || s.scenarios.includes(scenario)).map((s) => s.spec);
|
|
@@ -116,6 +116,17 @@ export interface CenterMcpServer {
|
|
|
116
116
|
/** B-036 / S-143:声明方自述的分组标签(开集,非空 ≤80;settings-schema 1.7.0 `McpServerSpec.source`)——上 core
|
|
117
117
|
* `McpServerSpec.source`,由 `wiring_manifest.mcp[].source` 逐字回显。显示用,不进信任轴。 */
|
|
118
118
|
source?: string;
|
|
119
|
+
/**
|
|
120
|
+
* 件8(core 7.8.0 [ref] §2.7;settings-schema 1.9.0 `McpServerSpec.toolFaces`)—— 这台 server 的
|
|
121
|
+
* **per-tool 面覆盖层**,键 = server 的**原始(未加命名空间)**工具名,值 = `{ family?, pathTarget?,
|
|
122
|
+
* renderHints?, ruleFace? }`。上 core `McpServerSpec.toolFaces`。
|
|
123
|
+
*
|
|
124
|
+
* 🔴 类型是 `unknown` 而不是那个结构,**刻意的**:本类型是两条 center 腿的**承载形**(本地 config.d 经
|
|
125
|
+
* settings-schema 的 `z.unknown()` 解析透明地过来、远端 /effective 是直读 JSON),判据与坏形处置的单一
|
|
126
|
+
* 属主是 `mcp-tool-faces.ts` 的 `readMcpToolFaces`(`resolveMcpServers` 在铸 spec 那一刻调它)。在承载
|
|
127
|
+
* 形上先写一个结构,等于把同一张判据表抄成两份(而其中一份没有执行力)。
|
|
128
|
+
*/
|
|
129
|
+
toolFaces?: unknown;
|
|
119
130
|
transport: {
|
|
120
131
|
kind: "stdio";
|
|
121
132
|
command: string;
|
package/dist/config-provider.js
CHANGED
|
@@ -144,6 +144,7 @@ export function mapToServiceEffective(eff, version) {
|
|
|
144
144
|
...(s.elicitation !== undefined ? { elicitation: s.elicitation } : {}),
|
|
145
145
|
...(s.contentOrigin !== undefined ? { contentOrigin: s.contentOrigin } : {}),
|
|
146
146
|
...readMcpSourceLabel(s.name, s.source),
|
|
147
|
+
...(s.toolFaces !== undefined ? { toolFaces: s.toolFaces } : {}),
|
|
147
148
|
};
|
|
148
149
|
if (s.transport.kind === "stdio") {
|
|
149
150
|
return {
|
package/dist/config-types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* in config.ts, which re-exports these names so existing `from "./config.js"` importers keep
|
|
6
6
|
* compiling unchanged. All imports below are top-level `import type` (erased — never a load edge,
|
|
7
7
|
* see test/module-cycle-gate.test.ts header on verbatimModuleSyntax). */
|
|
8
|
+
import type { ReadFaceSource } from "./read-face-posture.js";
|
|
8
9
|
import type { SealedKeyPoison } from "./sealed-key.js";
|
|
9
10
|
import type { A2aServerSpec, CompliancePosture, CrossSessionDialogExpiry, CrossSessionInboundSetting, FileHistoryRetentionPolicy, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy, WriteProtectedEntry } from "@sema-agent/core";
|
|
10
11
|
import type { ApprovalHmacKey, PrincipalJwtKey } from "./auth-keys.js";
|
|
@@ -83,6 +84,15 @@ export interface ScopedMcpServer {
|
|
|
83
84
|
scenarios: string[];
|
|
84
85
|
spec: McpServerSpec;
|
|
85
86
|
}
|
|
87
|
+
/** The deployment's MCP declaration, resolved (`resolveMcpServers`). `servers` = what actually MOUNTED (env-NAME refs
|
|
88
|
+
* resolved, `enabled:false` / missing-ref / malformed-`toolFaces` entries skipped, each with its own loud warn);
|
|
89
|
+
* `reservedNames` = EVERY name the deployment declared, mounted or not. A name the deployment declared is CLOSED to
|
|
90
|
+
* request-side injection (`resolveRequestMcp`) whether or not it mounted — otherwise "skipped" would silently mean
|
|
91
|
+
* "free for the caller to refill without the operator's `toolFaces` / `allowTools`" (车CF codex r2 [high]). */
|
|
92
|
+
export interface CenterMcp {
|
|
93
|
+
servers: ScopedMcpServer[];
|
|
94
|
+
reservedNames: readonly string[];
|
|
95
|
+
}
|
|
86
96
|
/** DESIGN-269 §2.1 —— an A2A PEER resolved to a core spec (header-NAME refs already → real values) plus
|
|
87
97
|
* the scenarios it applies to (empty = all). Exact sibling of {@link ScopedMcpServer}: `resolveSpec`
|
|
88
98
|
* filters by scenario (`a2aForScenario`) and passes `spec` to core as `TaskSpec.a2a`.
|
|
@@ -686,8 +696,9 @@ export interface ServiceConfigFlat {
|
|
|
686
696
|
};
|
|
687
697
|
/** MCP servers from sema-registry (`/effective.mcp`), resolved at startup (env-NAME refs → values) and
|
|
688
698
|
* filtered per scenario in `resolveSpec`. Not env-derived — populated by the sema-registry adapter when
|
|
689
|
-
* present (restart-to-apply, like models/roles); undefined = no center MCP (scenarios use no MCP).
|
|
690
|
-
|
|
699
|
+
* present (restart-to-apply, like models/roles); undefined = no center MCP (scenarios use no MCP).
|
|
700
|
+
* Carries both the mounted list and the full declared-name set — see {@link CenterMcp}. */
|
|
701
|
+
mcpServers?: CenterMcp;
|
|
691
702
|
/** DESIGN-269 §2.1 —— A2A peers from the config center (`/effective.a2a`), resolved at startup
|
|
692
703
|
* (header-NAME refs → values) and filtered per scenario in `resolveSpec` (`a2aForScenario`). Same
|
|
693
704
|
* posture as {@link mcpServers}: not env-derived (no A2A_* env leg — a peer is a structured record,
|
|
@@ -1539,9 +1550,17 @@ export interface ServiceConfigFlat {
|
|
|
1539
1550
|
/** Task Trace API (S1): persist the agent's reasoning/thinking into the durable run event log (for the CC-style
|
|
1540
1551
|
* trace UI). Default ON; `TRACE_THINKING=false` disables capture for sensitive deployments. Redacted on persist. */
|
|
1541
1552
|
traceThinking: boolean;
|
|
1542
|
-
/** [ref](core 5.30.0 readFace 部署座席):READ
|
|
1543
|
-
*
|
|
1553
|
+
/** [ref](core 5.30.0 readFace 部署座席):READ 容纳面。显式坏词启动期响亮拒;只影响读面,写面永不
|
|
1554
|
+
* 受它管(core 契约)。
|
|
1555
|
+
* 🔴 **S-167(7.65.0)起「缺席」按 posture 分两形**(clay 裁定 2):单用户 turnkey(`REQUIRE_PRINCIPAL`
|
|
1556
|
+
* 未设)⇒ 铸 `open`(工作区外读默认放行);多租户 ⇒ 仍**不铸**(core 引擎默认 roots,server 不写死
|
|
1557
|
+
* 上游默认)。谁定的这一档由 {@link ServiceConfigFlat.readFaceSource} 答 —— 值与来源**同一处写**。 */
|
|
1544
1558
|
readFace?: "open" | "roots";
|
|
1559
|
+
/** S-167:上面那一档**是谁定的**(闭集四词,见 `read-face-posture.ts`)。不变量:
|
|
1560
|
+
* `readFace === undefined` ⟺ `readFaceSource === "engine-default"`。
|
|
1561
|
+
* 🔴 承重面**不是**装饰:`config-center/read-face.ts` 的「env 恒赢」逐键判据读的就是它(修前读的是
|
|
1562
|
+
* 「键在不在场」这个代理,posture 会铸键 ⇒ 代理当场失真,center 的 face 会被从没设过 env 的机器挡掉)。 */
|
|
1563
|
+
readFaceSource: ReadFaceSource;
|
|
1545
1564
|
/** [ref]:部署级 READ deny 追加表(additive-only,core 内建表永不可缩)。env=JSON 数组
|
|
1546
1565
|
* [{pattern, caseSensitive?}],boot 时用 core `compileReadDeny` 预验——坏形拒启。缺席=不铸。 */
|
|
1547
1566
|
readDenyPatterns?: {
|
|
@@ -1674,7 +1693,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
|
|
|
1674
1693
|
* config 这一层它只是一句声明。 */
|
|
1675
1694
|
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
|
|
1676
1695
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
1677
|
-
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1696
|
+
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1678
1697
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
|
1679
1698
|
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
1680
1699
|
/** 组:observability(可观测)。 */
|
package/dist/config.js
CHANGED
|
@@ -12,6 +12,7 @@ import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
|
12
12
|
import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
13
13
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
14
14
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
15
|
+
import { readFaceForPosture } from "./read-face-posture.js";
|
|
15
16
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
16
17
|
export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
17
18
|
function csv(name) {
|
|
@@ -1458,7 +1459,7 @@ function parseAuthDomain(ctx) {
|
|
|
1458
1459
|
};
|
|
1459
1460
|
}
|
|
1460
1461
|
function parseOrchestrationDomain(ctx) {
|
|
1461
|
-
const { postureOn } = ctx;
|
|
1462
|
+
const { postureOn, singleUserTurnkey } = ctx;
|
|
1462
1463
|
const workflowSizeGuidelineRaw = process.env.WORKFLOW_SIZE_GUIDELINE;
|
|
1463
1464
|
if (workflowSizeGuidelineRaw !== undefined && !["small", "medium", "large", "unrestricted"].includes(workflowSizeGuidelineRaw)) {
|
|
1464
1465
|
throw new Error(`WORKFLOW_SIZE_GUIDELINE must be small|medium|large|unrestricted, got "${workflowSizeGuidelineRaw}"`);
|
|
@@ -1501,7 +1502,9 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1501
1502
|
}
|
|
1502
1503
|
}
|
|
1503
1504
|
return {
|
|
1504
|
-
...(process.env.READ_FACE !== undefined
|
|
1505
|
+
...(process.env.READ_FACE !== undefined
|
|
1506
|
+
? { readFace: enumEnv("READ_FACE", "roots", ["open", "roots"]), readFaceSource: "env" }
|
|
1507
|
+
: readFaceForPosture(singleUserTurnkey)),
|
|
1505
1508
|
...(process.env.READ_DENY_PATTERNS !== undefined ? { readDenyPatterns: parseReadDenyPatternsEnv(process.env.READ_DENY_PATTERNS) } : {}),
|
|
1506
1509
|
...(() => {
|
|
1507
1510
|
const tiers = process.env.READ_DENY_BUILTIN_TIERS !== undefined ? parseNameListEnv("READ_DENY_BUILTIN_TIERS", process.env.READ_DENY_BUILTIN_TIERS, { emptySentinel: "none" }) : undefined;
|
|
@@ -1905,7 +1908,7 @@ const ORCHESTRATION_GROUP_KEYS = [
|
|
|
1905
1908
|
"workflowSizeGuideline", "backgroundAgentRetentionMs", "backgroundAgentStaleRunningMs",
|
|
1906
1909
|
"backgroundAgentParkClaimStaleMs", "delegationEntryCaps", "rosterRetentionMs", "leaderRunStaleMs", "scratchpadSweepTtlMs", "sandboxPkgSource",
|
|
1907
1910
|
"sessionAutoTitle", "selectEnvironmentTool", "envFactsEnabled", "toolDeferLongtail", "lspEnabled", "lspHostEnabled",
|
|
1908
|
-
"imageBakes", "readFace", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude",
|
|
1911
|
+
"imageBakes", "readFace", "readFaceSource", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude",
|
|
1909
1912
|
];
|
|
1910
1913
|
const LIMITS_HTTP_GROUP_KEYS = [
|
|
1911
1914
|
"port", "attachmentOrphanGraceMs", "workspaceFileMaxBytes", "attachmentMaxBytes", "attachmentMimeAllowlist",
|
|
@@ -1973,7 +1976,7 @@ export function loadConfig() {
|
|
|
1973
1976
|
const approval = parseApprovalDomain({ postureOn });
|
|
1974
1977
|
const memory = parseMemoryDomain({ requirePrincipal });
|
|
1975
1978
|
const auth = parseAuthDomain({ requirePrincipal });
|
|
1976
|
-
const orchestration = parseOrchestrationDomain({ postureOn });
|
|
1979
|
+
const orchestration = parseOrchestrationDomain({ postureOn, singleUserTurnkey });
|
|
1977
1980
|
const limitsHttp = parseLimitsHttpDomain();
|
|
1978
1981
|
const observability = parseObservabilityDomain();
|
|
1979
1982
|
const integrations = parseIntegrationsDomain();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { screenedGate } from "../../trace/project.js";
|
|
1
2
|
import { personDecision } from "../../host-decision.js";
|
|
3
|
+
import { sameExecutionRecord } from "@sema-agent/core";
|
|
2
4
|
import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
|
|
3
5
|
import { APPROVAL_GATE_KINDS } from "../../tool-approval.js";
|
|
4
6
|
import { isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
@@ -71,6 +73,21 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
71
73
|
if (row.payloadFp === null || row.payloadFp !== requestFp)
|
|
72
74
|
return undefined;
|
|
73
75
|
}
|
|
76
|
+
const executionOutcome = (() => {
|
|
77
|
+
const records = rows.map((r) => r.executionOutcome);
|
|
78
|
+
const first = records[0];
|
|
79
|
+
if (first === null || first === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
for (const r of records) {
|
|
82
|
+
if (r === null)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (!sameExecutionRecord(r, first)) {
|
|
85
|
+
warn("decide_replay_execution_outcome_divergent", { sessionId, rows: rows.length });
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return screenedGate(first);
|
|
90
|
+
})();
|
|
74
91
|
return {
|
|
75
92
|
status: 200,
|
|
76
93
|
body: {
|
|
@@ -78,6 +95,7 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
78
95
|
idempotent: true,
|
|
79
96
|
decision,
|
|
80
97
|
bindingEnforced: true,
|
|
98
|
+
...(executionOutcome !== undefined ? { executionOutcome } : {}),
|
|
81
99
|
},
|
|
82
100
|
};
|
|
83
101
|
}
|