@sema-agent/server 7.114.0 → 7.116.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +237 -2
- package/USAGE.md +39 -3
- package/deploy/sema-up/cluster-up.sh +13 -1
- package/deploy/sema-up/kube-up.sh +13 -1
- package/deploy/sema-up/preflight.sh +13 -1
- package/deploy/sema-up/sema-up.sh +13 -1
- package/deploy/sema-up/smoke.sh +13 -1
- package/dist/adoption/wire.d.ts +0 -3
- package/dist/approval-ask-audit-store.d.ts +25 -6
- package/dist/approval-ask-audit-store.js +6 -1
- package/dist/approval-card.d.ts +25 -24
- package/dist/approval-card.js +14 -0
- package/dist/approval-crash-convergence.d.ts +60 -0
- package/dist/approval-crash-convergence.js +75 -0
- package/dist/approval-deny-reasons.d.ts +13 -0
- package/dist/approval-deny-reasons.js +2 -0
- package/dist/approval.js +7 -3
- package/dist/boot/coordinators.js +0 -12
- package/dist/boot/execution-env.js +2 -1
- package/dist/boot/exit-boundary.d.ts +3 -2
- package/dist/boot/exit-boundary.js +3 -0
- package/dist/boot/resolve-spec.js +5 -1
- package/dist/boot/shutdown.d.ts +19 -0
- package/dist/boot/shutdown.js +12 -1
- package/dist/boot/stage-07-capability-layer.js +21 -15
- package/dist/boot/stores.js +4 -4
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/hands-lane.d.ts +0 -23
- package/dist/capabilities/hands-lane.js +3 -2
- package/dist/capabilities/send-user-file-tool.d.ts +10 -22
- package/dist/capabilities/send-user-file-tool.js +1 -8
- package/dist/capability-facts.d.ts +296 -0
- package/dist/capability-facts.js +354 -0
- package/dist/config-catalog.js +2 -1
- package/dist/config-center/apply-effective.js +11 -11
- package/dist/config-provider.d.ts +16 -0
- package/dist/config-provider.js +11 -2
- package/dist/config-types.d.ts +20 -1
- package/dist/config.d.ts +55 -29
- package/dist/config.js +48 -25
- package/dist/deployment-governance.d.ts +15 -4
- package/dist/deployment-governance.js +20 -2
- package/dist/fleet/fleet-bus.d.ts +1 -22
- package/dist/hooks/hook-runner.d.ts +29 -3
- package/dist/hooks/hook-runner.js +30 -17
- package/dist/http/admission.d.ts +6 -0
- package/dist/http/admission.js +29 -7
- package/dist/http/approval-sweeps.js +20 -0
- package/dist/http/checkpoint-ctx.d.ts +68 -0
- package/dist/http/checkpoint-ctx.js +36 -0
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/principal-gate.d.ts +3 -0
- package/dist/http/principal-gate.js +3 -0
- package/dist/http/resume-legs.js +19 -1
- package/dist/http/retired-skill-manifest.d.ts +23 -0
- package/dist/http/retired-skill-manifest.js +22 -0
- package/dist/http/route-ctx.d.ts +7 -0
- package/dist/http/route-table.d.ts +3 -2
- package/dist/http/route-table.js +5 -5
- package/dist/http/routes/a2a-serve.d.ts +1 -3
- package/dist/http/routes/a2a-serve.js +14 -23
- package/dist/http/routes/admin-config-refresh.js +7 -2
- package/dist/http/routes/admin-drain.js +7 -2
- package/dist/http/routes/admin-memory-optout.d.ts +0 -2
- package/dist/http/routes/admin-memory-optout.js +20 -7
- package/dist/http/routes/adoption.js +19 -5
- package/dist/http/routes/agents-roster.js +4 -2
- package/dist/http/routes/approvals-assistant.js +41 -18
- package/dist/http/routes/background.d.ts +0 -2
- package/dist/http/routes/background.js +4 -5
- package/dist/http/routes/capabilities.d.ts +2 -1
- package/dist/http/routes/capabilities.js +63 -65
- package/dist/http/routes/config-catalog.d.ts +4 -5
- package/dist/http/routes/config-catalog.js +16 -2
- package/dist/http/routes/devices.js +17 -11
- package/dist/http/routes/diagnostics.d.ts +5 -0
- package/dist/http/routes/diagnostics.js +8 -1
- package/dist/http/routes/fleet.js +4 -2
- package/dist/http/routes/images.js +30 -3
- package/dist/http/routes/leader.js +7 -1
- package/dist/http/routes/mcp-reconnect.js +17 -10
- package/dist/http/routes/memory-bundle.js +20 -6
- package/dist/http/routes/memory-compliance.js +19 -5
- package/dist/http/routes/memory-consolidation.d.ts +9 -12
- package/dist/http/routes/memory-consolidation.js +17 -3
- package/dist/http/routes/memory-origin.js +19 -5
- package/dist/http/routes/notify-wake.js +5 -3
- package/dist/http/routes/observability.js +19 -5
- package/dist/http/routes/retention-ops.js +6 -1
- package/dist/http/routes/rules.js +49 -21
- package/dist/http/routes/run-memory-capture.js +5 -2
- package/dist/http/routes/runs.js +90 -60
- package/dist/http/routes/session-sync.js +64 -53
- package/dist/http/routes/sessions-list.js +8 -5
- package/dist/http/routes/sessions.js +40 -25
- package/dist/http/routes/shared-memory.js +5 -3
- package/dist/http/routes/tasks.js +5 -3
- package/dist/http/routes/trace-usage.js +18 -11
- package/dist/http/routes/workflows.js +36 -13
- package/dist/http/server.js +4 -1
- package/dist/http/wire-types.d.ts +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +2 -1
- package/dist/local-data-root.d.ts +1 -1
- package/dist/mcp-content-origin.d.ts +0 -8
- package/dist/model-entry-refusal.d.ts +10 -2
- package/dist/model-entry-refusal.js +19 -3
- package/dist/namespaced-rule-name.d.ts +54 -0
- package/dist/namespaced-rule-name.js +33 -0
- package/dist/observability/corrupt-read-seat.d.ts +4 -3
- package/dist/observability/corrupt-read-seat.js +4 -3
- package/dist/observability/fail-open.d.ts +10 -9
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/permission-rule-list.d.ts +81 -0
- package/dist/permission-rule-list.js +17 -0
- package/dist/plugins/file-run-store.d.ts +7 -1
- package/dist/plugins/file-run-store.js +20 -7
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/one-time-migrations.d.ts +4 -2
- package/dist/plugins/one-time-migrations.js +26 -3
- package/dist/plugins/pg-session-storage.js +1 -1
- package/dist/plugins/run-store-sql.js +4 -4
- package/dist/plugins/session-policy-store-sql.js +4 -2
- package/dist/plugins/store-backend.d.ts +6 -1
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/store-contracts.d.ts +27 -8
- package/dist/plugins/store-contracts.js +12 -3
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/request-key-closure.js +1 -1
- package/dist/run-local.d.ts +37 -3
- package/dist/run-local.js +52 -6
- package/dist/runs.d.ts +0 -58
- package/dist/runs.js +15 -20
- package/dist/runtime-governance.d.ts +21 -1
- package/dist/runtime-governance.js +6 -1
- package/dist/security.js +2 -2
- package/dist/session-policy-wire.d.ts +40 -59
- package/dist/session-policy-wire.js +60 -32
- package/dist/spec-fields.d.ts +32 -0
- package/dist/spec-fields.js +31 -16
- package/dist/standing-answer-grant.d.ts +8 -0
- package/dist/standing-answer-grant.js +5 -0
- package/dist/task-settings.d.ts +17 -6
- package/dist/task-settings.js +16 -30
- package/dist/tool-approval.d.ts +32 -4
- package/dist/tool-approval.js +31 -8
- package/dist/trace/core-keyset-guard.d.ts +13 -8
- package/dist/trace/project.js +9 -0
- package/dist/trace/self-minted-wire.d.ts +42 -0
- package/dist/trace/self-minted-wire.js +13 -0
- package/dist/user-input-ledger.d.ts +8 -5
- package/package.json +6 -4
- package/scripts/drain-check-local.mjs +528 -0
- package/scripts/drain-check.mysql.sql +67 -0
- package/scripts/drain-check.pg.sql +71 -0
- package/scripts/retired-mode-seed-drain-local.mjs +0 -317
package/dist/config.d.ts
CHANGED
|
@@ -285,18 +285,22 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
|
|
|
285
285
|
* ① env 腿(`APPROVAL_REQUIRE`/`DENY`/`NEVER_AUTO`,下方)—— boot 拒启;
|
|
286
286
|
* ② 中心下发腿(`runtime.approvalRequire`,config-center/apply-effective.ts)—— 域级拒绝+保前值+warn
|
|
287
287
|
* (常驻进程不 crash);
|
|
288
|
-
* ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts)—— 该请求 422
|
|
288
|
+
* ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts;S-654 (c) 起 `allow` 同判)—— 该请求 422;
|
|
289
|
+
* ④ env 腿 `APPROVAL_GRANT`(7.115.0,运维的 allow 规则,与 ③ 的 allow 桶同一个消费者 `rule-policy`)—— boot 拒启({@link approvalGrantRefusal})。
|
|
289
290
|
*
|
|
290
291
|
* 五种拼法:
|
|
291
292
|
* - CC 形规则条目(`Bash(ps:*)`/`Edit(src/**)`,[ref] [ref]§七 装机实测):CC 的权限规则 DSL 写法。这些
|
|
292
293
|
* 名单比的是**整串工具名**,带括号那串不是任何活工具的名字 ⇒ 永不匹配。指引=按命令名走
|
|
293
294
|
* `runtime.commandPolicy`,整条 shell 进门走 `MANUAL_MODE_SHELL_GATE=always`。判别式见
|
|
294
295
|
* {@link findCcRuleFormNames}(它同时是 `allow` 腿的**唯一**判据,见下方例外说明)。
|
|
295
|
-
* - 退役名(`bash`/`Task`/`KillShell`…)
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
296
|
+
* - 退役名(`bash`/`Task`/`KillShell`…)与 pre-prefix 短名(`figma__x`):S-1047(core 7.41.0 #1156)起判据是 core 导出的
|
|
297
|
+
* **那一只** `judgeRuleToolName`(准备阶段、两只会话规则店、本仓会话规则写口同调),本函数零手写臂;指引 = core 表给的现役指引 /
|
|
298
|
+
* 补全协议前缀。
|
|
299
|
+
* - 够不着挂载工具的协议名:缺段(`mcp__`、`mcp__srv__`、`mcp____x`)与**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、
|
|
300
|
+
* 超长 / 归一后带连串下划线的服务器段;服务器段带通配的 `mcp__*` 单列判词 `wildcard_peer`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。
|
|
301
|
+
* 判官与句子是 `namespaced-rule-name.ts` 的那**一只**(判据 = core `mintNamespacePrefix` 的自铸对照;会话规则写口两腿与 SQL 店写口同调),
|
|
302
|
+
* 本文件零手写臂。修前(7.116.0 之前)这里只有缺段一臂外加一条只排 `*` 的特例(`coversAServer`),对端段带点号 / 超长的覆盖形在每个面都照收。
|
|
303
|
+
* ⚠️ 两段覆盖形 `mcp__<server>`(服务器段是自铸形)够得着(S-1047 收掉的分叉,见下)。
|
|
300
304
|
* - **core 的文法编不出来的串**(名字里带反斜杠、单条 >512 字符、空串):判词取自 core 自己的
|
|
301
305
|
* `validatePermissionRules`,见 {@link uncompilableRuleGuidance}。[ref]①([ref])补齐 ——
|
|
302
306
|
* 此前只有 `ask` 腿(经 `createPermissionRulePolicy` 的 throw)拒得出这一族,`deny` 静默 no-op。
|
|
@@ -312,12 +316,12 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
|
|
|
312
316
|
* `<ns>__<server>__*` / `<ns>__<server>__<glob>` 在**每一条**腿上都是**真判**,不再是
|
|
313
317
|
* 「放行进 no-op」—— 而那正是 [ref] 病形(`APPROVAL_DENY=mcp__figma__*` 判 allow)的根。
|
|
314
318
|
*
|
|
315
|
-
*
|
|
316
|
-
* (`
|
|
317
|
-
*
|
|
318
|
-
* (
|
|
319
|
-
*
|
|
320
|
-
*
|
|
319
|
+
* 🔴 **S-1047(core 7.41.0 #1156)收掉的分叉:两段覆盖形 `<ns>__<server>`(`mcp__figma`)自 7.116.0 起受理**。core 全家把它解成
|
|
320
|
+
* covering(这台服务器的每一只工具,与 `mcp__figma__*` 同义),core 的判词 `judgeRuleToolName` 判它 `current`,本仓会话规则写口也一直
|
|
321
|
+
* 收它;而本函数此前以「一名两义」(运维也可能是把三段名写漏了一段)拒它,句子却说它「永远不生效」—— 对它不成立,且同一个名字在本仓两张表上
|
|
322
|
+
* 两种答案。收成一个答案:受理。代价(三问在 CHANGELOG 7.116.0):少了「三段名漏打一截」的那一次拦截 —— 想要一只工具就写全名;
|
|
323
|
+
* 拒放集合的变化逐条在 MIGRATION 7.116.0 节。服务器段带通配(`mcp__*`)仍拒(core 的匹配器对服务器段逐字比,一只都够不着;7.116.0 起它是
|
|
324
|
+
* 「对端段铸不出」的一例,不再单列)。
|
|
321
325
|
*/
|
|
322
326
|
export interface UnmatchableToolName {
|
|
323
327
|
name: string;
|
|
@@ -327,28 +331,50 @@ export interface UnmatchableToolName {
|
|
|
327
331
|
* S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。两个消费者形态:
|
|
328
332
|
*
|
|
329
333
|
* · `"tool-name-table"` —— 消费者按**整串工具名**比对(精确 ∪ covering):env `APPROVAL_*` 三键与
|
|
330
|
-
* 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`)
|
|
331
|
-
* `settings.permissions.allow`(消费者 core 的 `createAllowDenyPolicy` 的 allow 位)。带括号的内容形
|
|
334
|
+
* 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`)。带括号的内容形
|
|
332
335
|
* 在这里永远匹配不上任何活工具名 —— 与引擎的规则文法认不认得它**无关**,是这条腿的比对方式决定的,
|
|
333
336
|
* 所以内容形照旧响亮拒 + 指路。
|
|
334
337
|
* · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):
|
|
335
|
-
* `settings.permissions.deny`
|
|
336
|
-
*
|
|
337
|
-
* `
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* 写下的拼法族错,静默代价是整只 Bash 不可用。
|
|
338
|
+
* `settings.permissions.deny` / `.ask`(S-177 / core 7.9.0 起)与 `.allow`(S-654 (c) / 7.115.0 起;修前是
|
|
339
|
+
* `createAllowDenyPolicy` 的名单位)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` /
|
|
340
|
+
* `Edit(~/.sema/**)`),所以本仓不许再替它拒 —— 判据整条交回 core 的 `validatePermissionRules`,按这张表
|
|
341
|
+
* 自己的 behavior 问(allow 侧 core 只收工具名形,内容形由 core 判拒)。
|
|
342
|
+
*
|
|
343
|
+
* S-654 (c) 前这里还有一条「`allow` 豁免名字形与文法两族」的不对称,前提是 allow 为白名单(匹配不上 ⇒ 全拒,
|
|
344
|
+
* fail-closed 且当场可见);allow 改成规则后那条前提不在了(匹配不上 ⇒ 什么也不放行、照常问,写的人收不到信号),豁免随之删除。
|
|
343
345
|
*/
|
|
344
346
|
export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
347
|
+
/** {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器)、按哪种态度问 core 的文法、路径基。 */
|
|
348
|
+
export interface UnmatchableToolNameJudgment {
|
|
349
|
+
readonly consumer: RuleEntryConsumer;
|
|
350
|
+
readonly behavior: RuleBehavior;
|
|
351
|
+
readonly pathBases?: PathRuleBases;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* 「这张表里有永远不会生效的条目」的**判据与拒因句,同一只函数**(返回 `undefined` = 通过;拒 ⇒ 句子 + 被点名的条目)。
|
|
355
|
+
* 五条腿全调它:env `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO`(拒启)、中心下发 `governance.approvalRequire`(整组拒)、
|
|
356
|
+
* 请求体 `settings.permissions.{allow,ask,deny}`(422)、运维 `APPROVAL_GRANT`(拒启)。修前判据(`findUnmatchableToolNames`)与成句
|
|
357
|
+
* (`formatUnmatchableToolNames`)是两只导出,句子里那半句(「这些表都是整串逐字比对」)只对名表成立 —— 规则编译器的
|
|
358
|
+
* 三只桶(与 `APPROVAL_GRANT`)是按规则文法编、按 covering 匹配的,不是整串比对。
|
|
359
|
+
*
|
|
360
|
+
* 句子对两种消费者、三种态度都成立:每一条要么永远匹配不到活工具(退役名 / 无协议前缀的短名 / 缺段或对端段铸不出的协议名 / 名表里的内容形),要么编不成
|
|
361
|
+
* 一条规则(core 文法的判词原样带出);名字按写的样子读,不做别名折叠、不补协议前缀。逐条的「为什么 / 怎么改」在 `→` 之后。
|
|
362
|
+
*/
|
|
363
|
+
export declare function unmatchableToolNamesRefusal(source: string, names: readonly string[], judgment: UnmatchableToolNameJudgment): {
|
|
364
|
+
readonly message: string;
|
|
365
|
+
readonly names: readonly string[];
|
|
366
|
+
} | undefined;
|
|
367
|
+
/**
|
|
368
|
+
* 7.115.0 —— 运维 allow 规则 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:
|
|
369
|
+
* 返回句子 = 拒,`undefined` = 通过)。判据与请求腿的 `settings.permissions.allow` 是**同一只**:
|
|
370
|
+
* ① 名字形与 core 文法 —— {@link unmatchableToolNamesRefusal},消费者 `rule-policy`、behavior `allow`(退役名 / 无前缀短名 / 缺段或对端段铸不出的协议名 /
|
|
371
|
+
* 反斜杠 / 超长 / 内容形与参数级的 allow;句子点名哪一条、为什么、怎么改);
|
|
372
|
+
* ② 整表试编一次 —— 编的就是治理折叠里那一只(`runtime-governance.ts` {@link compileApprovalGrantLayers}),于是 core 的整表帽这类
|
|
373
|
+
* 只在编译时才抛的判据也在启动时响亮,而不是每个任务一次 500。
|
|
374
|
+
* 没有 `pathBases`:运维的规则不挂在任何一条任务的根上,而 allow 文法本来只收工具名形(路径形的 allow core 拒编,在 ① 里就拒了)。
|
|
375
|
+
* 坏值**不许**静默丢掉那一条后照常起:运维以为放行了,无人值守腿照旧拒 / 停驻,没有任何信号指回这里。
|
|
376
|
+
*/
|
|
377
|
+
export declare function approvalGrantRefusal(names: readonly string[]): string | undefined;
|
|
352
378
|
/**
|
|
353
379
|
* [ref] 件B/C/D —— 三条**部署治理声明**的 env 解析(auth 域的一段,fail-loud 全在这里)。
|
|
354
380
|
*
|
package/dist/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { DEFAULT_MODEL_REF } from "./model-select.js";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, isModelPriced, PROTOCOL_TABLE,
|
|
6
|
+
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable } from "@sema-agent/core";
|
|
7
7
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES, CENTER_CREDENTIAL_ENV } from "@sema-agent/settings-schema";
|
|
8
8
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
9
9
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
@@ -18,10 +18,11 @@ import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-post
|
|
|
18
18
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
19
19
|
import {} from "./posture-source.js";
|
|
20
20
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
21
|
+
import { describeUnreachableNamespacedRuleName, ruleEntryToolName, unreachableNamespacedRuleNameOf } from "./namespaced-rule-name.js";
|
|
21
22
|
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
|
|
22
23
|
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
23
24
|
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
24
|
-
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
25
|
+
import { APPROVAL_GRANT_SOURCE, AUTONOMY_MODES, compileApprovalGrantLayers } from "./runtime-governance.js";
|
|
25
26
|
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
26
27
|
import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
|
|
27
28
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
@@ -1226,14 +1227,16 @@ function uncompilableRuleGuidance(name, behavior, pathBases) {
|
|
|
1226
1227
|
const issue = issues[0];
|
|
1227
1228
|
if (issue === undefined)
|
|
1228
1229
|
return undefined;
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1230
|
+
if (behavior === "allow" && (issue.code === "unsupported.bash_prefix" || issue.code === "unsupported.file_glob" || issue.code === "invalid.allow_param_rule")) {
|
|
1231
|
+
return (`${issue.code} — ${issue.message}. An allow rule list compiles tool-name rules only (an exact name, or mcp__<server>__* / ` +
|
|
1232
|
+
`a2a__<peer>__* for every tool on that peer); a content-form standing approval lives in the persisted-rule store ` +
|
|
1233
|
+
`(/v1/rules/cc-import, or keep the rule when you answer the call's card)`);
|
|
1234
|
+
}
|
|
1232
1235
|
return (`${issue.code} — core's rule grammar cannot compile this name (${issue.message}), so a table keyed by it matches nothing. ` +
|
|
1233
1236
|
`Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use ` +
|
|
1234
1237
|
`mcp__<server>__* / a2a__<peer>__* for "every tool on that peer"`);
|
|
1235
1238
|
}
|
|
1236
|
-
|
|
1239
|
+
function findUnmatchableToolNames(names, opts) {
|
|
1237
1240
|
const out = [];
|
|
1238
1241
|
for (const name of names) {
|
|
1239
1242
|
if (opts.consumer === "tool-name-table") {
|
|
@@ -1243,34 +1246,48 @@ export function findUnmatchableToolNames(names, opts) {
|
|
|
1243
1246
|
continue;
|
|
1244
1247
|
}
|
|
1245
1248
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
out.push({ name, guidance: retired });
|
|
1249
|
+
const subject = opts.consumer === "tool-name-table" ? name : ruleEntryToolName(name);
|
|
1250
|
+
const verdict = judgeRuleToolName(subject, NO_DECLARED_TOOL_NAMES);
|
|
1251
|
+
if (verdict.kind === "retired") {
|
|
1252
|
+
out.push({ name, guidance: verdict.guidance });
|
|
1251
1253
|
continue;
|
|
1252
1254
|
}
|
|
1253
1255
|
const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
|
|
1254
|
-
const subject = opts.consumer === "tool-name-table" ? name : (() => { const parsed = parseRuleText(name, opts.behavior); return "rule" in parsed ? parsed.rule.tool : name; })();
|
|
1255
1256
|
if (uncompilable !== undefined) {
|
|
1256
1257
|
out.push({ name, guidance: uncompilable });
|
|
1257
1258
|
continue;
|
|
1258
1259
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
if (subject.includes("__"))
|
|
1262
|
-
out.push({ name, guidance: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentToolName(name)}`).join(" or ")}` });
|
|
1260
|
+
if (verdict.kind === "unprefixed_namespace") {
|
|
1261
|
+
out.push({ name, guidance: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentToolName(name)}`).join(" or ")}` });
|
|
1263
1262
|
continue;
|
|
1264
1263
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1264
|
+
const unreachable = unreachableNamespacedRuleNameOf(subject);
|
|
1265
|
+
if (unreachable !== undefined)
|
|
1266
|
+
out.push({ name, guidance: describeUnreachableNamespacedRuleName(unreachable) });
|
|
1268
1267
|
}
|
|
1269
1268
|
return out;
|
|
1270
1269
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1270
|
+
const NO_DECLARED_TOOL_NAMES = new Set();
|
|
1271
|
+
export function unmatchableToolNamesRefusal(source, names, judgment) {
|
|
1272
|
+
const bad = findUnmatchableToolNames(names, judgment);
|
|
1273
|
+
if (bad.length === 0)
|
|
1274
|
+
return undefined;
|
|
1275
|
+
const message = (`${source} has ${bad.length === 1 ? "an entry" : "entries"} that can never take effect — ${bad.length === 1 ? "it" : "each"} would never match a live tool or cannot be compiled into a rule ` +
|
|
1276
|
+
`(names are read exactly as written: no alias folding, no protocol prefix added): ` +
|
|
1273
1277
|
bad.map((b) => `${presentToolName(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
1278
|
+
return { message, names: bad.map((b) => b.name) };
|
|
1279
|
+
}
|
|
1280
|
+
export function approvalGrantRefusal(names) {
|
|
1281
|
+
const unmatchable = unmatchableToolNamesRefusal(APPROVAL_GRANT_SOURCE, names, { consumer: "rule-policy", behavior: "allow" });
|
|
1282
|
+
if (unmatchable !== undefined)
|
|
1283
|
+
return unmatchable.message;
|
|
1284
|
+
try {
|
|
1285
|
+
compileApprovalGrantLayers(names);
|
|
1286
|
+
}
|
|
1287
|
+
catch (e) {
|
|
1288
|
+
return e instanceof Error ? e.message : String(e);
|
|
1289
|
+
}
|
|
1290
|
+
return undefined;
|
|
1274
1291
|
}
|
|
1275
1292
|
function streamApprovalConfig(gates) {
|
|
1276
1293
|
const cfg = {
|
|
@@ -1337,9 +1354,9 @@ function parseApprovalDomain(ctx) {
|
|
|
1337
1354
|
const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS, announceKeySetRejection("APPROVAL_HMAC_KEYS"));
|
|
1338
1355
|
const durableApproval = postureOn("DURABLE_APPROVAL", infraReady);
|
|
1339
1356
|
const rejectUnmatchableToolNames = (envName, names) => {
|
|
1340
|
-
const
|
|
1341
|
-
if (
|
|
1342
|
-
throw new Error(
|
|
1357
|
+
const unmatchable = unmatchableToolNamesRefusal(envName, names, { consumer: "tool-name-table", behavior: "deny" });
|
|
1358
|
+
if (unmatchable !== undefined)
|
|
1359
|
+
throw new Error(unmatchable.message);
|
|
1343
1360
|
return names;
|
|
1344
1361
|
};
|
|
1345
1362
|
return {
|
|
@@ -1353,6 +1370,12 @@ function parseApprovalDomain(ctx) {
|
|
|
1353
1370
|
resourceSuspendMaxSlices: nonNegativeIntEnv("RESOURCE_SUSPEND_MAX_SLICES", "跨片 slice 数上限"),
|
|
1354
1371
|
approvalAutoBudget: clampEnvWithWarn("APPROVAL_AUTO_BUDGET", "0", 0, 10000),
|
|
1355
1372
|
approvalNeverAuto: rejectUnmatchableToolNames("APPROVAL_NEVER_AUTO", csv("APPROVAL_NEVER_AUTO")),
|
|
1373
|
+
approvalGrant: ((names) => {
|
|
1374
|
+
const refusal = approvalGrantRefusal(names);
|
|
1375
|
+
if (refusal !== undefined)
|
|
1376
|
+
throw new Error(refusal);
|
|
1377
|
+
return names;
|
|
1378
|
+
})(csv("APPROVAL_GRANT")),
|
|
1356
1379
|
approvalHmacKeys: dgHmacKeys,
|
|
1357
1380
|
directApprovalDoor,
|
|
1358
1381
|
sensitiveWritePatterns: ((raw) => {
|
|
@@ -2296,7 +2319,7 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
2296
2319
|
"modelApiKeyEnv", "modelApiKeys", "modelRefusals", "modelQuotaWeights", "tiers", "projects", "roles", "atModelAllowlist", "cascadeLadder", "degrade",
|
|
2297
2320
|
];
|
|
2298
2321
|
const APPROVAL_GROUP_KEYS = [
|
|
2299
|
-
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
2322
|
+
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto", "approvalGrant",
|
|
2300
2323
|
"approvalHmacKeys", "durableApproval", "durableApprovalSource", "streamAskWindowMsSource", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
2301
2324
|
"resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "mcpElicitation",
|
|
2302
2325
|
"sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "autoModeClassifierFallback", "autoModeClassifierTimeoutMs", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
@@ -11,6 +11,9 @@ export interface DeploymentGovernanceConfigView {
|
|
|
11
11
|
readonly commandPolicy?: ServiceConfig["commandPolicy"];
|
|
12
12
|
readonly manualModeShellGate?: ServiceConfig["manualModeShellGate"];
|
|
13
13
|
readonly sensitiveWritePatterns: ServiceConfig["sensitiveWritePatterns"];
|
|
14
|
+
/** 7.115.0:运维的 allow 规则(`APPROVAL_GRANT`)—— 原串交给折叠属主编成叶(`runtime-governance.ts` `compileApprovalGrantLayers`)。
|
|
15
|
+
* 必填:三条消费腿的切面都要点名它,漏了是编译错,不是一条腿上静默没有这只旋钮。 */
|
|
16
|
+
readonly approvalGrant: ServiceConfig["approvalGrant"];
|
|
14
17
|
/**
|
|
15
18
|
* 本部署的**本地店根**(`LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent`)—— 在本口只喂
|
|
16
19
|
* **誊本门**(`<localDataRoot>/sessions`)。S-133 起它**不再**当守卫集的 `dataRoot`(那是引擎记忆根,见
|
|
@@ -209,7 +212,7 @@ export declare class RelativeTargetLexicalEnv extends StubExecutionEnv {
|
|
|
209
212
|
* core 的原因。env 只是编译期的占位(compilePatterns 不碰它),真裁决用的是每请求按 lane 铸的那一个。
|
|
210
213
|
*/
|
|
211
214
|
export declare function assertGuardPatternsUsable(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">): void;
|
|
212
|
-
/** 一条 boot
|
|
215
|
+
/** 一条 boot 期运维告警 / 通告的**载荷**(纯数据 ⇒ `build*`;打给谁、用哪只 logger、哪一级归消费腿)。 */
|
|
213
216
|
export interface OperatorWarning {
|
|
214
217
|
readonly event: string;
|
|
215
218
|
readonly fields: Record<string, unknown>;
|
|
@@ -230,15 +233,17 @@ export interface GateSeatView {
|
|
|
230
233
|
* 此前是「这个部署一个门都没接」这个 misconfig 的**唯一**提示,而守卫集只挡那二十来个路径段、其余写
|
|
231
234
|
* 目标照旧无裁决 —— 信号不能因为我们铺了基线就静默消失,所以由我们自己按同一判据说一次。
|
|
232
235
|
*
|
|
233
|
-
* 判据(与信号消失的条件逐字互补)
|
|
234
|
-
*
|
|
236
|
+
* 判据(与信号消失的条件逐字互补):**非门的部署叶在场**(守卫集,或 7.115.0 起运维的 allow 规则 `APPROVAL_GRANT` —— 两者都让
|
|
237
|
+
* `spec.toolPolicy` 在场、又都不是门:一个只拒守卫段,一个只表态 allow)∧ 这条腿的审批基线**没有席**(`absent`)∧ 单用户
|
|
238
|
+
* auto-accept 基线不适用。事件名 `tool_policy_only_sensitive_baseline` 是allow 规则之前定的,保留不改(运维的日志告警按它匹配),
|
|
239
|
+
* 哪一片叶在场写在 `detail` 与 `guardedPatterns` / `grantRules` 两个计数里。三个量都是部署常量 ⇒ 消费腿在 boot 期说一次,不是每任务一次。
|
|
235
240
|
* S-694:席读的是喂审批基线的**同一只**({@link ApprovalSeat})—— `durable` / `live` 两形都有人服 ask、名单
|
|
236
241
|
* 进策略,「守卫集是唯一的策略」这句话在它们上面不成立。今天真发的只剩 HTTP 腿「durable 关 ∧ 多租户」那一形。
|
|
237
242
|
*
|
|
238
243
|
* ⚠️ 单一属主([ref] 件三):HTTP 腿与 run-local 腿共用本判据与文案。两处各写一份 = 一处改了另一处
|
|
239
244
|
* 没改,而两份都长得像对的 —— 那正是本文件存在的理由。
|
|
240
245
|
*/
|
|
241
|
-
export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">, seat: GateSeatView): OperatorWarning | undefined;
|
|
246
|
+
export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns" | "approvalGrant">, seat: GateSeatView): OperatorWarning | undefined;
|
|
242
247
|
/**
|
|
243
248
|
* `applyRuntimeGovernance` 的 governance 实参预铸([ref] 件一)。
|
|
244
249
|
*
|
|
@@ -253,6 +258,12 @@ export declare function buildOnlySensitiveBaselineWarning(config: Pick<Deploymen
|
|
|
253
258
|
* 的读法因此不变。
|
|
254
259
|
*/
|
|
255
260
|
export declare function createDeploymentGovernanceInputs(config: DeploymentGovernanceConfigView, pathAdjudication: PathAdjudication, roots: DeploymentGovernanceRoots): DeploymentGovernanceInputs;
|
|
261
|
+
/**
|
|
262
|
+
* 7.115.0 —— 运维 allow 规则的**起服通告**载荷(纯数据 ⇒ `build*`;HTTP 腿与 run-local 腿各在装配时说一次,用 info 级)。
|
|
263
|
+
* 规则名不是秘密(它们是工具名),所以原样列出;空表 ⇒ `undefined`(不打)。运维只读面上同一个事实另有一处:
|
|
264
|
+
* `GET /v1/diagnostics/wiring` 的 `serverGates.approvalGrant`。
|
|
265
|
+
*/
|
|
266
|
+
export declare function buildApprovalGrantNotice(config: Pick<DeploymentGovernanceConfigView, "approvalGrant">): OperatorWarning | undefined;
|
|
256
267
|
/**
|
|
257
268
|
* 审批基线 —— `applyRuntimeGovernance` 那个 base 的 `toolPolicy` 座([ref] 件一)。**恒非
|
|
258
269
|
* `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
|
|
@@ -61,7 +61,7 @@ export function assertGuardPatternsUsable(config) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
export function buildOnlySensitiveBaselineWarning(config, seat) {
|
|
64
|
-
if (config.sensitiveWritePatterns.length === 0 || seat.singleUserAutoAcceptBaseline)
|
|
64
|
+
if ((config.sensitiveWritePatterns.length === 0 && config.approvalGrant.length === 0) || seat.singleUserAutoAcceptBaseline)
|
|
65
65
|
return undefined;
|
|
66
66
|
switch (seat.approvalSeat) {
|
|
67
67
|
case "durable":
|
|
@@ -74,12 +74,17 @@ export function buildOnlySensitiveBaselineWarning(config, seat) {
|
|
|
74
74
|
throw new Error(`approval seat "${String(unhandled)}" has no arm in the only-sensitive-baseline warning`);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
const nonGateLayers = [
|
|
78
|
+
...(config.sensitiveWritePatterns.length > 0 ? ["the sensitive-path DENY baseline (SENSITIVE_WRITE_PATTERNS)"] : []),
|
|
79
|
+
...(config.approvalGrant.length > 0 ? ["the APPROVAL_GRANT allow rules"] : []),
|
|
80
|
+
];
|
|
77
81
|
return {
|
|
78
82
|
event: "tool_policy_only_sensitive_baseline",
|
|
79
83
|
fields: {
|
|
80
|
-
detail: "
|
|
84
|
+
detail: `${nonGateLayers.join(" and ")} ${nonGateLayers.length > 1 ? "are" : "is"} this deployment's ONLY tool policy and not a gate — writes outside the guarded segments are unadjudicated, and core's own UNGATED warning no longer fires because a policy is now always present`,
|
|
81
85
|
remedy: "wire an approval face (DURABLE_APPROVAL / APPROVAL_REQUIRE with a store) or accept the single-user auto-accept baseline",
|
|
82
86
|
guardedPatterns: config.sensitiveWritePatterns.length,
|
|
87
|
+
grantRules: config.approvalGrant.length,
|
|
83
88
|
},
|
|
84
89
|
};
|
|
85
90
|
}
|
|
@@ -108,6 +113,19 @@ export function createDeploymentGovernanceInputs(config, pathAdjudication, roots
|
|
|
108
113
|
commandPolicy: config.commandPolicy,
|
|
109
114
|
...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
|
|
110
115
|
...(sensitivePathLayers ? { sensitivePathLayers } : {}),
|
|
116
|
+
...(config.approvalGrant.length > 0 ? { approvalGrant: config.approvalGrant } : {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function buildApprovalGrantNotice(config) {
|
|
120
|
+
if (config.approvalGrant.length === 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
return {
|
|
123
|
+
event: "approval_grant_in_force",
|
|
124
|
+
fields: {
|
|
125
|
+
rules: config.approvalGrant.length,
|
|
126
|
+
names: [...config.approvalGrant],
|
|
127
|
+
note: "APPROVAL_GRANT states the allow for the tools these rules name on every leg it is installed on (HTTP submit, durable resume, cross-replica revival of parked background agents, run-local) — those calls are no longer asked for want of a statement; any layer's own deny or ask (approval lists, request settings, session rules, the engine's mode gates) still wins",
|
|
128
|
+
},
|
|
111
129
|
};
|
|
112
130
|
}
|
|
113
131
|
export function createApprovalBaselinePolicy(config, seat) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { TaskNotificationPayload, TerminalCause, WorkflowRun } from "@sema-agent/core";
|
|
2
|
-
import type { RunRecord
|
|
3
|
-
import type { AssertAllKeysHandled } from "../trace/core-keyset-guard.js";
|
|
2
|
+
import type { RunRecord } from "../plugins/store-contracts.js";
|
|
4
3
|
/** [ref] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
|
|
5
4
|
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
6
5
|
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
@@ -674,24 +673,4 @@ export declare function isTerminalFleetStatus(status: FleetTaskStatus): boolean;
|
|
|
674
673
|
export declare function runStatusToFleet(status: RunStatusForFleet, opts?: {
|
|
675
674
|
awaitingPlanApproval?: boolean;
|
|
676
675
|
}): FleetTaskStatus;
|
|
677
|
-
/**
|
|
678
|
-
* S-213③ —— **编译期同源钉**:fleet 的 `"failed"` 桶必须恰好收下 `RUN_STATUS_CLASS` 判为「非成功终局」
|
|
679
|
-
* 的每一个 core 词。
|
|
680
|
-
*
|
|
681
|
-
* 这个词的病史:`blocked` 曾在本仓有**两个独立写者**且答案互斥 —— 本文件读它为「非成功终局(agent
|
|
682
|
-
* 放弃)⇒ 行离场」,而 `observability/run-terminal.ts` 读它为「治理判决,不是失败 ⇒ 不打行」。两处
|
|
683
|
-
* 各自穷举、各自正确地编译,结果是一条 `report_blocked` 的 run 在面板上离场、在引擎日志里一个字都没有。
|
|
684
|
-
* 语义收归 `plugins/store-contracts.ts` 的那张表之后,本行不再自持判断:表把某个词挪出/挪进
|
|
685
|
-
* `unsuccessful`(或 core 新增一个非成功终态)而上面的 switch 没跟上 ⇒ 下面这条收不敛到 `never` ⇒ tsc 红。
|
|
686
|
-
* (`"error"` 是 server 自有别名、不在那张表的辖域内,故不在本钉的两边出现。)
|
|
687
|
-
*/
|
|
688
|
-
type FleetUnsuccessfulBucket = "failed" | "blocked";
|
|
689
|
-
type _GuardFleetUnsuccessfulBucket = AssertAllKeysHandled<Exclude<UnsuccessfulRunStatus, FleetUnsuccessfulBucket>>;
|
|
690
|
-
/** 反向门(与 `core-keyset-guard.ts` 的 `_Guard*Reverse` 同形):桶里留了一个表**不再**判为非成功终局的
|
|
691
|
-
* 词同样红 —— 只有正向门时,把 `blocked` 挪回 `succeeded` 而 switch 照旧塞进 failed 桶,并集覆盖、
|
|
692
|
-
* 差集门一声不吭(本批实测:只加正向门时该变异 tsc rc=0)。两道钉一起加。 */
|
|
693
|
-
type _GuardFleetUnsuccessfulBucketReverse = AssertAllKeysHandled<Exclude<FleetUnsuccessfulBucket, UnsuccessfulRunStatus>>;
|
|
694
|
-
/** 占位使用(防 noUnusedLocals 形 lint;类型别名本身零运行时)。 */
|
|
695
|
-
export type FleetVocabGuards = [_GuardFleetUnsuccessfulBucket, _GuardFleetUnsuccessfulBucketReverse];
|
|
696
|
-
export {};
|
|
697
676
|
//# sourceMappingURL=fleet-bus.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HooksConfig } from "@sema-agent/settings-schema/hooks";
|
|
1
|
+
import { HooksConfig, type HookEventName } from "@sema-agent/settings-schema/hooks";
|
|
2
2
|
import type { Hooks } from "@sema-agent/core";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
4
|
import type { HookNoticeObservation } from "../fleet/fleet-bus.js";
|
|
@@ -80,6 +80,10 @@ export interface ParseHooksConfigResult {
|
|
|
80
80
|
* 拒放集合相对 7.113.0 的一处放宽(披露在 CHANGELOG / MIGRATION):超过上限的字面别名 matcher 7.113.0 拒(当时字面判不设上限),自 7.114.0 两档都判未判、受理。
|
|
81
81
|
*
|
|
82
82
|
* 错误句只回显**目录里的**别名与正名(闭集字符串),不回显调用方的原字节(S-716 姿势)。
|
|
83
|
+
*
|
|
84
|
+
* 🔴 **判决是数据,句子与体的机读位同出一只铸点**(S-1025;client-core 请托,additive):{@link deadToolMatcherIssue} 回 {@link DeadToolMatcherIssue}(event / alias / canonical)
|
|
85
|
+
* 而不是句子;{@link buildDeadToolMatcherRefusal} 是**唯一**把它铸成拒句 + `field` + `detail` 的函数,受理门只取它的返回、**不解析句子**。体上多出的两键让三端不必解析英文就能把这只拒
|
|
86
|
+
* 与普通提交失败分流、提示用户改 matcher;句子逐字同 7.114.0、零新码。
|
|
83
87
|
*/
|
|
84
88
|
/** 超此长度的 matcher 不在受理面读(两档同一个上限;未判,reason = oversize)。与 {@link MAX_HOOK_COMMAND_CHARS} 同量纲的服务侧防线:真实 matcher(精确名 / 交替 / `mcp__.*`)远在其内;目录全部工具名交替 ~0.5 KiB。 */
|
|
85
89
|
export declare const MAX_MATCHER_EVAL_CHARS = 8192;
|
|
@@ -95,13 +99,35 @@ export interface MatcherEvalBudget {
|
|
|
95
99
|
readonly budgetMs?: number;
|
|
96
100
|
readonly now?: () => number;
|
|
97
101
|
}
|
|
102
|
+
/** 一条受理面判决(纯数据):`event` 事件下的某只 matcher 选中了退役别名 `alias`、却选不中它的正名 `canonical`。`alias` / `canonical` 恒取自 core 静态目录(闭集字符串,不含调用方原字节)。 */
|
|
103
|
+
export interface DeadToolMatcherIssue {
|
|
104
|
+
readonly event: HookEventName;
|
|
105
|
+
readonly alias: string;
|
|
106
|
+
readonly canonical: string;
|
|
107
|
+
}
|
|
108
|
+
/** {@link buildDeadToolMatcherRefusal} 的返回(纯数据):拒句 + 400 体上的两个机读位。`field` 恒为字面 `"settings.hooks"`;`detail` 与 {@link DeadToolMatcherIssue} 同三键(逐个挑出,不 spread)。 */
|
|
109
|
+
export interface DeadToolMatcherRefusal {
|
|
110
|
+
readonly message: string;
|
|
111
|
+
readonly field: "settings.hooks";
|
|
112
|
+
readonly detail: {
|
|
113
|
+
readonly event: HookEventName;
|
|
114
|
+
readonly alias: string;
|
|
115
|
+
readonly canonical: string;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 判决 → 拒句 + 机读位。**拒句的唯一铸点**:句子逐字 = 7.114.0 的那一句(`test/hooks-alias-matcher-s995.test.ts` R7 冻结);受理门 `admission.ts` 用返回值填 400 体,
|
|
120
|
+
* 不从句子里反解字段。
|
|
121
|
+
*/
|
|
122
|
+
export declare function buildDeadToolMatcherRefusal(issue: DeadToolMatcherIssue): DeadToolMatcherRefusal;
|
|
98
123
|
/**
|
|
99
|
-
* fresh 提交面专用判据(S-924 / S-995):配置里第一条「永远打不中」的工具事件 matcher
|
|
124
|
+
* fresh 提交面专用判据(S-924 / S-995):配置里第一条「永远打不中」的工具事件 matcher 的判决数据({@link DeadToolMatcherIssue});没有 ⇒ undefined。
|
|
125
|
+
* 拒句与 400 体的机读位由 {@link buildDeadToolMatcherRefusal} 从这份数据铸(S-1025)。
|
|
100
126
|
* 入参是 {@link parseHooksConfig} 已校验过的配置;`depth` 由调用点按车道定(见头注:`taskHooksHonored(config) ? "regex" : "literal"`)。
|
|
101
127
|
* `budget` **仅供测试注入**({@link MatcherEvalBudget});生产调用点只传前两个实参。
|
|
102
128
|
* **只有 `admission.ts` 的 fresh 提交门调它** —— resume 重放不调(理由见上面的头注)。未判的 matcher 过 `recordFailOpen`(tag `MATCHER_UNJUDGED_TAG`)。
|
|
103
129
|
*/
|
|
104
|
-
export declare function deadToolMatcherIssue(config: HooksConfig, depth: MatcherEvalDepth, budget?: MatcherEvalBudget):
|
|
130
|
+
export declare function deadToolMatcherIssue(config: HooksConfig, depth: MatcherEvalDepth, budget?: MatcherEvalBudget): DeadToolMatcherIssue | undefined;
|
|
105
131
|
/**
|
|
106
132
|
* 把 wire 上的 untrusted `settings.hooks` 校验成契约 {@link HooksConfig} + 服务侧上限。
|
|
107
133
|
* 错误返回 error 字符串而不 throw——submit 路径 400 fail-loud、resume 防御路径 warn-drop,由调用方选。
|
|
@@ -142,6 +142,13 @@ function deadAliasInMatcher(matcher, depth, budget) {
|
|
|
142
142
|
}
|
|
143
143
|
return undefined;
|
|
144
144
|
}
|
|
145
|
+
export function buildDeadToolMatcherRefusal(issue) {
|
|
146
|
+
return {
|
|
147
|
+
message: `settings.hooks.${issue.event}: matcher "${issue.alias}" is an alias; tool-event matchers match the tool's wire name (PostToolUse / PostToolUseFailure since 7.107.0) — use "${issue.canonical}"`,
|
|
148
|
+
field: "settings.hooks",
|
|
149
|
+
detail: { event: issue.event, alias: issue.alias, canonical: issue.canonical },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
145
152
|
export function deadToolMatcherIssue(config, depth, budget = {}) {
|
|
146
153
|
const now = budget.now ?? (() => performance.now());
|
|
147
154
|
const deadline = { deadlineMs: now() + (budget.budgetMs ?? ADMISSION_MATCHER_EVAL_BUDGET_MS), now };
|
|
@@ -158,7 +165,7 @@ export function deadToolMatcherIssue(config, depth, budget = {}) {
|
|
|
158
165
|
recordFailOpen(MATCHER_UNJUDGED_TAG, `event=${event} reason=${verdict.reason} matcherChars=${group.matcher.length}`);
|
|
159
166
|
continue;
|
|
160
167
|
}
|
|
161
|
-
return
|
|
168
|
+
return { event, alias: verdict.alias, canonical: verdict.canonical };
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
return undefined;
|
|
@@ -855,6 +862,7 @@ export function createTaskHooks(config, ctx) {
|
|
|
855
862
|
let currentInput = input;
|
|
856
863
|
let rewritten = false;
|
|
857
864
|
let asked;
|
|
865
|
+
let allowedFor;
|
|
858
866
|
let unknownDecision = false;
|
|
859
867
|
const contexts = [];
|
|
860
868
|
const deadlineAt = Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000;
|
|
@@ -868,11 +876,12 @@ export function createTaskHooks(config, ctx) {
|
|
|
868
876
|
}
|
|
869
877
|
if (!m)
|
|
870
878
|
continue;
|
|
879
|
+
const groupInput = currentInput;
|
|
871
880
|
const payload = {
|
|
872
881
|
...basePayload(ctx, tctx.permissionMode),
|
|
873
882
|
hook_event_name: "PreToolUse",
|
|
874
883
|
tool_name: toolName,
|
|
875
|
-
tool_input:
|
|
884
|
+
tool_input: groupInput,
|
|
876
885
|
tool_use_id: tctx.toolCallId,
|
|
877
886
|
};
|
|
878
887
|
const singles = await runMatchingEntries("PreToolUse", [{ ...group, matcher: undefined }], toolName, payload, ctx, onceFired, deadlineAt, `PreToolUse:${gi}`);
|
|
@@ -913,10 +922,13 @@ export function createTaskHooks(config, ctx) {
|
|
|
913
922
|
asked = clip(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS);
|
|
914
923
|
}
|
|
915
924
|
if (decision === "allow" || decision === undefined) {
|
|
916
|
-
|
|
925
|
+
const rewrites = h.updatedInput !== null && typeof h.updatedInput === "object";
|
|
926
|
+
if (rewrites) {
|
|
917
927
|
currentInput = h.updatedInput;
|
|
918
928
|
rewritten = true;
|
|
919
929
|
}
|
|
930
|
+
if (decision === "allow")
|
|
931
|
+
allowedFor = { input: rewrites ? h.updatedInput : groupInput };
|
|
920
932
|
}
|
|
921
933
|
else if (decision !== "deny" && decision !== "ask") {
|
|
922
934
|
unknownDecision = true;
|
|
@@ -946,13 +958,11 @@ export function createTaskHooks(config, ctx) {
|
|
|
946
958
|
...buildHookContextField(contexts),
|
|
947
959
|
};
|
|
948
960
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
};
|
|
955
|
-
}
|
|
961
|
+
const carried = { ...(rewritten ? { updatedInput: currentInput } : {}), ...buildHookContextField(contexts) };
|
|
962
|
+
if (allowedFor !== undefined && allowedFor.input === currentInput)
|
|
963
|
+
return { action: "allow", ...carried };
|
|
964
|
+
if (rewritten || contexts.length > 0)
|
|
965
|
+
return carried;
|
|
956
966
|
return undefined;
|
|
957
967
|
};
|
|
958
968
|
hooks.preToolUse = preToolUseHook;
|
|
@@ -1324,6 +1334,7 @@ export function composeHooks(deployment, task) {
|
|
|
1324
1334
|
let current = input;
|
|
1325
1335
|
const contexts = [];
|
|
1326
1336
|
let asked;
|
|
1337
|
+
let allowedFor;
|
|
1327
1338
|
for (const slot of [d.preToolUse?.bind(d), t.preToolUse?.bind(t)]) {
|
|
1328
1339
|
if (!slot)
|
|
1329
1340
|
continue;
|
|
@@ -1343,6 +1354,10 @@ export function composeHooks(deployment, task) {
|
|
|
1343
1354
|
asked = r;
|
|
1344
1355
|
break;
|
|
1345
1356
|
case "allow":
|
|
1357
|
+
if (updatedInput !== undefined)
|
|
1358
|
+
current = updatedInput;
|
|
1359
|
+
allowedFor = { input: current };
|
|
1360
|
+
break;
|
|
1346
1361
|
case undefined:
|
|
1347
1362
|
if (updatedInput !== undefined)
|
|
1348
1363
|
current = updatedInput;
|
|
@@ -1362,13 +1377,11 @@ export function composeHooks(deployment, task) {
|
|
|
1362
1377
|
...buildHookContextField(contexts),
|
|
1363
1378
|
};
|
|
1364
1379
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1380
|
+
const carried = { ...(current !== input ? { updatedInput: current } : {}), ...buildHookContextField(contexts) };
|
|
1381
|
+
if (allowedFor !== undefined && allowedFor.input === current)
|
|
1382
|
+
return { action: "allow", ...carried };
|
|
1383
|
+
if (current !== input || contexts.length > 0)
|
|
1384
|
+
return carried;
|
|
1372
1385
|
return undefined;
|
|
1373
1386
|
};
|
|
1374
1387
|
}
|
package/dist/http/admission.d.ts
CHANGED
|
@@ -52,6 +52,12 @@ export declare const MAX_OUTPUT_SCHEMA_CHARS = 32768;
|
|
|
52
52
|
* 🔴 **server 侧禁裁列表 / 禁截描述**:渲染归引擎(单一属主)。本函数只判「这一条技能的形能不能用」。
|
|
53
53
|
*/
|
|
54
54
|
export declare function validateUserSkills(skills: unknown): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* core 7.41.0 #1303 —— 顶层技能带 `manifest` 的拒因句(单铸点)。出路只点本服务上**真存在**的旋钮:技能的两只清单
|
|
57
|
+
* (`allowedTools` / `disallowedTools`)、圈工具 = deny 规则(请求 `settings.permissions.deny` / 运维 `APPROVAL_DENY`)或子代定义的
|
|
58
|
+
* `agents[].allowTools`、圈写路径 = 会话规则 `allowDirs`(`PUT /v1/sessions/:id/policy`)。
|
|
59
|
+
*/
|
|
60
|
+
export declare const SKILL_MANIFEST_RETIRED = "skill.manifest was retired in core 7.41.0 \u2014 a skill's tools are its allowedTools / disallowedTools now (an allow-rule and a deny-rule source for the run the skill is activated in, never a fence); move manifest.allowTools to allowedTools. To fence tools use deny rules (settings.permissions.deny) or a delegated agent's allowTools; to fence write paths use the session rules' allowDirs";
|
|
55
61
|
export declare function createAdmission(ctx: {
|
|
56
62
|
deps: FlatServiceDeps;
|
|
57
63
|
runnerFor: FlatServiceDeps["runnerFor"];
|