@sema-agent/server 7.116.1 → 7.117.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 +98 -0
- package/USAGE.md +32 -27
- package/dist/approval-card.d.ts +43 -9
- package/dist/approval-card.js +36 -4
- package/dist/boot/budget-tracing.js +2 -3
- package/dist/boot/config-center.d.ts +2 -2
- package/dist/boot/config-center.js +5 -7
- package/dist/boot/resolve-spec.js +10 -3
- package/dist/boot/runner-deps.d.ts +5 -3
- package/dist/boot/runner-deps.js +4 -2
- package/dist/brain.d.ts +17 -8
- package/dist/brain.js +10 -7
- package/dist/config-catalog.js +3 -2
- package/dist/config-center/apply-effective.d.ts +6 -16
- package/dist/config-center/apply-effective.js +0 -6
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/restart-signal.d.ts +1 -1
- package/dist/config-types.d.ts +12 -16
- package/dist/config.d.ts +34 -27
- package/dist/config.js +23 -19
- package/dist/deployment-governance.d.ts +11 -12
- package/dist/deployment-governance.js +4 -9
- package/dist/governance-overrides.d.ts +20 -0
- package/dist/governance-overrides.js +8 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +15 -12
- package/dist/http/checkpoint-ctx.d.ts +83 -24
- package/dist/http/checkpoint-ctx.js +68 -8
- package/dist/http/resume-legs.js +11 -15
- package/dist/http/retired-skill-manifest.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/routes/capabilities.js +3 -3
- package/dist/http/routes/runs.js +12 -0
- package/dist/model-select.d.ts +16 -5
- package/dist/model-select.js +7 -3
- package/dist/namespaced-rule-name.d.ts +7 -5
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/observability/run-terminal.js +21 -1
- package/dist/outbound-dispatcher.d.ts +18 -0
- package/dist/outbound-dispatcher.js +96 -10
- package/dist/permission-rule-list.d.ts +20 -31
- package/dist/plugins/one-time-migrations.d.ts +20 -0
- package/dist/plugins/one-time-migrations.js +84 -4
- package/dist/router/route-orchestration.d.ts +2 -23
- package/dist/rule-entry-list.d.ts +99 -0
- package/dist/rule-entry-list.js +110 -0
- package/dist/run-local.d.ts +4 -2
- package/dist/run-local.js +3 -3
- package/dist/runs.js +1 -0
- package/dist/runtime-governance.d.ts +5 -21
- package/dist/runtime-governance.js +4 -8
- package/dist/spec-fields.d.ts +15 -24
- package/dist/spec-fields.js +16 -17
- package/dist/task-settings.d.ts +18 -18
- package/dist/task-settings.js +57 -30
- package/dist/tool-approval.d.ts +24 -33
- package/dist/tool-approval.js +5 -7
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +4 -6
- package/dist/trace/engine-notice-wire.js +14 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +31 -6
- package/scripts/drain-check.mysql.sql +31 -6
- package/scripts/drain-check.pg.sql +38 -8
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **规则条目表在本仓受理口的判** —— 一份,三张表同调(core 7.42.0 起「这个调用不用问」只有一个去处:引擎的 allow 层;条目的文法、匹配、
|
|
3
|
+
* auto 剔除、读成空的通告都归引擎,本仓不写第二套):
|
|
4
|
+
* · 请求自带技能的 `allowedTools` / `disallowedTools`(顶层 `skills[]` 与子代 `agents[].skills[]` 两处受理口,`spec-fields.ts` / `http/admission.ts`);
|
|
5
|
+
* · 请求的 `settings.permissions.allow`(整份原样投 `TaskSpec.allowedTools`,`task-settings.ts`);
|
|
6
|
+
* · 运维的 `APPROVAL_GRANT`(整份投 `RunnerDeps.allowedTools`,`config.ts` 启动校验)。
|
|
7
|
+
*
|
|
8
|
+
* 本仓在这些表上只剩下面三道判加名字层一问(内容形、`*`、写路径形引擎怎么读就怎么算)。**名字层四张表同一只判官**(core `judgeRuleToolName`):请求的
|
|
9
|
+
* `settings.permissions.allow` 与 deny / ask 两桶经 `config.ts` `unmatchableToolNamesRefusal`,技能两只清单经 `spec-fields.ts` `skillToolListRefusal`,运维表经
|
|
10
|
+
* `config.ts` `approvalGrantRefusal` —— 三只包装调的都是本文件 {@link retiredRuleEntryOf}(退役名 / 短名 / 协议名;不问文法),句子一组。
|
|
11
|
+
* ① **形**({@link ruleEntryListShapeOk}):字符串数组。引擎逐条迭代;一个裸字符串会被逐字符读成几条(deny 侧 = 意图静默落空)。
|
|
12
|
+
* 本谓词不带条数帽(引擎的 `screenAllowedToolsValue` 只判形);条数帽是另一只函数 {@link allowLayerEntryCountIssue}(`MAX_ALLOW_LAYER_ENTRIES`,
|
|
13
|
+
* 请求 allow 桶、技能两只清单、运维表四张表同一只、同一句)。
|
|
14
|
+
* ④ **退役名 / 无协议前缀的短名**({@link retiredRuleEntryOf}):判官是 core 导出的 `judgeRuleToolName`(请求三桶、会话规则写口、准备阶段的名审计
|
|
15
|
+
* 同一只、同一份空名册)。引擎的 allow 层对这一族今天照编一个永不命中的名字、不通告 —— 7.116.0 的运维表对它拒启,本仓不许在同一张表上
|
|
16
|
+
* 由响亮变静默;翻转条件与 `config.ts` 的 `allow-layer` 消费者同一句(引擎对非现行名出通告之后退役)。
|
|
17
|
+
* ② **协议名的对端段铸不出 / 缺段 / 对端段写通配**({@link unreachableRuleEntryOf}):判官是 `namespaced-rule-name.ts` 的那一只、取名口是
|
|
18
|
+
* `ruleEntryToolName`(配置表与会话规则写口同读)。引擎今天对这一族照编、一条都不命中、不通告 —— 等引擎的判官自己给这个判词时整只退役。
|
|
19
|
+
* ③ **引擎读成空的条目**({@link inertRuleEntriesOf},判据 = 引擎包根 `compileAllowList(entries, behavior).inert`,原因词是引擎的
|
|
20
|
+
* `ALLOW_ENTRY_INERT_REASONS`):deny 侧(技能的 `disallowedTools`)⇒ 拒 —— 一条以为拒了其实什么也不拒的条目是安全轴上的静默放宽;
|
|
21
|
+
* 运维的 allow 表 ⇒ 拒启(运维自己的配置,写错了当场改)。**请求的 allow 侧不拒**:引擎在开跑时发
|
|
22
|
+
* `permission.allowed_tool_not_granted`(受众 = 用户)—— 终端会把用户设置里的 allow 整份投过来,一条今天读成空的写法不能把整条请求打回。
|
|
23
|
+
*/
|
|
24
|
+
import { type AllowEntryInertReason, type RuleToolNameVerdict } from "@sema-agent/core";
|
|
25
|
+
import { type UnreachableNamespacedRuleName } from "./namespaced-rule-name.js";
|
|
26
|
+
export declare function presentRuleEntry(entry: string): string;
|
|
27
|
+
/** ① 形:字符串数组(空串也是字符串 —— 引擎把它读成 `grammar` 并通告 / 拒,见 ③)。 */
|
|
28
|
+
export declare const ruleEntryListShapeOk: (v: unknown) => v is readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* ①b **条数上限** —— 引擎要逐条播种的条目表(allow 层的三个本仓成员:请求的 `settings.permissions.allow`、请求自带技能的两只清单、运维的
|
|
31
|
+
* `APPROVAL_GRANT`)**一只常量、一只函数**。理由是引擎的准备开销,不是本仓的语义判据:引擎每次准备都逐条播种这些表(`AllowEntryTable.seed`
|
|
32
|
+
* 与技能授予表的 `hold` 都对已有条目线性查重 ⇒ 整表平方级、同步执行;core 7.42.0 上 1 万条约 2 s、2 万条约 6 s,本仓亲测)。不设帽 = 一份
|
|
33
|
+
* 8 MiB 以内的请求体能把整个进程的事件循环卡住几十秒。与 deny / ask 两桶的帽(`permission-rule-list.ts` `MAX_PERMISSION_RULES_PER_LIST`,
|
|
34
|
+
* 编译口的整表帽)是两件事、两个名字,数字恰好相同。
|
|
35
|
+
* 翻转条件:上游把播种改成集合查重之后,按新的开销重估这个数(或整只退役)。
|
|
36
|
+
*/
|
|
37
|
+
export declare const MAX_ALLOW_LAYER_ENTRIES = 1000;
|
|
38
|
+
/** 超帽的拒句(不截:截掉的尾项读起来像「在生效」)—— 句子的唯一铸点(请求 allow 桶的 wire 拒体按字段重铸也调它)。 */
|
|
39
|
+
export declare function describeAllowLayerOverCap(field: string, count: number): string;
|
|
40
|
+
/** 一张条目表超了 {@link MAX_ALLOW_LAYER_ENTRIES} ⇒ 拒句 + 机读两数;没超 ⇒ `undefined`。四张表同调这一只。 */
|
|
41
|
+
export declare function allowLayerEntryCountIssue(field: string, list: readonly unknown[]): {
|
|
42
|
+
readonly message: string;
|
|
43
|
+
readonly count: number;
|
|
44
|
+
readonly cap: number;
|
|
45
|
+
} | undefined;
|
|
46
|
+
/** 一张表里第一条「协议名写得出、却永远匹配不到」的条目。 */
|
|
47
|
+
export interface UnreachableRuleEntry {
|
|
48
|
+
readonly entry: string;
|
|
49
|
+
readonly verdict: UnreachableNamespacedRuleName;
|
|
50
|
+
}
|
|
51
|
+
/** ② 第一条够不着任何挂载工具的协议名条目(都够得着 ⇒ `undefined`)。纯字符串运算。 */
|
|
52
|
+
export declare function unreachableRuleEntryOf(list: readonly string[]): UnreachableRuleEntry | undefined;
|
|
53
|
+
/** ② 的句子(`<field> names "<entry>", which can never match a tool (…)`;后半与配置表 / 会话规则写口同一只)。 */
|
|
54
|
+
export declare function describeUnreachableRuleEntry(field: string, u: UnreachableRuleEntry): string;
|
|
55
|
+
/** ④ 一张表里第一条退役名 / 无协议前缀短名的条目(都现行 ⇒ `undefined`)。名册恒空:本仓不在退役名下挂自有工具(与 `config.ts` 同注)。 */
|
|
56
|
+
export interface RetiredRuleEntry {
|
|
57
|
+
readonly entry: string;
|
|
58
|
+
readonly verdict: Exclude<RuleToolNameVerdict, {
|
|
59
|
+
kind: "current";
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
export declare function retiredRuleEntryOf(list: readonly string[]): RetiredRuleEntry | undefined;
|
|
63
|
+
/** ④ 的句子:退役名给 core 的现役指引;短名枚举协议表前缀(与 `config.ts` 对请求三桶的说法同口径)。 */
|
|
64
|
+
export declare function describeRetiredRuleEntry(field: string, r: RetiredRuleEntry): string;
|
|
65
|
+
/** 一条引擎读成空的条目与引擎给的原因词。 */
|
|
66
|
+
export interface InertRuleEntry {
|
|
67
|
+
readonly entry: string;
|
|
68
|
+
readonly reason: AllowEntryInertReason;
|
|
69
|
+
}
|
|
70
|
+
/** ③ 引擎按 `behavior` 读这张表时读成空的条目(引擎的整表读法:allow 表里的 `*` 让整表读成空;deny 表里的 `*` 只读空它自己)。 */
|
|
71
|
+
export declare function inertRuleEntriesOf(list: readonly string[], behavior: "allow" | "deny"): readonly InertRuleEntry[];
|
|
72
|
+
/**
|
|
73
|
+
* 原因词 → 「怎么写」(闭集穷举:引擎加一个原因词 ⇒ 编译红)。句子只说这一条为什么什么也不做、换成哪种写法;原因词本身原样带在句子里。
|
|
74
|
+
*/
|
|
75
|
+
export declare function inertRuleEntryGuidance(reason: AllowEntryInertReason): string;
|
|
76
|
+
/** ③ 的句子(`<field> entry "<e>" reads as nothing (<reason>): <怎么写>`;多条用分号连)。 */
|
|
77
|
+
export declare function describeInertRuleEntries(field: string, inert: readonly InertRuleEntry[]): string;
|
|
78
|
+
/** 提交体里技能所在的层(命中句与排空读数点名用):顶层 `skills[]` / 子代 `agents[].skills[]`。 */
|
|
79
|
+
export type SkillListLayer = "skills" | "agents.skills";
|
|
80
|
+
/** 一份提交体里第一处「技能的拒侧清单里有引擎读成空的条目」:在哪一层、拒句是什么。 */
|
|
81
|
+
export interface SkillDenyInertEntry {
|
|
82
|
+
readonly layer: SkillListLayer;
|
|
83
|
+
readonly message: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* ③ 的拒侧在**提交体**上的判(顶层 `skills[]` 与子代 `agents[].skills[]` 两层;第一处命中即答)。
|
|
87
|
+
*
|
|
88
|
+
* 🔴 **调用点两处**:`resolveSpec`(`boot/resolve-spec.ts`,**拒点** —— 新提交与五条续跑腿都经过它,所以「新提交会被拒的体,续跑重放时同样拒」
|
|
89
|
+
* 是结构性的)与 `http/checkpoint-ctx.ts` `storedBodyReplayRefusal`(存量体重放前的**预判**,五个续跑问点同调 —— 含 SLA 拒扫的跳过臂与终态 steer
|
|
90
|
+
* 铸唤醒卡之前,这两处不经 `resolveSpec`)。提交腿 ⇒ 400 `request.field_invalid`;续跑腿 ⇒ 409 `resume_blocked_by_policy`(`blockedBy: request.field_invalid`,
|
|
91
|
+
* 句子同一句,铸点 `checkpoint-ctx.ts` `resumeBlockedByPolicyAnswer`),拒在认领之前,行 / 卡 / ctx 不动。
|
|
92
|
+
* 为什么续跑也要判:引擎 7.41 的规则解析先去首尾空白(`"Bash "` 是一条有效的拒),7.42 逐字读(同一条读成 `grammar`,什么也不拒)——
|
|
93
|
+
* 7.116.x 上停驻、体里带这种条目的行,不判就是升级后那条拒无声消失。随包排空判据 `skill-deny-inert`(`scripts/drain-check-local.mjs` 调本函数)。
|
|
94
|
+
* 判据就是引擎的读法(`compileAllowList(entries, "deny").inert`),本函数只负责走提交体的两层、给出句子与层。
|
|
95
|
+
* 翻转条件:引擎今天对读成空的拒侧条目只发通告、照常激活技能;上游已认改成 fail-closed(读不出的拒侧条目所守的那次激活不发生)。
|
|
96
|
+
* 那一版到货后,本仓这一处与引擎的拒是同一件事的两处 —— 届时重估是否退役本判(连同随包排空判据 `skill-deny-inert`)。
|
|
97
|
+
*/
|
|
98
|
+
export declare function skillDenyInertEntryOf(body: unknown): SkillDenyInertEntry | undefined;
|
|
99
|
+
//# sourceMappingURL=rule-entry-list.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { compileAllowList, judgeRuleToolName, PROTOCOL_TABLE } from "@sema-agent/core";
|
|
2
|
+
import { describeUnreachableNamespacedRuleName, ruleEntryToolName, unreachableNamespacedRuleNameOf } from "./namespaced-rule-name.js";
|
|
3
|
+
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
4
|
+
const PRESENTED_ENTRY_MAX = 160;
|
|
5
|
+
export function presentRuleEntry(entry) {
|
|
6
|
+
const clean = entry.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
7
|
+
return clean.length <= PRESENTED_ENTRY_MAX ? clean : `${clean.slice(0, PRESENTED_ENTRY_MAX)}…(${entry.length} chars)`;
|
|
8
|
+
}
|
|
9
|
+
export const ruleEntryListShapeOk = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
10
|
+
export const MAX_ALLOW_LAYER_ENTRIES = 1_000;
|
|
11
|
+
export function describeAllowLayerOverCap(field, count) {
|
|
12
|
+
return (`${field} has ${count} entries — at most ${MAX_ALLOW_LAYER_ENTRIES} are accepted. Refused rather than truncated: a truncated list silently drops the entries past the cap. ` +
|
|
13
|
+
`Merge entries into broader ones (mcp__<server>__*, Bash(<prefix>:*), Read(//<dir>/**)) or trim the list`);
|
|
14
|
+
}
|
|
15
|
+
export function allowLayerEntryCountIssue(field, list) {
|
|
16
|
+
if (list.length <= MAX_ALLOW_LAYER_ENTRIES)
|
|
17
|
+
return undefined;
|
|
18
|
+
return { message: describeAllowLayerOverCap(field, list.length), count: list.length, cap: MAX_ALLOW_LAYER_ENTRIES };
|
|
19
|
+
}
|
|
20
|
+
export function unreachableRuleEntryOf(list) {
|
|
21
|
+
for (const entry of list) {
|
|
22
|
+
const verdict = unreachableNamespacedRuleNameOf(ruleEntryToolName(entry));
|
|
23
|
+
if (verdict !== undefined)
|
|
24
|
+
return { entry, verdict };
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
export function describeUnreachableRuleEntry(field, u) {
|
|
29
|
+
return `${field} names "${presentRuleEntry(u.entry)}", which can never match a tool (${describeUnreachableNamespacedRuleName(u.verdict)})`;
|
|
30
|
+
}
|
|
31
|
+
export function retiredRuleEntryOf(list) {
|
|
32
|
+
for (const entry of list) {
|
|
33
|
+
const verdict = judgeRuleToolName(ruleEntryToolName(entry), new Set());
|
|
34
|
+
if (verdict.kind !== "current")
|
|
35
|
+
return { entry, verdict };
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
export function describeRetiredRuleEntry(field, r) {
|
|
40
|
+
const why = r.verdict.kind === "retired"
|
|
41
|
+
? r.verdict.guidance
|
|
42
|
+
: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentRuleEntry(r.entry)}`).join(" or ")}`;
|
|
43
|
+
return `${field} names "${presentRuleEntry(r.entry)}", which can never match a tool (${why})`;
|
|
44
|
+
}
|
|
45
|
+
export function inertRuleEntriesOf(list, behavior) {
|
|
46
|
+
return compileAllowList(list, behavior).inert;
|
|
47
|
+
}
|
|
48
|
+
export function inertRuleEntryGuidance(reason) {
|
|
49
|
+
switch (reason) {
|
|
50
|
+
case "wildcard_all":
|
|
51
|
+
return "`*` names no tool (on an allow list it voids the whole list) — name the tools instead";
|
|
52
|
+
case "namespace_wildcard":
|
|
53
|
+
return "a server segment does not take a wildcard (`mcp__*`) — name the server: mcp__<server> or mcp__<server>__*";
|
|
54
|
+
case "path_pattern":
|
|
55
|
+
return "a write-path or file-pattern entry has no reading on this side — name the tool, or use Read(//<dir>/**) for a directory a Read may cover";
|
|
56
|
+
case "domain":
|
|
57
|
+
return "a domain entry has no reading on this side — name the tool";
|
|
58
|
+
case "content_unsupported":
|
|
59
|
+
return "this tool takes no content form here — name the tool itself, or use Bash(<command>) / Bash(<prefix>:*) for a shell command";
|
|
60
|
+
case "grammar":
|
|
61
|
+
return "the entry is not read as written (surrounding whitespace, an empty or unbalanced tool name) — write the tool name exactly, with no padding";
|
|
62
|
+
default: {
|
|
63
|
+
const unhandled = reason;
|
|
64
|
+
throw new Error(`unhandled allow-entry inert reason: ${String(unhandled)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function describeInertRuleEntries(field, inert) {
|
|
69
|
+
return inert.map((i) => `${field} entry "${presentRuleEntry(i.entry)}" reads as nothing (${i.reason}): ${inertRuleEntryGuidance(i.reason)}`).join("; ");
|
|
70
|
+
}
|
|
71
|
+
const isPlainRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
72
|
+
function skillDenyInertSentence(skill) {
|
|
73
|
+
if (!isPlainRecord(skill))
|
|
74
|
+
return undefined;
|
|
75
|
+
const deny = skill["disallowedTools"];
|
|
76
|
+
if (!ruleEntryListShapeOk(deny))
|
|
77
|
+
return undefined;
|
|
78
|
+
const inert = inertRuleEntriesOf(deny, "deny");
|
|
79
|
+
if (inert.length === 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
return `${describeInertRuleEntries("skill.disallowedTools", inert)} — a deny entry that reads as nothing refuses nothing, so it is refused here rather than taken as in effect`;
|
|
82
|
+
}
|
|
83
|
+
export function skillDenyInertEntryOf(body) {
|
|
84
|
+
if (!isPlainRecord(body))
|
|
85
|
+
return undefined;
|
|
86
|
+
const skills = body["skills"];
|
|
87
|
+
if (Array.isArray(skills)) {
|
|
88
|
+
for (const skill of skills) {
|
|
89
|
+
const sentence = skillDenyInertSentence(skill);
|
|
90
|
+
if (sentence !== undefined)
|
|
91
|
+
return { layer: "skills", message: sentence };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const agents = body["agents"];
|
|
95
|
+
if (Array.isArray(agents)) {
|
|
96
|
+
for (let i = 0; i < agents.length; i++) {
|
|
97
|
+
const agent = agents[i];
|
|
98
|
+
const agentSkills = isPlainRecord(agent) ? agent["skills"] : undefined;
|
|
99
|
+
if (!Array.isArray(agentSkills))
|
|
100
|
+
continue;
|
|
101
|
+
for (let j = 0; j < agentSkills.length; j++) {
|
|
102
|
+
const sentence = skillDenyInertSentence(agentSkills[j]);
|
|
103
|
+
if (sentence !== undefined)
|
|
104
|
+
return { layer: "agents.skills", message: `agents[${i}]: skills[${j}]: ${sentence}` };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=rule-entry-list.js.map
|
package/dist/run-local.d.ts
CHANGED
|
@@ -132,7 +132,7 @@ export interface LocalApproverIo {
|
|
|
132
132
|
* 🔴 S-694 起本席也服**三张审批名单**产的 ask(审批基线取 live 席,见 {@link RUN_LOCAL_APPROVAL_SEAT}):
|
|
133
133
|
* `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ 策略 `ask` ⇒ 落到这里。`AskRequest` 不带是哪条声明产的
|
|
134
134
|
* ask(core 把 `matchedAskRule` 刻意留在闸内、不上请求),但带这一问的**类**(`classes`,core 7.37.0 起)。
|
|
135
|
-
* 🔴 无 TTY 拒句的出路**按这一问的类说话**({@link NO_TTY_ASK_CLASS_ROW} 一张闭集表:每类 ⇒ 一种出路)
|
|
135
|
+
* 🔴 无 TTY 拒句的出路**按这一问的类说话**({@link NO_TTY_ASK_CLASS_ROW} 一张闭集表:每类 ⇒ 一种出路):普通问与没人表态的服务器工具调用指 `APPROVAL_GRANT`(allow 层的部署成员清的就是这两类),
|
|
136
136
|
* 写下的 ask 名单指名单旋钮(`APPROVAL_DENY` 也点名 —— 它当场拒、不经本席),部署强制要人答的问(`operator_forced`)列「one of」能铸它的来源、shell 地板(`shell_floor`)另一句只列铸它的旋钮,其余各类**不点任何旋钮**、只说去终端答;
|
|
137
137
|
* 类缺席 ⇒ 说实话(「引擎没有说明这一问的类」),不回落成全量旋钮清单(那句对多数类是错的出路)。
|
|
138
138
|
*
|
|
@@ -161,7 +161,9 @@ type NoTtyRemedy = NoTtySpokenRemedy | "terminal_only";
|
|
|
161
161
|
/**
|
|
162
162
|
* 一类问 ⇒ 这条腿上它的出路。**穷尽表**:core 的 `ASK_CLASSES` 加一类 ⇒ 这里 tsc 红(`Record<AskClass, …>` 少一键),新类必须在这里表态,
|
|
163
163
|
* 不会悄悄落成「没有出路」。本仓知道而 core 不知道的只有一件:**哪只部署旋钮会产哪一类问**(逐类的产地见各行):
|
|
164
|
-
* · `unstated`:没有任何层表态的服务器工具调用(core 7.40.0)——
|
|
164
|
+
* · `unstated`:没有任何层表态的服务器工具调用(core 7.40.0)—— 运维写一次的地方是 `APPROVAL_GRANT`(`run-local` 没有请求体,写不了 `settings.permissions.allow`;
|
|
165
|
+
* 7.117.0 / core 7.42.0 起它是 allow 层的部署成员,经部署姿态座席族落在本腿的每只 Runner 上);
|
|
166
|
+
* · `ordinary`:普通问(内置工具的写门、shell 的普通检查等)—— 7.117.0 起同样由 `APPROVAL_GRANT` 清(allow 层只清这两类),出路同上;
|
|
165
167
|
* · `ask_rule`:写下的 ask 规则 —— `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO`(`operatorAsk`)与治理层的 commandPolicy ask 名单(同一只铸点);请求体的
|
|
166
168
|
* `settings.permissions.ask` 到不了这条腿(本文件不 import 任何请求受理 / `task-settings`,`run-local` 的 spec 自铸);
|
|
167
169
|
* · `operator_forced`:部署**强制**要人答的问 —— 能铸它的来源(真 Runner × `applyLocalGovernance` 亲读类集,读数在 `test/run-local.test.ts` S-1100 ⑨ 各格):`APPROVAL_NEVER_AUTO`
|
package/dist/run-local.js
CHANGED
|
@@ -157,12 +157,12 @@ export const NO_TTY_ASK_CLASS_ROW = {
|
|
|
157
157
|
artifact_shared_read: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
158
158
|
mode: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
159
159
|
unstated: { remedy: "grant", autoBudgetReleases: false },
|
|
160
|
-
ordinary: { remedy: "
|
|
160
|
+
ordinary: { remedy: "grant", autoBudgetReleases: false },
|
|
161
161
|
requires_user_interaction: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
162
162
|
};
|
|
163
163
|
const NO_TTY_REMEDY_SENTENCE = {
|
|
164
|
-
grant: () => `
|
|
165
|
-
`(
|
|
164
|
+
grant: () => `Nothing on the allow layer covers this call: the operator can clear this kind of question once with APPROVAL_GRANT ` +
|
|
165
|
+
`(a tool name, mcp__<server>__* / a2a__<peer>__*, Bash(<prefix>:*), or Read(//<dir>/**)).`,
|
|
166
166
|
approval_lists: (budget) => `The gate comes from the approval lists: APPROVAL_REQUIRE / APPROVAL_NEVER_AUTO (APPROVAL_DENY refuses outright without asking); ` +
|
|
167
167
|
`a separate .env for this CLI keeps a server's lists off it.` +
|
|
168
168
|
(budget ? ` APPROVAL_AUTO_BUDGET auto-approves that many APPROVAL_REQUIRE calls.` : ""),
|
package/dist/runs.js
CHANGED
|
@@ -82,6 +82,7 @@ const PREPARE_REFUSAL_HTTP_STATUS = {
|
|
|
82
82
|
"config.invalid_checkpoint_store": 400,
|
|
83
83
|
"config.skill_listing_budget_invalid": 400,
|
|
84
84
|
"config.user_invoked_skill_unknown": 400,
|
|
85
|
+
"config.image_invalid": 400,
|
|
85
86
|
"config.memory_capture_unsupported": 409,
|
|
86
87
|
};
|
|
87
88
|
export function resumeAtHttpStatus(result) {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
27
27
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
28
28
|
import { type GovernanceAskMarks } from "./governance-ask-marks.js";
|
|
29
|
+
import { type GovernanceOverrides } from "./governance-overrides.js";
|
|
29
30
|
/** 运维 autonomy 四档的**闭集**(同上)。`config.ts` 的 `parseAutonomy` 也从这里读 —— 四档词表全仓一份。 */
|
|
30
31
|
export declare const AUTONOMY_MODES: ["read-only", "ask", "plan", "auto"];
|
|
31
32
|
/** A per-command governance rule (registry `runtime.commandPolicy[]`) —— **型也从契约派生**:
|
|
@@ -97,19 +98,6 @@ export declare function validateCommandRules(rules: CommandRule[]): string[];
|
|
|
97
98
|
* 闭集本身则由契约 zod 派生 ⇒ 契约加一个判词,`RANK` 与桶分配的穷尽 switch 双双**编译红**。
|
|
98
99
|
*/
|
|
99
100
|
export declare function compileCommandPolicyLayers(rules: CommandRule[] | undefined): ToolPolicy[];
|
|
100
|
-
/** 运维 allow 规则表的出处名 = 它的 env 名(盖进每条规则;编不出来 / 不合文法时拒启句的主语)。 */
|
|
101
|
-
export declare const APPROVAL_GRANT_SOURCE = "APPROVAL_GRANT";
|
|
102
|
-
/**
|
|
103
|
-
* 7.115.0 —— 运维的 allow 规则(`APPROVAL_GRANT`)编成治理层的**叶**(空 / 缺席 ⇒ `[]`,本层一片叶都不铸,折叠逐字与修前同)。
|
|
104
|
-
*
|
|
105
|
-
* 编译口是**请求腿 `settings.permissions.allow` 的同一只**(`permission-rule-list.ts` `createRuleListPolicy`,`behavior: "allow"`):命中 = core 带
|
|
106
|
-
* `decisionReason: "rule"` 的放行(折叠把这句表态带到终局,引擎据此不再为没人表态的服务器工具调用出 `unstated` 卡);未命中 = 裸 allow =
|
|
107
|
-
* 不表态。它是一片**不收紧**的叶 —— 与 commandPolicy / 守卫集同住一个折叠点,只因为三者是同一个「部署说的话」:折叠 deny > ask > allow,
|
|
108
|
-
* 同折叠里任何一片叶的 deny / ask、`tightenTaskSpec` 之下的审批基线、之上的请求 settings 与会话规则,照旧压过它。
|
|
109
|
-
*
|
|
110
|
-
* 启动校验(`config.ts` `approvalGrantRefusal`)试编的也是本函数 —— 拒启判据与运行期编译是同一只,不会出现「启动收下、任务里编不出来」。
|
|
111
|
-
*/
|
|
112
|
-
export declare function compileApprovalGrantLayers(grant: readonly string[] | undefined): ToolPolicy[];
|
|
113
101
|
/**
|
|
114
102
|
* Expand an autonomy mode into TIGHTEN-ONLY `TaskSpec` safety overrides (the primitives — TRAP #2):
|
|
115
103
|
* - `read-only` / `plan` → `handsReadOnly: true` **+ `writeFace: "roots"`** (read + propose, never mutate; the
|
|
@@ -133,7 +121,7 @@ export declare function compileApprovalGrantLayers(grant: readonly string[] | un
|
|
|
133
121
|
* 与 `ask → shellGate:"always"` 同姿势:这一档**自己就是**一次「围栏在」的表态,所以无表态的请求上
|
|
134
122
|
* 它照样把键写出来(不是「只在有 open 时才压」——那又是一条判据)。
|
|
135
123
|
*/
|
|
136
|
-
export declare function autonomyOverrides(autonomy: Autonomy | undefined):
|
|
124
|
+
export declare function autonomyOverrides(autonomy: Autonomy | undefined): GovernanceOverrides;
|
|
137
125
|
/**
|
|
138
126
|
* [ref]:**部署级**判据 —— 本部署的治理层自己是否把 shellGate 抬到 `"always"`。
|
|
139
127
|
*
|
|
@@ -216,17 +204,15 @@ export declare function governanceOriginOf(gate: {
|
|
|
216
204
|
};
|
|
217
205
|
} | undefined, governance: GovernancePosture | undefined): true | undefined;
|
|
218
206
|
/**
|
|
219
|
-
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate + the guard set
|
|
220
|
-
* `APPROVAL_GRANT` allow rules) onto a base
|
|
207
|
+
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate + the guard set) onto a base
|
|
221
208
|
* `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
|
|
222
209
|
* override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
|
|
223
210
|
* `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
|
|
224
211
|
* override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
|
|
225
212
|
* weakening the gate. Returns `base` untouched when there is nothing to apply.
|
|
226
213
|
*
|
|
227
|
-
* 7.
|
|
228
|
-
*
|
|
229
|
-
* 编译口与请求腿的 allow 桶同一只({@link compileApprovalGrantLayers})。
|
|
214
|
+
* 7.117.0(core 7.42.0):运维的 allow 表 `APPROVAL_GRANT` 不再是这里的一片叶 —— 策略只说 deny / ask,allow 规则住在引擎 allow 层的部署席
|
|
215
|
+
* `RunnerDeps.allowedTools`(`boot/runner-deps.ts` 的部署姿态座席族,每只 Runner 同一席)。本函数因此只剩收紧。
|
|
230
216
|
*
|
|
231
217
|
* [ref]([ref] server 分单②):`MANUAL_MODE_SHELL_GATE` 从 settings 折叠搬到这里**无条件施加**。旧家
|
|
232
218
|
* (task-settings deriveSettingsPolicy 的 default/auto/acceptEdits 三臂)让一个**部署级 tighten-only 旋钮
|
|
@@ -260,8 +246,6 @@ export declare function applyRuntimeGovernance(base: TaskSpec, governance: {
|
|
|
260
246
|
/** 守卫集的**叶**(`deployment-governance.ts` `createDeploymentGovernanceInputs` 预铸;S-958 起是叶的列表,不是一只预折的策略)。
|
|
261
247
|
* 空列表与缺席同义。 */
|
|
262
248
|
sensitivePathLayers?: readonly ToolPolicy[];
|
|
263
|
-
/** 7.115.0:运维的 allow 规则(`APPROVAL_GRANT` 原串;本函数经 {@link compileApprovalGrantLayers} 编成叶)。空列表与缺席同义。 */
|
|
264
|
-
approvalGrant?: readonly string[];
|
|
265
249
|
/** [ref]/[ref] `governanceForced` 的标记表(缺省 = **当前 run 腿的 ALS 表**,见
|
|
266
250
|
* `governance-ask-marks.ts` 顶注「作用域」段)。注入口只为测试与将来的多实例形。 */
|
|
267
251
|
askMarks?: GovernanceAskMarks;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, DEFAULT_SUBAGENT_TOOL_NAME, parseLeadingCommandName,
|
|
1
|
+
import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, DEFAULT_SUBAGENT_TOOL_NAME, parseLeadingCommandName, wrapToolPolicy, } from "@sema-agent/core";
|
|
2
2
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
3
3
|
import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
|
|
4
|
+
import { tightenGoverned } from "./governance-overrides.js";
|
|
4
5
|
import { createDeploymentAuthoredPolicy, operatorAsk } from "./operator-ask.js";
|
|
5
|
-
import { createRuleListPolicy } from "./permission-rule-list.js";
|
|
6
6
|
import { heldRuleName } from "./tool-rule-names.js";
|
|
7
7
|
const COMMAND_RULE_SHAPE = GovernanceConfig.shape.commandPolicy.unwrap().element.shape;
|
|
8
8
|
const COMMAND_DECISIONS = COMMAND_RULE_SHAPE.decision.options;
|
|
@@ -101,10 +101,6 @@ export function compileCommandPolicyLayers(rules) {
|
|
|
101
101
|
}
|
|
102
102
|
return policies;
|
|
103
103
|
}
|
|
104
|
-
export const APPROVAL_GRANT_SOURCE = "APPROVAL_GRANT";
|
|
105
|
-
export function compileApprovalGrantLayers(grant) {
|
|
106
|
-
return grant !== undefined && grant.length > 0 ? [createRuleListPolicy(grant, "allow", APPROVAL_GRANT_SOURCE, undefined)] : [];
|
|
107
|
-
}
|
|
108
104
|
export function autonomyOverrides(autonomy) {
|
|
109
105
|
switch (autonomy) {
|
|
110
106
|
case "read-only":
|
|
@@ -148,7 +144,7 @@ function createGovernanceShellGateMarkPolicy(marksOf, tools = COARSE_SHELL_TOOLS
|
|
|
148
144
|
export function applyRuntimeGovernance(base, governance) {
|
|
149
145
|
const marksOf = () => governance.askMarks ?? currentGovernanceAskMarks();
|
|
150
146
|
const overrides = { ...autonomyOverrides(governance.autonomy) };
|
|
151
|
-
const layers = [...compileCommandPolicyLayers(governance.commandPolicy), ...(governance.sensitivePathLayers ?? [])
|
|
147
|
+
const layers = [...compileCommandPolicyLayers(governance.commandPolicy), ...(governance.sensitivePathLayers ?? [])];
|
|
152
148
|
const governancePolicy = layers.length > 0 ? createGovernanceAskMarkingPolicy(combinePolicies(...layers.map(createDeploymentAuthoredPolicy)), marksOf) : undefined;
|
|
153
149
|
if (governancePolicy !== undefined)
|
|
154
150
|
overrides.toolPolicy = governancePolicy;
|
|
@@ -161,6 +157,6 @@ export function applyRuntimeGovernance(base, governance) {
|
|
|
161
157
|
if (Object.keys(overrides).length === 0) {
|
|
162
158
|
return base;
|
|
163
159
|
}
|
|
164
|
-
return
|
|
160
|
+
return tightenGoverned(base, overrides);
|
|
165
161
|
}
|
|
166
162
|
//# sourceMappingURL=runtime-governance.js.map
|
package/dist/spec-fields.d.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* body WITHOUT the HTTP-layer 400 validation, so a malformed value must fall back to a safe default, never throw.
|
|
5
5
|
*/
|
|
6
6
|
import { type AgentDefinition, type TaskSpec } from "@sema-agent/core";
|
|
7
|
-
import { type UnreachableNamespacedRuleName } from "./namespaced-rule-name.js";
|
|
8
7
|
/**
|
|
9
8
|
* E12 — normalize `body.suggestNextPrompts` → `TaskSpec.suggestNextPrompts`. `true` = on with core defaults
|
|
10
9
|
* (count 3, cheap role); an object tunes `count` (clamped 1..10) / `role` (a free string — core validates it against
|
|
@@ -48,31 +47,24 @@ export declare const MAX_TASK_AGENTS = 32;
|
|
|
48
47
|
* deny 规则来源,不是围栏)的**形**谓词。两道技能门(顶层 `body.skills[]` 的 `validateUserSkills`、`agents[].skills[]` 的
|
|
49
48
|
* {@link SKILL_FIELD_SHAPES})**同一只**:一边判一边不判 = 同一个键两种话(S-377 baseDir 吃过的亏)。
|
|
50
49
|
*
|
|
51
|
-
*
|
|
52
|
-
* `B` / `a` / `s` / `h` 四条
|
|
53
|
-
* (`*` / 写路径 / 域名)
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
50
|
+
* 本谓词只判「字符串数组」(core 7.42.0 起与请求的 `settings.permissions.allow`、运维的 `APPROVAL_GRANT` **同一只**:`rule-entry-list.ts` 的
|
|
51
|
+
* `ruleEntryListShapeOk`):引擎直接 `for (const entry of list)` —— 一个裸字符串 `"Bash"` 会被逐字符迭代成 `B` / `a` / `s` / `h` 四条
|
|
52
|
+
* (deny 侧 = 意图静默落空,安全轴上的放宽方向)。条目的**文法**归引擎:允侧读不出的条目(`*` / 写路径 / 域名)引擎在开跑时发
|
|
53
|
+
* `permission.allowed_tool_not_granted`(受众 = 用户);拒侧读成空的条目在 `resolveSpec` 拒(提交 400 / 续跑 409,`rule-entry-list.ts` 的
|
|
54
|
+
* `skillDenyInertEntryOf`,两处调用:提交口拒点 + 存量体重放预判)。形对之后两道门再问 {@link skillToolListRefusal}(协议名的服务器段铸不出 / 缺段 ⇒ 400)——
|
|
55
|
+
* 不是文法,是「写得出却永远不生效」,判据与请求的 allow 桶、运维的 allow 表同一份(`rule-entry-list.ts`)。
|
|
56
|
+
* 条数帽 = 条目表那一只(`rule-entry-list.ts` `allowLayerEntryCountIssue`,准备开销的界 —— 引擎逐条播种是平方级;与请求 allow 桶、运维表同一只),
|
|
57
|
+
* 在 {@link skillToolListRefusal} 里判;无长度帽([ref])。
|
|
57
58
|
*/
|
|
58
|
-
export declare const skillToolListOk: (v: unknown) =>
|
|
59
|
-
/** 技能的两只清单(`allowedTools` = allow 规则来源,`disallowedTools` = deny 规则来源;条目的工具名那一段两只同读)。 */
|
|
60
|
-
declare const SKILL_TOOL_LISTS: readonly ["allowedTools", "disallowedTools"];
|
|
61
|
-
/** 技能清单里一条够不着任何挂载工具的协议名条目。 */
|
|
62
|
-
export interface SkillToolListUnreachable {
|
|
63
|
-
readonly key: (typeof SKILL_TOOL_LISTS)[number];
|
|
64
|
-
readonly entry: string;
|
|
65
|
-
readonly verdict: UnreachableNamespacedRuleName;
|
|
66
|
-
}
|
|
59
|
+
export declare const skillToolListOk: (v: unknown) => v is readonly string[];
|
|
67
60
|
/**
|
|
68
|
-
* 一条技能(形已过两道门的字符串数组判)
|
|
69
|
-
* (
|
|
70
|
-
*
|
|
71
|
-
*
|
|
61
|
+
* 一条技能(形已过两道门的字符串数组判)的两只清单在受理口的拒因 —— 都过 ⇒ `undefined`。两处受理口同调:顶层 `skills[]`
|
|
62
|
+
* (`http/admission.ts` 的 `validateUserSkills`)、子代 `agents[].skills[]`({@link validateTaskAgents})。判据是 `rule-entry-list.ts` 那一份:
|
|
63
|
+
* 任一只清单里「协议名写得出、却永远匹配不到」的条目(与请求的 allow 桶、配置表、会话规则写口同一只判官)。
|
|
64
|
+
* 拒侧读成空的条目**不在这里判**:那一问在 `resolveSpec`(`rule-entry-list.ts` 的 `skillDenyInertEntryOf`;新提交与每条续跑腿都经过那一处)与存量体重放前的
|
|
65
|
+
* 预判(`http/checkpoint-ctx.ts` `storedBodyReplayRefusal`)—— 一只判、两处调用。
|
|
72
66
|
*/
|
|
73
|
-
export declare function
|
|
74
|
-
/** 两处受理口同一句(句子的后半与配置表 / 会话规则写口同一只 `describeUnreachableNamespacedRuleName`)。 */
|
|
75
|
-
export declare function describeUnreachableSkillToolListEntry(u: SkillToolListUnreachable): string;
|
|
67
|
+
export declare function skillToolListRefusal(skill: Record<string, unknown>): string | undefined;
|
|
76
68
|
/** [ref] 单条 agent 定义的形状裁决:null=合法;否则一句可 400 的英文错误(不带索引,调用方自己加)。
|
|
77
69
|
* 白名单外键 fail-loud —— 与 resilience/limits 的「容忍未知键」相反:agents 是能力定义,静默丢字段 =
|
|
78
70
|
* 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。⚠️ 已知残账(core 半场):
|
|
@@ -188,5 +180,4 @@ export declare function normalizeApproachNotice(raw: unknown): false | {
|
|
|
188
180
|
* 全空 ⇒ undefined(spec 不挂 limits 键,byte-compat)。
|
|
189
181
|
*/
|
|
190
182
|
export declare function resolveTaskLimits(bodyLimits: unknown, caps: TaskLimitCaps, envTimeoutSec: number, requirePrincipal: boolean | undefined, big: boolean, warn?: (event: string, fields: Record<string, unknown>) => void): TaskSpec["limits"];
|
|
191
|
-
export {};
|
|
192
183
|
//# sourceMappingURL=spec-fields.d.ts.map
|
package/dist/spec-fields.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isThinkingLevel } from "@sema-agent/core";
|
|
2
2
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
3
3
|
import { isValidCwd } from "./task-cwd.js";
|
|
4
|
-
import {
|
|
4
|
+
import { allowLayerEntryCountIssue, describeRetiredRuleEntry, describeUnreachableRuleEntry, retiredRuleEntryOf, ruleEntryListShapeOk, unreachableRuleEntryOf } from "./rule-entry-list.js";
|
|
5
5
|
export function normalizeSuggestNextPrompts(v) {
|
|
6
6
|
if (v === true)
|
|
7
7
|
return true;
|
|
@@ -66,26 +66,25 @@ const toolList = (v) => Array.isArray(v) &&
|
|
|
66
66
|
v.length <= MAX_AGENT_TOOLS &&
|
|
67
67
|
v.every((x) => typeof x === "string" && x.length > 0 && x.length <= MAX_AGENT_TOOL_NAME_CHARS);
|
|
68
68
|
const plainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
69
|
-
export const skillToolListOk =
|
|
69
|
+
export const skillToolListOk = ruleEntryListShapeOk;
|
|
70
70
|
const SKILL_TOOL_LISTS = ["allowedTools", "disallowedTools"];
|
|
71
|
-
export function
|
|
71
|
+
export function skillToolListRefusal(skill) {
|
|
72
72
|
for (const key of SKILL_TOOL_LISTS) {
|
|
73
73
|
const list = skill[key];
|
|
74
|
-
if (!
|
|
74
|
+
if (!ruleEntryListShapeOk(list))
|
|
75
75
|
continue;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
const over = allowLayerEntryCountIssue(`skill.${key}`, list);
|
|
77
|
+
if (over !== undefined)
|
|
78
|
+
return over.message;
|
|
79
|
+
const retired = retiredRuleEntryOf(list);
|
|
80
|
+
if (retired !== undefined)
|
|
81
|
+
return describeRetiredRuleEntry(`skill.${key}`, retired);
|
|
82
|
+
const unreachable = unreachableRuleEntryOf(list);
|
|
83
|
+
if (unreachable !== undefined)
|
|
84
|
+
return describeUnreachableRuleEntry(`skill.${key}`, unreachable);
|
|
83
85
|
}
|
|
84
86
|
return undefined;
|
|
85
87
|
}
|
|
86
|
-
export function describeUnreachableSkillToolListEntry(u) {
|
|
87
|
-
return `skill.${u.key} names "${u.entry}", which can never match a tool (${describeUnreachableNamespacedRuleName(u.verdict)})`;
|
|
88
|
-
}
|
|
89
88
|
const skillFilesOk = (v) => Array.isArray(v) &&
|
|
90
89
|
v.every((f) => plainObject(f) && typeof f.path === "string" && f.path.length > 0 && typeof f.content === "string");
|
|
91
90
|
const SKILL_FIELD_SHAPES = {
|
|
@@ -197,9 +196,9 @@ export function validateTaskAgents(v) {
|
|
|
197
196
|
const skills = v[i].skills;
|
|
198
197
|
if (Array.isArray(skills)) {
|
|
199
198
|
for (let j = 0; j < skills.length; j++) {
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
return `agents[${i}]: skills[${j}]: ${
|
|
199
|
+
const refusal = skillToolListRefusal(skills[j]);
|
|
200
|
+
if (refusal !== undefined)
|
|
201
|
+
return `agents[${i}]: skills[${j}]: ${refusal}`;
|
|
203
202
|
}
|
|
204
203
|
}
|
|
205
204
|
const name = v[i].name;
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -12,22 +12,21 @@
|
|
|
12
12
|
* idempotent (the same tighten), so no special resume handling is needed (mirrors runtime-governance).
|
|
13
13
|
*
|
|
14
14
|
* 🔒 SECURITY INVARIANTS (SDK settings.ts §3.3) —— **scope: the `settings.permissions` channel this module projects, and only it**:
|
|
15
|
-
* -
|
|
16
|
-
* LOOSEN a stricter base safety field. Through
|
|
17
|
-
* deployment + operator baseline
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `test/skill-grant-operator-knobs.test.ts`.
|
|
15
|
+
* - `deny` / `ask` are folded via `tightenTaskSpec` (deny-wins `combinePolicies`); it THROWS on any override that would
|
|
16
|
+
* LOOSEN a stricter base safety field. Through those two buckets the client can only ADD denies / asks / handsReadOnly on
|
|
17
|
+
* top of the deployment + operator baseline.
|
|
18
|
+
* - `allow` (server 7.117.0, core 7.42.0) is NOT a policy: the list rides WHOLE and verbatim into `TaskSpec.allowedTools`,
|
|
19
|
+
* the request member of the engine's ONE allow layer (beside a deployment's `RunnerDeps.allowedTools`, a person's stored
|
|
20
|
+
* rows, an active skill's `allowed-tools` and a PreToolUse hook's `allow`), read under one predicate. It IS an accepted
|
|
21
|
+
* loosening for this run: an entry clears the `unstated` / `ordinary` questions the engine raises for the calls it covers
|
|
22
|
+
* (the built-in write gate and the shell's ordinary asks included), on hosted / multi-tenant deployments too — no
|
|
23
|
+
* deployment switch, by ruling (the execution lane is the sandbox; the operator's tools are the per-tool knobs). It clears
|
|
24
|
+
* no deny / ask rule (request or operator), no governance / shell-floor / write-protection / plan-mode / probe / mandated
|
|
25
|
+
* ask and no PreToolUse hook's ask, and opens no write containment; an entry that matches and cannot clear is disclosed on
|
|
26
|
+
* the card (`allowRuleShadowed`). An operator who needs a person asked for a given tool regardless uses the per-tool knobs:
|
|
27
|
+
* `APPROVAL_REQUIRE` (an ask rule), `APPROVAL_NEVER_AUTO` (a person, mandated), `MANUAL_MODE_SHELL_GATE=always` (shell).
|
|
28
|
+
* A request-carried skill's `allowedTools` is the same member's twin (same grammar, same predicate, same admission judge
|
|
29
|
+
* `rule-entry-list.ts`). Pinned on the real assembly: `test/allow-layer-operator-knobs.test.ts`.
|
|
31
30
|
* - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收**五**模式
|
|
32
31
|
* (default | acceptEdits | plan | bypassPermissions | auto),bypass/auto 被 HONOR 为 gate-shape 选择
|
|
33
32
|
* ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
|
|
@@ -265,8 +264,9 @@ export interface SettingsPermissionBucketIssue {
|
|
|
265
264
|
}
|
|
266
265
|
/**
|
|
267
266
|
* 三桶的**唯一**判据(纯函数)。`perms` 不是对象 ⇒ 不判(那一形归「settings.permissions must be an object」那道门);桶缺席 / `null`
|
|
268
|
-
* ⇒ 不判(与 `permissions: null` 的「容器缺席」同一约定)。逐桶按 {@link SETTINGS_PERMISSION_BUCKETS}
|
|
269
|
-
* `index`)
|
|
267
|
+
* ⇒ 不判(与 `permissions: null` 的「容器缺席」同一约定)。逐桶按 {@link SETTINGS_PERMISSION_BUCKETS} 的序、按该桶的条目规则:不是数组 ⇒ `bad_entry`
|
|
268
|
+
* (无 `index`);`rule_policy` 桶超帽 ⇒ `over_cap`(先判,拒体有界)、首个非串 / 空串项 ⇒ `bad_entry` + 它的下标;`allow_layer` 桶首个非串项 ⇒
|
|
269
|
+
* `bad_entry` + 它的下标(整表判据 = `ruleEntryListShapeOk`),超帽同样 `over_cap`(帽的理由不同,见段头)。全合法 ⇒ `null`。
|
|
270
270
|
*/
|
|
271
271
|
export declare function settingsPermissionBucketIssue(perms: unknown): SettingsPermissionBucketIssue | null;
|
|
272
272
|
/** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
|