@sema-agent/server 7.78.0 → 7.78.2
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/README.md +6 -0
- package/USAGE.md +4 -0
- package/dist/approval-card.d.ts +37 -4
- package/dist/approval-card.js +22 -2
- package/dist/boot/resolve-spec.js +6 -3
- package/dist/config-catalog.d.ts +18 -1
- package/dist/config-catalog.js +3 -3
- package/dist/governance-http-status.d.ts +27 -0
- package/dist/governance-http-status.js +11 -0
- package/dist/http/routes/approvals-assistant.d.ts +35 -0
- package/dist/http/routes/approvals-assistant.js +39 -1
- package/dist/http/routes/tasks.js +4 -9
- package/dist/http/server.d.ts +28 -4
- package/dist/http/server.js +33 -13
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/parked-decide.d.ts +7 -2
- package/dist/plugins/host-workspace-registry.d.ts +27 -0
- package/dist/plugins/host-workspace-registry.js +27 -0
- package/dist/plugins/one-time-migrations.d.ts +3 -0
- package/dist/plugins/one-time-migrations.js +18 -11
- package/dist/plugins/remote-env-host.d.ts +15 -0
- package/dist/plugins/remote-env-host.js +41 -1
- package/dist/request-key-closure.d.ts +30 -0
- package/dist/request-key-closure.js +56 -0
- package/dist/runs.d.ts +51 -0
- package/dist/runs.js +4 -11
- package/dist/runtime-governance.d.ts +18 -4
- package/dist/runtime-governance.js +2 -2
- package/dist/spec-fields.d.ts +4 -2
- package/dist/spec-fields.js +4 -5
- package/dist/task-a2a.js +8 -6
- package/dist/task-mcp.js +5 -3
- package/dist/task-settings.d.ts +49 -17
- package/dist/task-settings.js +37 -38
- package/dist/tool-approval.d.ts +35 -3
- package/dist/tool-approval.js +41 -19
- package/dist/tool-axis-words.d.ts +30 -0
- package/dist/tool-axis-words.js +9 -0
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/ledger-sink.js +12 -5
- package/dist/trace/model-segment.d.ts +32 -2
- package/dist/trace/model-segment.js +15 -5
- package/dist/trace/project.d.ts +37 -3
- package/dist/trace/project.js +14 -10
- package/package.json +3 -3
package/dist/task-a2a.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveComplianceDenies } from "@sema-agent/core";
|
|
2
2
|
import { isUsableWireName } from "./wire-name-bound.js";
|
|
3
3
|
import { HttpError } from "./security.js";
|
|
4
|
+
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
5
|
+
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
4
6
|
export const MAX_REQUEST_A2A_PEERS = 32;
|
|
5
7
|
export function a2aInjectionHonored(config) {
|
|
6
8
|
if (config.lockedConfigKeys?.includes("a2a") === true)
|
|
@@ -49,16 +51,16 @@ function normalizeA2aPeer(e) {
|
|
|
49
51
|
if (!ax || typeof ax !== "object" || Array.isArray(ax))
|
|
50
52
|
return null;
|
|
51
53
|
const a = ax;
|
|
52
|
-
if (a.effect !== undefined && a.effect
|
|
54
|
+
if (a.effect !== undefined && !isToolEffect(a.effect))
|
|
53
55
|
return null;
|
|
54
56
|
if (a.egress !== undefined && typeof a.egress !== "boolean")
|
|
55
57
|
return null;
|
|
56
|
-
if (a.irreversibility !== undefined && a.irreversibility
|
|
58
|
+
if (a.irreversibility !== undefined && !isToolAxisIrreversibility(a.irreversibility))
|
|
57
59
|
return null;
|
|
58
60
|
const entry = {
|
|
59
|
-
...(a.effect
|
|
60
|
-
...(a.egress
|
|
61
|
-
...(a.irreversibility
|
|
61
|
+
...(isToolEffect(a.effect) ? { effect: a.effect } : {}),
|
|
62
|
+
...(typeof a.egress === "boolean" ? { egress: a.egress } : {}),
|
|
63
|
+
...(isToolAxisIrreversibility(a.irreversibility) ? { irreversibility: a.irreversibility } : {}),
|
|
62
64
|
};
|
|
63
65
|
if (Object.keys(entry).length > 0)
|
|
64
66
|
axes[skill] = entry;
|
|
@@ -91,7 +93,7 @@ export function assertRequestA2aUnlocked(bodyA2a, lockedKeys) {
|
|
|
91
93
|
return;
|
|
92
94
|
if (bodyA2a === undefined || bodyA2a === null)
|
|
93
95
|
return;
|
|
94
|
-
throw new HttpError(
|
|
96
|
+
throw new HttpError(GOVERNANCE_HTTP_STATUS["config.locked_key"], "`a2aPeers` is administratively locked by this deployment (locked key \"a2a\") — task-supplied A2A peers are refused; locks are config-time and there is no request-time unlock", {
|
|
95
97
|
code: "config.locked_key",
|
|
96
98
|
});
|
|
97
99
|
}
|
package/dist/task-mcp.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { mcpInjectionDroppedNotice, resolveComplianceDenies } from "@sema-agent/core";
|
|
2
2
|
import { isUsableWireName } from "./wire-name-bound.js";
|
|
3
3
|
import { HttpError } from "./security.js";
|
|
4
|
+
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
4
5
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "./mcp-content-origin.js";
|
|
5
6
|
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
6
7
|
import { readMcpToolFaces } from "./mcp-tool-faces.js";
|
|
8
|
+
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
7
9
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
8
10
|
export const MAX_REQUEST_MCP_DROP_NAMES = MAX_REQUEST_MCP_SERVERS;
|
|
9
11
|
export function mcpInjectionHonored(config) {
|
|
@@ -72,9 +74,9 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
72
74
|
continue;
|
|
73
75
|
const a = ax;
|
|
74
76
|
const entry = {
|
|
75
|
-
...(a.effect
|
|
77
|
+
...(isToolEffect(a.effect) ? { effect: a.effect } : {}),
|
|
76
78
|
...(typeof a.egress === "boolean" ? { egress: a.egress } : {}),
|
|
77
|
-
...(a.irreversibility
|
|
79
|
+
...(isToolAxisIrreversibility(a.irreversibility) ? { irreversibility: a.irreversibility } : {}),
|
|
78
80
|
};
|
|
79
81
|
if (Object.keys(entry).length > 0)
|
|
80
82
|
axes[tool] = entry;
|
|
@@ -123,7 +125,7 @@ export function assertRequestMcpUnlocked(bodyMcp, lockedKeys) {
|
|
|
123
125
|
return;
|
|
124
126
|
if (bodyMcp === undefined || bodyMcp === null)
|
|
125
127
|
return;
|
|
126
|
-
throw new HttpError(
|
|
128
|
+
throw new HttpError(GOVERNANCE_HTTP_STATUS["config.locked_key"], "`mcpServers` is administratively locked by this deployment (locked key \"mcp\") — task-supplied MCP servers are refused; locks are config-time and there is no request-time unlock", {
|
|
127
129
|
code: "config.locked_key",
|
|
128
130
|
});
|
|
129
131
|
}
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy, type PromptProvider } from "@sema-agent/core";
|
|
39
39
|
import type { HooksConfig } from "@sema-agent/settings-schema/hooks";
|
|
40
|
+
import { type KeyClosureIssue } from "./request-key-closure.js";
|
|
40
41
|
/** The permission MODE a client may request (SDK `SettingsPermissions.defaultMode`), the [ref]/[ref] five-word
|
|
41
42
|
* table. ⚠️ This is a DELIBERATE deployment set, NOT a copy of any CC set ([ref] P2-1 corpus check): CC's own
|
|
42
43
|
* full enum is SIX words (= ours + `dontAsk`), CC's managed/remote form accepts FOUR (= ours − `bypassPermissions`,
|
|
@@ -118,10 +119,40 @@ export declare function effectivePermissionMode(body: {
|
|
|
118
119
|
* 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
|
|
119
120
|
* 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
|
|
120
121
|
*
|
|
121
|
-
* 🔴
|
|
122
|
+
* 🔴 **闭集穷举表,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
|
|
122
123
|
* 是**编译错误**([ref] 的安全轴纪律:词表的未知项不许有静默兜底臂)。
|
|
123
124
|
*/
|
|
124
125
|
export declare function shellGateForMode(effMode: SettingsPermissionMode): "off" | "classify";
|
|
126
|
+
/**
|
|
127
|
+
* [ref] —— 生效模式 → `TaskSpec.writeFace`(**写**面的 roots 容纳判断)。`"open"` = 工作区根之外也能写;
|
|
128
|
+
* `undefined` = **不写这个键**(core 缺省 `"roots"`,与 7.77.0 及以前逐字节相同)。表逐字在
|
|
129
|
+
* {@link MODE_ENGINE_FACES},裁定与硬边界写在 `resolve-spec.ts` 的写点上。
|
|
130
|
+
*
|
|
131
|
+
* 🔴 与 {@link shellGateForMode} 同律:入参非可选 —— 「表态缺席」不是这张表的一行(那是调用点判的
|
|
132
|
+
* 「整个键都不写」),把它折进来就会逼出第二个 `undefined` 语义。
|
|
133
|
+
*/
|
|
134
|
+
export declare function writeFaceForMode(effMode: SettingsPermissionMode): "open" | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* [ref] ① —— 生效模式 → `TaskSpec.autoModeRequested`(auto **意图**座,CC 250「用户开」半场)。
|
|
137
|
+
* `true` 只有 auto 一行;`undefined` = 不写键。表逐字在 {@link MODE_ENGINE_FACES}。
|
|
138
|
+
*/
|
|
139
|
+
export declare function autoModeRequestedForMode(effMode: SettingsPermissionMode): true | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* S-346 —— [ref] 技能清单预算两旋钮的**值域谓词**(一对具名单铸点)。
|
|
142
|
+
*
|
|
143
|
+
* 两层同问这一只:FRESH 提交面的 400 门(`http/server.ts`)问「合不合法」,RESUME 宽容层
|
|
144
|
+
* ({@link parseTaskSettings})问「收不收下」—— 这两句话在这两个键上是**同一个问题的正反面**
|
|
145
|
+
* (400 的条件恰是受理条件的否定)。修前它们各写一遍字面比较,而承重的两件事
|
|
146
|
+
* (`Number.isFinite` 挡 `NaN`/`Infinity`、`Number.isSafeInteger` 与引擎受理域**同字**)
|
|
147
|
+
* 靠两份注释各自记着 —— 任一处漏一个判据,同一个坏值就会在一条腿上 400、在另一条腿上被收下。
|
|
148
|
+
*
|
|
149
|
+
* 值域**抄 CC settings schema**,不自创;server 侧零加工(不夹紧、不换算、不复刻 core 的默认值),
|
|
150
|
+
* 判据全文见两个调用点的顶注。
|
|
151
|
+
*/
|
|
152
|
+
export declare function skillListingBudgetFractionOk(v: unknown): v is number;
|
|
153
|
+
/** {@link skillListingBudgetFractionOk} 的孪生(`int().positive()`;`isSafeInteger` 而不是 `isInteger` 的
|
|
154
|
+
* 理由见那里)。 */
|
|
155
|
+
export declare function skillListingMaxDescOk(v: unknown): v is number;
|
|
125
156
|
/** The validated, service-trusted subset of `SemaSettings` we project onto the spec. `env` is parsed only to REPORT
|
|
126
157
|
* it as received-but-deferred when off the host lane (never silently dropped); a MALFORMED `hooks` likewise reports
|
|
127
158
|
* deferred (submit 路径另有 400 fail-loud),valid `hooks` 进 applied shape(hook-runner 阶段一)。 */
|
|
@@ -160,6 +191,13 @@ export interface ParsedTaskSettings {
|
|
|
160
191
|
* 这里只 PARSE;应用点(main.ts resolveSpec)按单用户闸(`requirePrincipal !== true`)决定翻不翻成
|
|
161
192
|
* core `TaskSpec.hooks` 回调——多租户 lane 收到只警告忽略(hook 命令跑在 worker host = class ② 能力授予)。 */
|
|
162
193
|
hooks?: HooksConfig;
|
|
194
|
+
/** [ref](7.77.0)—— CC settings 同名键:上下文窗口里**留给技能清单**的比例((0,1];CC 默认 0.01 = 1%)。
|
|
195
|
+
* 超预算时引擎把描述裁短、再不够退成 name-only(`cli259.js` 的清单渲染算法),**从不**拒掉一条技能 ——
|
|
196
|
+
* 这正是本版把技能条数/描述两条帽整条退役后的补偿面:per-turn 成本的旋钮在**渲染侧**,不在受理侧。
|
|
197
|
+
* server 零加工透传到 `TaskSpec`,默认值属主是引擎(缺席 ⇒ 整键不写)。 */
|
|
198
|
+
skillListingBudgetFraction?: number;
|
|
199
|
+
/** 同族第二键:每条技能描述在清单里的字符帽(正整数;CC 默认 1536)。超出由引擎**截断**,不是拒。 */
|
|
200
|
+
skillListingMaxDescChars?: number;
|
|
163
201
|
}
|
|
164
202
|
/** The result of parsing an untrusted `body.settings`: the applied subset + the names of present-but-deferred
|
|
165
203
|
* fields (so the caller can log/advertise them — no silent drop). `null` settings ⇒ `{ settings: undefined }`. */
|
|
@@ -180,10 +218,15 @@ export declare const MAX_SETTINGS_ENV_VARS = 256;
|
|
|
180
218
|
export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
|
|
181
219
|
export declare const MAX_SETTINGS_ENV_KEY_CHARS = 256;
|
|
182
220
|
/** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
|
|
183
|
-
* `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode`
|
|
184
|
-
*
|
|
185
|
-
* 加键必须同时加消费点,否则就是往闭集里塞一个新的静默丢。
|
|
186
|
-
|
|
221
|
+
* `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode` 与两个 `skillListing*` 在
|
|
222
|
+
* {@link parseTaskSettings};`webSearch` 在 `capabilities/scenarios.ts`(`webSearchConfigFromSettings`,单用户闸)。
|
|
223
|
+
* 加键必须同时加消费点,否则就是往闭集里塞一个新的静默丢。
|
|
224
|
+
*
|
|
225
|
+
* 🔴 两个 `skillListing*`([ref],7.77.0)= **CC settings 同名键**,值域抄 CC 的 schema 声明
|
|
226
|
+
* (`skillListingBudgetFraction` 是 `gt(0).lte(1)`、`skillListingMaxDescChars` 是 `int().positive()`);
|
|
227
|
+
* 消费点 = 本文件的 parse + `boot/resolve-spec.ts` 的 `TaskSpec` 透传。语义(预算怎么花、描述怎么裁、
|
|
228
|
+
* 溢出怎么退成 name-only)整个归引擎,本仓一个字节都不渲染,也**不复刻默认值**——缺席即整键不写。 */
|
|
229
|
+
export declare const TASK_SETTINGS_KEYS: readonly ["env", "hooks", "model", "outputStyle", "permissions", "skillListingBudgetFraction", "skillListingMaxDescChars", "ultracode", "webSearch"];
|
|
187
230
|
/** 本 service 消费的 `settings.permissions` 子键 —— 闭集(值形校验另有既有门,本门只判键名)。 */
|
|
188
231
|
export declare const TASK_SETTINGS_PERMISSION_KEYS: readonly ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
|
|
189
232
|
/** CC/SDK 合约里合法、本 service **不接线**的顶层键。词表外的 CC 键落 `unknown` —— 两列都是 400,标签只影响
|
|
@@ -202,17 +245,6 @@ export declare const TASK_SETTINGS_UNSUPPORTED_KEYS: readonly ["apiKeyHelper", "
|
|
|
202
245
|
/** 同上,`settings.permissions` 子层(出处 = SDK `SettingsPermissions` 的三个声明键)。
|
|
203
246
|
* `additionalDirectories` 有**同请求替代面**:顶层 `body.additionalDirectories`([ref],resolve-spec 真消费)。 */
|
|
204
247
|
export declare const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS: readonly ["additionalDirectories", "disableBypassPermissionsMode"];
|
|
205
|
-
/** 拒体里逐字回显的键名条数上限。键名原样回显(不猜语义),但**条数有界** —— 一个塞满键的体不许把 400 响应
|
|
206
|
-
* 放大成流量面。超出部分只报数。 */
|
|
207
|
-
export declare const MAX_SETTINGS_KEY_ECHO = 20;
|
|
208
|
-
/** {@link taskSettingsKeyIssue} 的拒体:一句 wire 文案 + 两张分类键名表(消费端可机读,不必解析文案)。 */
|
|
209
|
-
export interface TaskSettingsKeyIssue {
|
|
210
|
-
message: string;
|
|
211
|
-
/** 词表外的键(拼错/未来键),`settings.x` / `settings.permissions.x` 全路径形,已清洗+截断+排序+封顶。 */
|
|
212
|
-
unknownKeys: string[];
|
|
213
|
-
/** CC/SDK 合约合法但本 service 不接线的键,同形。 */
|
|
214
|
-
unsupportedKeys: string[];
|
|
215
|
-
}
|
|
216
248
|
/**
|
|
217
249
|
* FRESH 提交面的 `settings` 键闭集裁决:顶层或 `permissions` 子层出现受理集之外的键 ⇒ 返回拒体(调用方 400
|
|
218
250
|
* `request.body_shape`);全部受理 ⇒ `null`。
|
|
@@ -224,7 +256,7 @@ export interface TaskSettingsKeyIssue {
|
|
|
224
256
|
* 「settings must be an object」/「settings.permissions must be an object」两道门,免得同一个体被两句话抢答。
|
|
225
257
|
* · `permissions: null` 是既有钉住的「容器缺席」形,照旧不咬。
|
|
226
258
|
*/
|
|
227
|
-
export declare function taskSettingsKeyIssue(raw: unknown):
|
|
259
|
+
export declare function taskSettingsKeyIssue(raw: unknown): KeyClosureIssue | null;
|
|
228
260
|
/**
|
|
229
261
|
* Validate an untrusted `body.settings` (raw `unknown` off the wire) into the service-trusted {@link
|
|
230
262
|
* ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked. `defaultMode` accepts the FIVE
|
package/dist/task-settings.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { combinePolicies, createAllowDenyPolicy, createFsWriteGatePolicy, createPermissionRulePolicy, parseRuleText, tightenTaskSpec, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, ruleToolGrammarOf, } from "@sema-agent/core";
|
|
2
2
|
import { findUnmatchableToolNames, formatUnmatchableToolNames } from "./config.js";
|
|
3
3
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
4
|
+
import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "./request-key-closure.js";
|
|
4
5
|
import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
5
6
|
export function coercePermissionMode(raw) {
|
|
6
7
|
const strict = coercePermissionModeStrict(raw);
|
|
@@ -34,15 +35,26 @@ export function effectivePermissionMode(body, settings) {
|
|
|
34
35
|
return requested;
|
|
35
36
|
}
|
|
36
37
|
export function shellGateForMode(effMode) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
return MODE_ENGINE_FACES[effMode].shellGate;
|
|
39
|
+
}
|
|
40
|
+
const MODE_ENGINE_FACES = {
|
|
41
|
+
bypassPermissions: { shellGate: "off", writeFace: "open", autoModeRequested: undefined },
|
|
42
|
+
auto: { shellGate: "classify", writeFace: undefined, autoModeRequested: true },
|
|
43
|
+
default: { shellGate: "classify", writeFace: undefined, autoModeRequested: undefined },
|
|
44
|
+
acceptEdits: { shellGate: "classify", writeFace: undefined, autoModeRequested: undefined },
|
|
45
|
+
plan: { shellGate: "classify", writeFace: undefined, autoModeRequested: undefined },
|
|
46
|
+
};
|
|
47
|
+
export function writeFaceForMode(effMode) {
|
|
48
|
+
return MODE_ENGINE_FACES[effMode].writeFace;
|
|
49
|
+
}
|
|
50
|
+
export function autoModeRequestedForMode(effMode) {
|
|
51
|
+
return MODE_ENGINE_FACES[effMode].autoModeRequested;
|
|
52
|
+
}
|
|
53
|
+
export function skillListingBudgetFractionOk(v) {
|
|
54
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 && v <= 1;
|
|
55
|
+
}
|
|
56
|
+
export function skillListingMaxDescOk(v) {
|
|
57
|
+
return typeof v === "number" && Number.isSafeInteger(v) && v > 0;
|
|
46
58
|
}
|
|
47
59
|
export const MAX_SETTINGS_OUTPUT_STYLE_CHARS = 16_384;
|
|
48
60
|
export const MAX_SETTINGS_PERMISSION_RULES = 1_000;
|
|
@@ -72,49 +84,31 @@ function cleanStringArray(v) {
|
|
|
72
84
|
const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
|
|
73
85
|
return out.length > 0 ? out : undefined;
|
|
74
86
|
}
|
|
75
|
-
export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "ultracode", "webSearch"];
|
|
87
|
+
export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "skillListingBudgetFraction", "skillListingMaxDescChars", "ultracode", "webSearch"];
|
|
76
88
|
export const TASK_SETTINGS_PERMISSION_KEYS = ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
|
|
77
89
|
export const TASK_SETTINGS_UNSUPPORTED_KEYS = ["apiKeyHelper", "crossSessionInbound", "dialogExpiry", "sandbox", "statusLine"];
|
|
78
90
|
export const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS = ["additionalDirectories", "disableBypassPermissionsMode"];
|
|
79
|
-
export const MAX_SETTINGS_KEY_ECHO = 20;
|
|
80
|
-
const MAX_SETTINGS_KEY_ECHO_CHARS = 64;
|
|
81
91
|
const ACCEPTED_TOP = new Set(TASK_SETTINGS_KEYS);
|
|
82
92
|
const ACCEPTED_PERM = new Set(TASK_SETTINGS_PERMISSION_KEYS);
|
|
83
93
|
const UNSUPPORTED_TOP = new Set(TASK_SETTINGS_UNSUPPORTED_KEYS);
|
|
84
94
|
const UNSUPPORTED_PERM = new Set(TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS);
|
|
85
|
-
function echoSettingsKey(prefix, key) {
|
|
86
|
-
const clean = key.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
87
|
-
return `${prefix}${clean.length > MAX_SETTINGS_KEY_ECHO_CHARS ? `${clean.slice(0, MAX_SETTINGS_KEY_ECHO_CHARS)}…` : clean}`;
|
|
88
|
-
}
|
|
89
95
|
export function taskSettingsKeyIssue(raw) {
|
|
90
96
|
if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
|
|
91
97
|
return null;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const scan = (bag, prefix, accepted, tolerated) => {
|
|
95
|
-
for (const k of Object.keys(bag)) {
|
|
96
|
-
if (accepted.has(k))
|
|
97
|
-
continue;
|
|
98
|
-
(tolerated.has(k) ? unsupported : unknown).push(echoSettingsKey(prefix, k));
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
scan(raw, "settings.", ACCEPTED_TOP, UNSUPPORTED_TOP);
|
|
98
|
+
const buckets = { unknown: [], unsupported: [] };
|
|
99
|
+
scanKeyClosure(raw, "settings.", ACCEPTED_TOP, UNSUPPORTED_TOP, buckets);
|
|
102
100
|
const perms = raw.permissions;
|
|
103
101
|
if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
|
|
104
|
-
|
|
102
|
+
scanKeyClosure(perms, "settings.permissions.", ACCEPTED_PERM, UNSUPPORTED_PERM, buckets);
|
|
105
103
|
}
|
|
104
|
+
const { unknown, unsupported } = buckets;
|
|
106
105
|
if (unknown.length === 0 && unsupported.length === 0)
|
|
107
106
|
return null;
|
|
108
107
|
unknown.sort();
|
|
109
108
|
unsupported.sort();
|
|
110
|
-
const clause = (names, label) => {
|
|
111
|
-
const shown = names.slice(0, MAX_SETTINGS_KEY_ECHO);
|
|
112
|
-
const more = names.length - shown.length;
|
|
113
|
-
return `${label}: ${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
114
|
-
};
|
|
115
109
|
const parts = [
|
|
116
|
-
...(unknown.length > 0 ? [
|
|
117
|
-
...(unsupported.length > 0 ? [
|
|
110
|
+
...(unknown.length > 0 ? [keyClosureClause(unknown, "unknown (not a settings key here — check the spelling)")] : []),
|
|
111
|
+
...(unsupported.length > 0 ? [keyClosureClause(unsupported, "unsupported (a CC settings key this service does not wire)")] : []),
|
|
118
112
|
];
|
|
119
113
|
const dirHint = unsupported.some((n) => n.endsWith(".additionalDirectories"))
|
|
120
114
|
? " Extra directories ride the top-level body.additionalDirectories instead."
|
|
@@ -123,8 +117,8 @@ export function taskSettingsKeyIssue(raw) {
|
|
|
123
117
|
message: `settings: unrecognized key(s) — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
|
|
124
118
|
`${parts.join("; ")}.${dirHint} ` +
|
|
125
119
|
`Accepted: settings.{${[...TASK_SETTINGS_KEYS].join(",")}} and settings.permissions.{${[...TASK_SETTINGS_PERMISSION_KEYS].join(",")}}`,
|
|
126
|
-
unknownKeys: unknown.slice(0,
|
|
127
|
-
unsupportedKeys: unsupported.slice(0,
|
|
120
|
+
unknownKeys: unknown.slice(0, MAX_KEY_ECHO),
|
|
121
|
+
unsupportedKeys: unsupported.slice(0, MAX_KEY_ECHO),
|
|
128
122
|
};
|
|
129
123
|
}
|
|
130
124
|
export function parseTaskSettings(raw) {
|
|
@@ -178,8 +172,13 @@ export function parseTaskSettings(raw) {
|
|
|
178
172
|
out.ultracode = true;
|
|
179
173
|
if (hooksConfig)
|
|
180
174
|
out.hooks = hooksConfig;
|
|
181
|
-
const
|
|
182
|
-
|
|
175
|
+
const fraction = s.skillListingBudgetFraction;
|
|
176
|
+
if (skillListingBudgetFractionOk(fraction))
|
|
177
|
+
out.skillListingBudgetFraction = fraction;
|
|
178
|
+
const maxDesc = s.skillListingMaxDescChars;
|
|
179
|
+
if (skillListingMaxDescOk(maxDesc))
|
|
180
|
+
out.skillListingMaxDescChars = maxDesc;
|
|
181
|
+
return { settings: Object.keys(out).length > 0 ? out : undefined, deferred };
|
|
183
182
|
}
|
|
184
183
|
export function hasConstitutionAnchors(text) {
|
|
185
184
|
return typeof text === "string" && text.includes(CYBER_RISK) && text.includes(URL_SAFETY) && text.includes(HARNESS_SECTION_ANCHOR);
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
|
-
import { type AskRequest, type AskOutcome, type RuleOffer } from "@sema-agent/core";
|
|
2
|
+
import { type AskRequest, type AskOutcome, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
|
|
3
3
|
import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
|
|
4
4
|
import { type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
|
|
5
5
|
import { type ApprovalAskStore } from "./plugins/approval-ask-store-sql.js";
|
|
@@ -169,6 +169,27 @@ export interface ToolApprovalFrame {
|
|
|
169
169
|
* `GateDisposition.denied.cause`(`tool_end.gate` / `permissionDenied.gate`),ask 面自此没有这一位。
|
|
170
170
|
*/
|
|
171
171
|
ruleStoreUnreadable?: string;
|
|
172
|
+
/**
|
|
173
|
+
* **[ref]**(core 7.19.0;**ADDITIVE**,`"tool_approval"` only)—— 这只**根外读** ask 的**清障目录**:
|
|
174
|
+
* 把 `dir` 加进本会话的读目录(壳侧既有的 `/add-dir` 口),同一条调用就不会再问。
|
|
175
|
+
*
|
|
176
|
+
* 🔴 **在场才读,永远不读缺席**。缺席不是「无计可施」:它同时覆盖敏感路径 deny 行(按模式判,加任何
|
|
177
|
+
* 根都不动它)、读边界压根读不懂的命令(heredoc / 换行 / 命令替换 / 参数当程序)、没展开的 glob、
|
|
178
|
+
* 递归遍历、以及每一只**不是**读边界提的 ask —— 7.18.0 及以前的**每**一只 ask 都是这一形。
|
|
179
|
+
*
|
|
180
|
+
* 🔴 **原样回传**(core 契约 `shell.read_boundary.grant_candidate_is_the_grant`):`dir` 是绝对、已做词法
|
|
181
|
+
* 折叠、用读边界自己的拼法写的串;**显示的串就是要加的串**。壳可以为了好看渲一个缩短形,回传必须是
|
|
182
|
+
* 本键里的原串。⇒ 本仓的姿势是「整只逐字带,或整只丢」,**绝不截**(见 `MAX_READ_ROOT_CANDIDATE_DIR`)。
|
|
183
|
+
*
|
|
184
|
+
* 🔴 **echo-only,零判读**:server 不从命令文本重推目录,也不复核它真不真能清掉这只 ask —— core 已经
|
|
185
|
+
* 把提议的根放回读边界又走了一遍才递出来,下游再判一遍就是同一事实的第二个判官(与 `origin` /
|
|
186
|
+
* `ruleStoreUnreadable` / `denialLimitFallback` 三条顶注的同一条禁令)。
|
|
187
|
+
*
|
|
188
|
+
* 🔴 **只此一面**:core 的负控逐字写着「the durable park row has no twin of this seat in this version」——
|
|
189
|
+
* 一只 PARKED 的根外读 ask 渲的仍是单独那张确认卡。所以 `card_json` / 耐久面**没有**这一座,本仓也不
|
|
190
|
+
* 自铸一个(自铸 = 同一事实的第二个源,而且那个源会在 park 兑现时说谎)。
|
|
191
|
+
*/
|
|
192
|
+
readRootCandidate?: ReadRootGrantCandidate;
|
|
172
193
|
/**
|
|
173
194
|
* **E-14**([ref]② Trojan Source 族;**ADDITIVE**,`"tool_approval"` only)—— 这只 ask 的**工具输入**
|
|
174
195
|
* (`AskRequest.args`)里含 bidi 控制符(LRM/RLM、嵌入/覆写、隔离符;字符类属主 `text-bidi.ts`)。
|
|
@@ -858,9 +879,20 @@ export declare class ToolApprovalCoordinator {
|
|
|
858
879
|
* 残余 = 那类行的归因会记成 `routing_failure`(其实是政策 deny),归因精化登记为候件。
|
|
859
880
|
*/
|
|
860
881
|
private unattendedOutcome;
|
|
882
|
+
/** 政策**读**口(不是终局铸口)—— 回答「这台部署把无人可答翻成 park 吗」。
|
|
883
|
+
*
|
|
884
|
+
* 🔴 与 {@link unattendedOutcome} 分家是 S-187 的一半:此前两件事共用一只方法,于是墓碑腿那次
|
|
885
|
+
* 纯粹的**政策查询**(`unattendedOutcome() !== false`)会和同一只 ask 的真终局各记一条遥测 ——
|
|
886
|
+
* 「一只 ask 一条线」当场不成立。读口不落日志,铸口落;谁也不兼职。 */
|
|
887
|
+
private get unattendedRoutesToPark();
|
|
861
888
|
/** {@link unattendedOutcome} 的**公开**口:`windowZero` 的 immediate-unavailable 闭包长在装配层
|
|
862
|
-
* (`http/routes/tasks.ts` 的 sync 腿),它必须与协调器内五臂读**同一个**旋钮值,而不是各算一遍。
|
|
863
|
-
|
|
889
|
+
* (`http/routes/tasks.ts` 的 sync 腿),它必须与协调器内五臂读**同一个**旋钮值,而不是各算一遍。
|
|
890
|
+
* 出处随调用方给(那条腿知道自己是哪一只 ask)。 */
|
|
891
|
+
unattendedAskOutcome(site: {
|
|
892
|
+
taskId?: string;
|
|
893
|
+
owner?: string | null;
|
|
894
|
+
toolName?: string;
|
|
895
|
+
}): "unavailable" | false;
|
|
864
896
|
/**
|
|
865
897
|
* [ref] 车3 刀 3b —— [ref] §3.3 / 设计稿 §0 X-2 的**写侧准入门**。
|
|
866
898
|
*
|
package/dist/tool-approval.js
CHANGED
|
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
|
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
9
9
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
10
10
|
import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
11
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
11
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
12
12
|
import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
13
13
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
14
14
|
const defaultLogger = createLogger();
|
|
@@ -300,11 +300,29 @@ export class ToolApprovalCoordinator {
|
|
|
300
300
|
this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
|
|
301
301
|
this.askAudit = opts?.askAudit;
|
|
302
302
|
}
|
|
303
|
-
unattendedOutcome() {
|
|
304
|
-
|
|
303
|
+
unattendedOutcome(site) {
|
|
304
|
+
const outcome = this.unattendedPolicy === "deny" ? false : "unavailable";
|
|
305
|
+
defaultLogger.warn("approver_unavailable", {
|
|
306
|
+
arm: site.arm,
|
|
307
|
+
policy: this.unattendedPolicy,
|
|
308
|
+
outcome: outcome === false ? "deny" : "unavailable",
|
|
309
|
+
owner: site.owner ?? null,
|
|
310
|
+
taskId: site.taskId ?? null,
|
|
311
|
+
toolName: site.toolName ?? null,
|
|
312
|
+
reachability: site.reachability ?? null,
|
|
313
|
+
});
|
|
314
|
+
return outcome;
|
|
315
|
+
}
|
|
316
|
+
get unattendedRoutesToPark() {
|
|
317
|
+
return this.unattendedPolicy !== "deny";
|
|
305
318
|
}
|
|
306
|
-
unattendedAskOutcome() {
|
|
307
|
-
return this.unattendedOutcome(
|
|
319
|
+
unattendedAskOutcome(site) {
|
|
320
|
+
return this.unattendedOutcome({
|
|
321
|
+
arm: "window_zero",
|
|
322
|
+
...(site.taskId !== undefined ? { taskId: site.taskId } : {}),
|
|
323
|
+
...(site.owner !== undefined && site.owner !== null ? { owner: site.owner } : {}),
|
|
324
|
+
...(site.toolName !== undefined ? { toolName: site.toolName } : {}),
|
|
325
|
+
});
|
|
308
326
|
}
|
|
309
327
|
admit(taskId, owner) {
|
|
310
328
|
if (!this.askStore)
|
|
@@ -758,7 +776,7 @@ export class ToolApprovalCoordinator {
|
|
|
758
776
|
ask = async (req, signal) => {
|
|
759
777
|
const ctx = this.als.getStore();
|
|
760
778
|
if (!ctx)
|
|
761
|
-
return this.unattendedOutcome();
|
|
779
|
+
return this.unattendedOutcome({ arm: "no_run_context", toolName: req.toolName });
|
|
762
780
|
return this.askBroadcast({
|
|
763
781
|
taskId: ctx.taskId,
|
|
764
782
|
owner: ctx.owner,
|
|
@@ -786,11 +804,9 @@ export class ToolApprovalCoordinator {
|
|
|
786
804
|
const aliveCtxs = ctxs.filter((c) => !c.abortSignal?.aborted);
|
|
787
805
|
const reachability = aliveCtxs.length === 0 ? "unreached" : "reachable";
|
|
788
806
|
if (reachability === "unreached") {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!this.askStore)
|
|
793
|
-
return unattended;
|
|
807
|
+
if (!this.unattendedRoutesToPark || !this.askStore) {
|
|
808
|
+
return this.unattendedOutcome({ arm: "no_live_target", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
809
|
+
}
|
|
794
810
|
}
|
|
795
811
|
const primary = aliveCtxs[0];
|
|
796
812
|
const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
|
|
@@ -809,7 +825,7 @@ export class ToolApprovalCoordinator {
|
|
|
809
825
|
admitMaxPerTask: this.admitMaxPerTask,
|
|
810
826
|
admitMaxPerOwner: this.admitMaxPerOwner,
|
|
811
827
|
});
|
|
812
|
-
return this.unattendedOutcome();
|
|
828
|
+
return this.unattendedOutcome({ arm: "admission_exhausted", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
813
829
|
}
|
|
814
830
|
const id = uuidv7();
|
|
815
831
|
const child = isChildAsk(req);
|
|
@@ -848,6 +864,7 @@ export class ToolApprovalCoordinator {
|
|
|
848
864
|
...((a) => (a !== undefined ? { ruleOffersAbsence: a } : {}))(readRuleOffersAbsence(req)),
|
|
849
865
|
...((f) => (f !== undefined ? { denialLimitFallback: f } : {}))(readDenialLimitFallback(req)),
|
|
850
866
|
...(typeof req.ruleStoreUnreadable === "string" && req.ruleStoreUnreadable.length > 0 ? { ruleStoreUnreadable: req.ruleStoreUnreadable } : {}),
|
|
867
|
+
...((g) => (g !== undefined ? { readRootCandidate: g } : {}))(readReadRootCandidate(req)),
|
|
851
868
|
...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
|
|
852
869
|
...((c) => (c !== undefined ? { ruleEvidence: c } : {}))(readRuleEvidence(req)),
|
|
853
870
|
...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
|
|
@@ -904,7 +921,9 @@ export class ToolApprovalCoordinator {
|
|
|
904
921
|
if (existingOrCreated.row.state !== "STREAM_PENDING") {
|
|
905
922
|
releaseAdmission();
|
|
906
923
|
const replayed = replayTerminalAskRow(existingOrCreated.row);
|
|
907
|
-
return replayed === "unavailable"
|
|
924
|
+
return replayed === "unavailable"
|
|
925
|
+
? this.unattendedOutcome({ arm: "replayed_parked", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability })
|
|
926
|
+
: replayed;
|
|
908
927
|
}
|
|
909
928
|
const persistedEnvelope = ApprovalCardEnvelopeSchema.safeParse(existingOrCreated.row.cardJson);
|
|
910
929
|
const maxMintableWindowMs = Math.max(effectiveWindowMs, this.unreachedTtlMs);
|
|
@@ -985,7 +1004,7 @@ export class ToolApprovalCoordinator {
|
|
|
985
1004
|
voidAbandonedRow(lateEnsuredAskId);
|
|
986
1005
|
if (reachability === "unreached") {
|
|
987
1006
|
releaseAdmission();
|
|
988
|
-
return this.unattendedOutcome();
|
|
1007
|
+
return this.unattendedOutcome({ arm: "unreached_no_row", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
989
1008
|
}
|
|
990
1009
|
}
|
|
991
1010
|
}
|
|
@@ -1014,8 +1033,11 @@ export class ToolApprovalCoordinator {
|
|
|
1014
1033
|
return false;
|
|
1015
1034
|
};
|
|
1016
1035
|
const shapeOutcome = (settled) => {
|
|
1017
|
-
if (windowRouteUnavailable)
|
|
1018
|
-
return this.unattendedOutcome(
|
|
1036
|
+
if (windowRouteUnavailable) {
|
|
1037
|
+
return this.unattendedOutcome({ arm: "window_expired", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability }) === false
|
|
1038
|
+
? shapeDeny()
|
|
1039
|
+
: "unavailable";
|
|
1040
|
+
}
|
|
1019
1041
|
if (settled.allowed) {
|
|
1020
1042
|
return settled.updatedInput !== undefined ? { allow: true, updatedInput: settled.updatedInput } : true;
|
|
1021
1043
|
}
|
|
@@ -1032,7 +1054,7 @@ export class ToolApprovalCoordinator {
|
|
|
1032
1054
|
for (const [sig, fn] of abortListeners)
|
|
1033
1055
|
sig.removeEventListener("abort", fn);
|
|
1034
1056
|
this.pending.delete(id);
|
|
1035
|
-
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.
|
|
1057
|
+
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedRoutesToPark) {
|
|
1036
1058
|
this.recordParkTombstone(wireApprovalId, { kind: "park", askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1037
1059
|
parkRouted = true;
|
|
1038
1060
|
}
|
|
@@ -1456,11 +1478,11 @@ export class ToolApprovalCoordinator {
|
|
|
1456
1478
|
.catch(() => undefined);
|
|
1457
1479
|
}
|
|
1458
1480
|
if (emitFailed)
|
|
1459
|
-
return this.unattendedOutcome();
|
|
1481
|
+
return this.unattendedOutcome({ arm: "emit_failed", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1460
1482
|
if (windowRouteUnavailable)
|
|
1461
1483
|
return shapeOutcome(settled);
|
|
1462
1484
|
if (!emitSettled && lastOutcome === "expired")
|
|
1463
|
-
return this.unattendedOutcome();
|
|
1485
|
+
return this.unattendedOutcome({ arm: "emit_unsettled", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1464
1486
|
return shapeOutcome(settled);
|
|
1465
1487
|
}
|
|
1466
1488
|
respond(id, principal, body, httpReq) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-249(DT-13)—— per-tool **风险轴覆写**的值域词表与谓词,**两面共用一份**。
|
|
3
|
+
*
|
|
4
|
+
* ## 病(修前)
|
|
5
|
+
* `task-mcp.ts` 与 `task-a2a.ts` 各自在请求腿上逐键校验同一个三成员结构
|
|
6
|
+
* (`effect` / `egress` / `irreversibility`),而两个**闭集词表**是**手抄的字面串**:四份字面量
|
|
7
|
+
* (两文件 × 两个词轴),没有一份钉在 core 的型上。后果不是「今天判错了」,而是**判据比声明窄**这件事
|
|
8
|
+
* 没有任何门看得见 —— core 哪天给 `ToolEffect` 加一个词(或把 `irreversibility` 的两词改成三词,它在
|
|
9
|
+
* `AskOrigin` 那一面**已经**是 `never|maybe|always` 三词了),两条请求腿会把那个**合法**的新词
|
|
10
|
+
* **静默剔除**(MCP 腿)或**整条 server 判假**(A2A 腿),而 tsc 一声不吭。
|
|
11
|
+
*
|
|
12
|
+
* ## 修法
|
|
13
|
+
* 词表留在本仓(运行期要它),但**两向钉死在 core 的型上**:core 加词 ⇒ 正向门红;本表留了一个 core
|
|
14
|
+
* 已经没有的词 ⇒ 反向门红。两个面共用**同一只**谓词,于是「什么算一个合法的轴值」在本仓只有一个答案。
|
|
15
|
+
*
|
|
16
|
+
* 🔴 **只管值域,不管处置**:两条腿对坏值的处置**刻意不同**(MCP = 丢这一键、留 server;A2A = 整条拒),
|
|
17
|
+
* 判据与理由各写在各自的调用点。合并处置会抹掉那条真实的分歧(MCP 的 `toolAxes` 有 server-hint 腿可折,
|
|
18
|
+
* A2A 收下的就是最终值),而分歧本身是裁定过的。
|
|
19
|
+
*/
|
|
20
|
+
import type { McpServerSpec, ToolEffect } from "@sema-agent/core";
|
|
21
|
+
/** 一条 per-tool 轴覆写的形。取 MCP 那一面作代表 —— 下面的同型钉证明两面逐字同声明。 */
|
|
22
|
+
export type ToolAxisEntry = NonNullable<McpServerSpec["toolAxes"]>[string];
|
|
23
|
+
/** 轴覆写上那个**两词**的不可逆档(⚠️ 与 `AskOrigin` 面的**三词** `never|maybe|always` 不是同一个词表:
|
|
24
|
+
* 覆写面只收两极,`maybe` 是引擎按工具自己的声明定的,调用方说不出口)。 */
|
|
25
|
+
export type ToolAxisIrreversibility = NonNullable<ToolAxisEntry["irreversibility"]>;
|
|
26
|
+
/** 值是不是一个合法的 `effect` 词(两条请求腿共用)。 */
|
|
27
|
+
export declare function isToolEffect(v: unknown): v is ToolEffect;
|
|
28
|
+
/** 值是不是一个合法的**覆写面** `irreversibility` 词(两条请求腿共用)。 */
|
|
29
|
+
export declare function isToolAxisIrreversibility(v: unknown): v is ToolAxisIrreversibility;
|
|
30
|
+
//# sourceMappingURL=tool-axis-words.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const TOOL_EFFECT_WORDS = ["read", "write", "idempotent"];
|
|
2
|
+
const TOOL_AXIS_IRREVERSIBILITY_WORDS = ["always", "never"];
|
|
3
|
+
export function isToolEffect(v) {
|
|
4
|
+
return typeof v === "string" && TOOL_EFFECT_WORDS.includes(v);
|
|
5
|
+
}
|
|
6
|
+
export function isToolAxisIrreversibility(v) {
|
|
7
|
+
return typeof v === "string" && TOOL_AXIS_IRREVERSIBILITY_WORDS.includes(v);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=tool-axis-words.js.map
|
|
@@ -43,7 +43,7 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
|
|
|
43
43
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
44
44
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
45
45
|
type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
|
|
46
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable";
|
|
46
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "readRootCandidate";
|
|
47
47
|
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
|
|
48
48
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
49
49
|
type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
|
|
@@ -121,8 +121,8 @@ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "seve
|
|
|
121
121
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
|
|
122
122
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
123
123
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
124
|
-
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";
|
|
125
|
-
type TaskSpecUnwired = "excludeAllTools" | "
|
|
124
|
+
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" | "writeFace" | "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" | "skillListingBudgetFraction" | "skillListingMaxDescChars";
|
|
125
|
+
type TaskSpecUnwired = "excludeAllTools" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
|
|
126
126
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
127
127
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
128
128
|
type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles";
|