@sema-agent/server 7.89.0 → 7.90.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/dist/boot/fatal-exit.d.ts +3 -0
- package/dist/boot/fatal-exit.js +5 -0
- package/dist/boot/resolve-spec.js +2 -0
- package/dist/brain.js +32 -19
- package/dist/capabilities/memory-notice.d.ts +7 -1
- package/dist/capabilities/memory-notice.js +6 -6
- package/dist/capabilities/team.d.ts +37 -0
- package/dist/config-catalog.js +6 -18
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -5
- package/dist/hooks/hook-llm.js +10 -6
- package/dist/hooks/hook-runner.js +19 -2
- package/dist/http/admission.js +9 -5
- package/dist/http/routes/capabilities.js +2 -2
- package/dist/http/routes/session-sync.js +8 -2
- package/dist/http/routes/sessions.js +16 -14
- package/dist/http/wire-types.d.ts +12 -0
- package/dist/main.js +2 -1
- package/dist/model-route-url.d.ts +8 -0
- package/dist/model-route-url.js +17 -8
- package/dist/plugins/memory-embedder.d.ts +0 -6
- package/dist/plugins/memory-embedder.js +10 -16
- package/dist/request-key-closure.js +2 -1
- package/dist/runtime-governance.d.ts +16 -7
- package/dist/runtime-governance.js +27 -7
- package/dist/session-policy-wire.d.ts +103 -0
- package/dist/session-policy-wire.js +66 -0
- package/dist/task-settings.d.ts +12 -2
- package/dist/task-settings.js +7 -5
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/project.d.ts +7 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/redact.d.ts +96 -11
- package/dist/trace/redact.js +65 -10
- package/package.json +2 -2
|
@@ -3,6 +3,14 @@ export declare const GATEWAY_URL_USERINFO_CODE = "config.gateway_url_userinfo";
|
|
|
3
3
|
/** 同族第二码(S-483 座⑨):这一位根本不是一个层级式绝对 URL。三根键共用一码,与上面那只同纪律
|
|
4
4
|
* (码按**规则**命名,不按键名 —— 键名已经逐字在拒因文案里点过了)。 */
|
|
5
5
|
export declare const MODEL_ROUTE_URL_INVALID_CODE = "config.model_route_url_invalid";
|
|
6
|
+
/**
|
|
7
|
+
* 🔴 **第三条臂的机读码(S-514;core 7.24.2 / [ref] 提货)——请求根带 `#fragment`**。
|
|
8
|
+
*
|
|
9
|
+
* 为什么与前两条各自一码:运维要按码行动,而三条臂的**出路不同**(补全 URL / 把凭据搬去旋钮 / 删掉 `#`
|
|
10
|
+
* 之后的一切)。`fetch` **从不把 fragment 发上线**,所以这一形此前是**静默**打根自己的路径、端点后缀整段
|
|
11
|
+
* 落进被丢弃的那一半 —— 请求发得出去、也可能 200,只是打错了地方而一个字不说。
|
|
12
|
+
*/
|
|
13
|
+
export declare const MODEL_ROUTE_URL_FRAGMENT_CODE = "config.model_route_url_fragment";
|
|
6
14
|
/**
|
|
7
15
|
* 模型路由 base URL 的拒因(`undefined` = 这一位合法)—— **这三根键的唯一判官**。
|
|
8
16
|
*
|
package/dist/model-route-url.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildDisplaySafeUrl, isHierarchicalUrl } from "./trace/redact.js";
|
|
2
2
|
export const GATEWAY_URL_USERINFO_CODE = "config.gateway_url_userinfo";
|
|
3
3
|
export const MODEL_ROUTE_URL_INVALID_CODE = "config.model_route_url_invalid";
|
|
4
|
+
export const MODEL_ROUTE_URL_FRAGMENT_CODE = "config.model_route_url_fragment";
|
|
4
5
|
export function modelRouteUrlRefusal(envKey, raw, credentialKnob) {
|
|
5
6
|
if (raw === "")
|
|
6
7
|
return undefined;
|
|
@@ -12,13 +13,21 @@ export function modelRouteUrlRefusal(envKey, raw, credentialKnob) {
|
|
|
12
13
|
`Set ${envKey} to a full scheme://host URL (and put the credential in ${credentialKnob}, never in the URL).`);
|
|
13
14
|
}
|
|
14
15
|
const u = new URL(raw);
|
|
15
|
-
if (u.username
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
if (u.username !== "" || u.password !== "")
|
|
17
|
+
return (`env ${envKey} carries credentials in the URL userinfo (${buildDisplaySafeUrl(raw)}) [${GATEWAY_URL_USERINFO_CODE}] — ` +
|
|
18
|
+
"WHATWG forbids constructing a fetch Request from a URL with credentials, so EVERY model call on this route " +
|
|
19
|
+
"fails deterministically at request-construction time (it used to look like a slow network flake: the error was " +
|
|
20
|
+
"retried for ~3 minutes before surfacing). " +
|
|
21
|
+
`Put the credential in ${credentialKnob} (or the matching header knob) instead of the URL, and keep the URL to ` +
|
|
22
|
+
"scheme/host/port/path.");
|
|
23
|
+
if (u.hash !== "") {
|
|
24
|
+
return (`env ${envKey} carries a URL fragment (${buildDisplaySafeUrl(raw)}#…) [${MODEL_ROUTE_URL_FRAGMENT_CODE}] — ` +
|
|
25
|
+
"a fragment is never sent on the wire, so this route would silently target the root's own path and every " +
|
|
26
|
+
"endpoint suffix (/chat/completions, /v1/messages) would land inside the discarded part: the request goes out, " +
|
|
27
|
+
"may even return 200, and points somewhere else without saying so. " +
|
|
28
|
+
`Drop everything from the "#" on in ${envKey}. A per-deployment QUERY is fully supported and rides every ` +
|
|
29
|
+
"request (a gateway's `?api-version=…`); a fragment cannot be.");
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
23
32
|
}
|
|
24
33
|
//# sourceMappingURL=model-route-url.js.map
|
|
@@ -26,12 +26,6 @@ export interface OpenAiCompatEmbedderOptions extends MemoryEmbedderConfig {
|
|
|
26
26
|
* URL 合法性门,只在运行期炸(而运行期的炸法就是上面那条「写入失败/检索退档」的静默病)。
|
|
27
27
|
*/
|
|
28
28
|
export declare function embeddingsUrl(endpoint: string): string;
|
|
29
|
-
/**
|
|
30
|
-
* 诊断面用的**脱敏** URL(codex 复审 F4,已核真):端点里可能带 userinfo(`https://user:pass@h/v1`)
|
|
31
|
-
* 或把凭据放 query(Azure 的 `api-key=`),而错误文本会进日志、进 PatchReport 的 conflict reason。
|
|
32
|
-
* 主机与路径保留(诊断价值全在这里),userinfo 抹掉、query 值一律换成 `***`(键名保留,便于认形)。
|
|
33
|
-
*/
|
|
34
|
-
export declare function redactEndpointForLog(raw: string): string;
|
|
35
29
|
/** 造一个 OpenAI 兼容的 `PgEmbedder`(工厂命名律:返回带行为的对象 ⇒ `create*`)。 */
|
|
36
30
|
export declare function createOpenAiCompatEmbedder(opts: OpenAiCompatEmbedderOptions): PgEmbedder;
|
|
37
31
|
/**
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { joinRequestUrl } from "@sema-agent/core";
|
|
1
2
|
import { z } from "zod";
|
|
3
|
+
import { buildDisplaySafeRefusal, buildDisplaySafeUrl } from "../trace/redact.js";
|
|
2
4
|
const EmbeddingsResponse = z.object({
|
|
3
5
|
data: z.array(z.object({ embedding: z.array(z.number()) })).min(1),
|
|
4
6
|
});
|
|
5
7
|
export function embeddingsUrl(endpoint) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const path = URL.canParse(endpoint) ? new URL(endpoint).pathname.replace(/\/+$/, "") : "";
|
|
9
|
+
try {
|
|
10
|
+
return joinRequestUrl(endpoint, decodeUnreservedEscapes(path).endsWith("/embeddings") ? "" : "/embeddings");
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
throw new Error(buildDisplaySafeRefusal(e instanceof Error ? e.message : String(e), [endpoint]), { cause: undefined });
|
|
14
|
+
}
|
|
11
15
|
}
|
|
12
16
|
function decodeUnreservedEscapes(s) {
|
|
13
17
|
return s.replace(/%([0-9A-Fa-f]{2})/g, (whole, hex) => {
|
|
@@ -15,16 +19,6 @@ function decodeUnreservedEscapes(s) {
|
|
|
15
19
|
return /[A-Za-z0-9\-._~]/.test(c) ? c : whole;
|
|
16
20
|
});
|
|
17
21
|
}
|
|
18
|
-
export function redactEndpointForLog(raw) {
|
|
19
|
-
if (!URL.canParse(raw))
|
|
20
|
-
return "<invalid-url>";
|
|
21
|
-
const u = new URL(raw);
|
|
22
|
-
u.username = "";
|
|
23
|
-
u.password = "";
|
|
24
|
-
for (const k of [...u.searchParams.keys()])
|
|
25
|
-
u.searchParams.set(k, "***");
|
|
26
|
-
return u.toString();
|
|
27
|
-
}
|
|
28
22
|
function credentialsInQuery(endpoint) {
|
|
29
23
|
if (!URL.canParse(endpoint))
|
|
30
24
|
return [];
|
|
@@ -32,7 +26,7 @@ function credentialsInQuery(endpoint) {
|
|
|
32
26
|
}
|
|
33
27
|
export function createOpenAiCompatEmbedder(opts) {
|
|
34
28
|
const url = embeddingsUrl(opts.endpoint);
|
|
35
|
-
const shownUrl =
|
|
29
|
+
const shownUrl = buildDisplaySafeUrl(url);
|
|
36
30
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
37
31
|
const { model, dimensions, timeoutMs, apiKey, onFailure } = opts;
|
|
38
32
|
const secrets = [...(apiKey !== undefined ? [apiKey] : []), ...credentialsInQuery(opts.endpoint)];
|
|
@@ -20,7 +20,8 @@ export function keyClosureClause(names, label) {
|
|
|
20
20
|
const TASK_BODY_DECLARED_KEYS = [
|
|
21
21
|
"a2aPeers", "acceptPartialRestore", "additionalDirectories", "additionalReadDirectories", "agents",
|
|
22
22
|
"appendSystemPrompt", "attachmentIds", "attachments", "capabilitiesNeeded", "cascade", "clientContext",
|
|
23
|
-
"compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "enableFork",
|
|
23
|
+
"compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "enableFork",
|
|
24
|
+
"excludeAllTools", "excludeTools",
|
|
24
25
|
"finalVerification", "forwardSubagentEvents", "images", "interactiveTools", "jobId", "limits", "maxCostUsd",
|
|
25
26
|
"maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries",
|
|
26
27
|
"outputSchema", "permissionMode", "projectId", "promptProfile", "reasoningEffort", "requireExistingSession",
|
|
@@ -24,12 +24,14 @@
|
|
|
24
24
|
* name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
|
|
25
25
|
*/
|
|
26
26
|
import { type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
27
|
+
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
27
28
|
import { type GovernanceAskMarks } from "./governance-ask-marks.js";
|
|
28
|
-
/**
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/** 运维 autonomy 四档的**闭集**(同上)。`config.ts` 的 `parseAutonomy` 也从这里读 —— 四档词表全仓一份。 */
|
|
30
|
+
export declare const AUTONOMY_MODES: ["read-only", "ask", "plan", "auto"];
|
|
31
|
+
/** A per-command governance rule (registry `runtime.commandPolicy[]`) —— **型也从契约派生**:
|
|
32
|
+
* 契约给 `command` 加一条 refine / 给 `decision` 加一个判词,本仓的型当场跟上(或在下面的
|
|
33
|
+
* `RANK` / 穷尽 switch 上编译红),而不是靠人记得把手写联合改一遍。 */
|
|
34
|
+
export type CommandRule = NonNullable<GovernanceConfig["commandPolicy"]>[number];
|
|
33
35
|
/** True when `name` names a DELEGATION tool — the subagent tool(CC-187 canonical `"Agent"`,core
|
|
34
36
|
* `DEFAULT_SUBAGENT_TOOL_NAME`)。5.0.0 [ref]:折叠面退役,RAW 比对 core 单源常量——旧名("Task")
|
|
35
37
|
* 在 core roster 层响亮 miss,不再需要归一;单源常量本身就防了当年 1.202 改名时裸字符串失配的
|
|
@@ -50,8 +52,8 @@ export declare function isDelegationToolName(name: string): boolean;
|
|
|
50
52
|
export declare function stripDelegationTools<T extends {
|
|
51
53
|
name: string;
|
|
52
54
|
}>(tools: readonly T[]): T[];
|
|
53
|
-
/** The operator autonomy mode (registry `
|
|
54
|
-
export type Autonomy = "
|
|
55
|
+
/** The operator autonomy mode (registry `governance.autonomy`) —— 契约派生(见 {@link AUTONOMY_MODES})。 */
|
|
56
|
+
export type Autonomy = NonNullable<GovernanceConfig["autonomy"]>;
|
|
55
57
|
/** 导出给 `test/coarse-shell-tools-mirror.test.ts` 的同源钉用(见那里的顶注)。 */
|
|
56
58
|
export declare const COARSE_SHELL_TOOLS_MIRROR: readonly string[];
|
|
57
59
|
/** Validate `commandPolicy` rules — the shape (array of {command, decision}), the `command` (a bare argv[0] name,
|
|
@@ -81,6 +83,13 @@ export declare function validateCommandRules(rules: CommandRule[]): string[];
|
|
|
81
83
|
* Either way the result only ever ADDS asks/denies — composed onto the baseline via `combinePolicies`
|
|
82
84
|
* (deny-wins) by {@link applyRuntimeGovernance}, so it is tighten-only. (Assumes commands are pre-validated by
|
|
83
85
|
* {@link validateCommandRules}; an un-matchable command here is simply inert, never a parse error.)
|
|
86
|
+
*
|
|
87
|
+
* 🔴 **S-496:词表外的 `decision` 折 `deny`,不折 `ask`。** {@link validateCommandRules} 的头注写下的那条
|
|
88
|
+
* 后果(「域外的 `decision` 穿过本函数的三元链落到 ASK,静默把一条 intended deny 弱化掉」)此前只靠
|
|
89
|
+
* **调用方记得先校验**挡着 —— 而本函数在 `resolve-spec` 里是逐请求现调的,`config.commandPolicy` 的来路
|
|
90
|
+
* (center hot apply / 本地 `config.d/governance.json` / LKG / 测试直塞)不止一条。现在闭集在本函数**内部**
|
|
91
|
+
* 收口:`VALID_DECISIONS` 之外的值一律折最严的 `deny`(fail-closed,[ref] 的运行期 miss 臂,下方自陈),
|
|
92
|
+
* 闭集本身则由契约 zod 派生 ⇒ 契约加一个判词,`RANK` 与桶分配的穷尽 switch 双双**编译红**。
|
|
84
93
|
*/
|
|
85
94
|
export declare function compileCommandPolicy(rules: CommandRule[] | undefined): ToolPolicy | undefined;
|
|
86
95
|
/**
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { combinePolicies, COARSE_SHELL_TOOLS as CORE_COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, DEFAULT_SUBAGENT_TOOL_NAME, parseLeadingCommandName, tightenTaskSpec, } from "@sema-agent/core";
|
|
2
|
-
import { SAFETY_MERGE_SPEC } from "@sema-agent/settings-schema";
|
|
2
|
+
import { GovernanceConfig, SAFETY_MERGE_SPEC } from "@sema-agent/settings-schema";
|
|
3
3
|
import { currentGovernanceAskMarks } from "./governance-ask-marks.js";
|
|
4
4
|
import { operatorAsk } from "./operator-ask.js";
|
|
5
|
+
const COMMAND_RULE_SHAPE = GovernanceConfig.shape.commandPolicy.unwrap().element.shape;
|
|
6
|
+
const COMMAND_DECISIONS = COMMAND_RULE_SHAPE.decision.options;
|
|
7
|
+
export const AUTONOMY_MODES = GovernanceConfig.shape.autonomy.unwrap().options;
|
|
5
8
|
export function isDelegationToolName(name) {
|
|
6
9
|
return name === DEFAULT_SUBAGENT_TOOL_NAME;
|
|
7
10
|
}
|
|
@@ -30,7 +33,9 @@ function createCommandAskListPolicy(askCommands, tools = COARSE_SHELL_TOOLS) {
|
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
35
|
const VALID_COMMAND_NAME = /^[A-Za-z0-9._+-]+$/;
|
|
33
|
-
|
|
36
|
+
function isCommandDecision(raw) {
|
|
37
|
+
return typeof raw === "string" && COMMAND_DECISIONS.includes(raw);
|
|
38
|
+
}
|
|
34
39
|
export function validateCommandRules(rules) {
|
|
35
40
|
const errors = [];
|
|
36
41
|
if (!Array.isArray(rules))
|
|
@@ -45,8 +50,8 @@ export function validateCommandRules(rules) {
|
|
|
45
50
|
errors.push(`commandPolicy command "${String(command)}" is not a bare argv[0] name — core matches the EXACT command name (no glob/path/operators). ` +
|
|
46
51
|
`Use e.g. "git" not "git*"; to gate sub-commands or pipes, deny the interpreter ("sh"/"bash") + rely on the sandbox.`);
|
|
47
52
|
}
|
|
48
|
-
if (
|
|
49
|
-
errors.push(`commandPolicy command "${String(command)}" has invalid decision "${String(decision)}" — must be one of
|
|
53
|
+
if (!isCommandDecision(decision)) {
|
|
54
|
+
errors.push(`commandPolicy command "${String(command)}" has invalid decision "${String(decision)}" — must be one of ${COMMAND_DECISIONS.join(" | ")}`);
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
return errors;
|
|
@@ -57,15 +62,30 @@ export function compileCommandPolicy(rules) {
|
|
|
57
62
|
const RANK = { deny: 3, ask: 2, allow: 1 };
|
|
58
63
|
const strictest = new Map();
|
|
59
64
|
for (const r of rules) {
|
|
65
|
+
const decision = isCommandDecision(r.decision) ? r.decision : "deny";
|
|
60
66
|
const prev = strictest.get(r.command);
|
|
61
|
-
if (prev === undefined
|
|
62
|
-
strictest.set(r.command,
|
|
67
|
+
if (RANK[decision] > (prev === undefined ? 0 : RANK[prev]))
|
|
68
|
+
strictest.set(r.command, decision);
|
|
63
69
|
}
|
|
64
70
|
const deny = [];
|
|
65
71
|
const allow = [];
|
|
66
72
|
const ask = [];
|
|
67
73
|
for (const [command, decision] of strictest) {
|
|
68
|
-
(decision
|
|
74
|
+
switch (decision) {
|
|
75
|
+
case "deny":
|
|
76
|
+
deny.push(command);
|
|
77
|
+
break;
|
|
78
|
+
case "allow":
|
|
79
|
+
allow.push(command);
|
|
80
|
+
break;
|
|
81
|
+
case "ask":
|
|
82
|
+
ask.push(command);
|
|
83
|
+
break;
|
|
84
|
+
default: {
|
|
85
|
+
const unhandled = decision;
|
|
86
|
+
throw new Error(`commandPolicy decision "${String(unhandled)}" has no compilation arm`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
69
89
|
}
|
|
70
90
|
const policies = [];
|
|
71
91
|
if (allow.length > 0) {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SessionPermissionRules` 的 **wire 形读者** —— 两条写入腿(`PUT /v1/sessions/:id/policy` 与
|
|
3
|
+
* `POST /v1/sessions/:id/sync/import` 的 `policy` 面)**唯一**的形判据。
|
|
4
|
+
*
|
|
5
|
+
* ── 为什么是一只共享读者(S-496 / [ref],取证 P4 + S-498)───────────────────────────────────────────
|
|
6
|
+
* 改前两条腿各自为政:
|
|
7
|
+
* · PUT 腿在 `http/routes/sessions.ts` 里**手抄**五桶键名(`["toolAllow","toolDeny","allowDirs",
|
|
8
|
+
* "commandAllow","commandDeny"] as const`),core 的 `SessionPermissionRules` 加第六桶 ⇒ 那一桶在
|
|
9
|
+
* PUT 上被**静默丢**、200 回执却说「已生效」;
|
|
10
|
+
* · sync/import 腿(S-498)只判 `Array.isArray(body.policy)` **外层**,逐条记录的 `principal` / `rules`
|
|
11
|
+
* 一个字节都不校验就裸交 `replayPolicyRecords` → `putRules`。坏形走的是**放宽**方向,亲读 core
|
|
12
|
+
* `session-policy-store.js` 两处实现:
|
|
13
|
+
* · `normalizeRules` 对每桶做 `[...rules.toolDeny]` ⇒ 一个**字符串** `"rm"` 被展开成 `["r","m"]`,
|
|
14
|
+
* 本来该拒 `rm` 的 deny 规则变成拒两个不存在的命令名 = 门整条失效;
|
|
15
|
+
* · `allowDirs` 走 `.map(...)` ⇒ 字符串上没有 `.map` ⇒ **TypeError → 500**(路由头注 `:300-302`
|
|
16
|
+
* 自陈「Malformed shape → 400(never a downstream TypeError)」,这一半没守);
|
|
17
|
+
* · `loosenReasons` 的每一臂都从 `prior` 出发,**prior 为空时恒回空集** ⇒ tighten-only 门对一条
|
|
18
|
+
* 全新的会话放行任何内容,坏形因此连「收紧门」这一层都碰不到。
|
|
19
|
+
* ⇒ 类修:一只读者,键集由 `keyof SessionPermissionRules` **派生**(下方 `RULE_FIELD_PRESENCE` 的
|
|
20
|
+
* `satisfies Record<keyof SessionPermissionRules, true>`:core 加桶 ⇒ 缺键**编译红**;core 删桶 ⇒
|
|
21
|
+
* 多键**编译红**),两条腿同调。
|
|
22
|
+
*
|
|
23
|
+
* ── 文案也在这里(codex 一轮追加:两条腿各铸一份句子 = 第二份手抄)───────────────────────────────
|
|
24
|
+
* 初版把 wire 文案留在两个路由各自铸,理由是错误文案冻结门(`test/api-error-text-freeze.test.ts`)锚的是
|
|
25
|
+
* `src/http/` 桶里的 `sendError` 站点。codex 一轮指出那等于**把同一句话写了两遍**(`sessions.ts` 与
|
|
26
|
+
* `session-sync.ts` 各一份),这正是本批要消灭的形。改法照本仓既有成案(S-405 / [ref] / [ref] 三处
|
|
27
|
+
* 「常量锚」):句子收进本文件**单一属主**,冻结职责搬到冻结门的**常量锚格** —— 那一格直接断言这两只
|
|
28
|
+
* 渲染器对每一种判词的输出字节,且对判词闭集穷尽(加一种判词 ⇒ 那一格编译红)。文案一个字节未改。
|
|
29
|
+
*/
|
|
30
|
+
import type { SessionPermissionRules, SessionRulesRecord } from "@sema-agent/core";
|
|
31
|
+
/** core `SessionPermissionRules` 的桶名全集(派生,不是手抄)。 */
|
|
32
|
+
export declare const SESSION_PERMISSION_RULE_FIELDS: readonly (keyof SessionPermissionRules)[];
|
|
33
|
+
/** 一条规则体的形问题。判词而非文案(见文件头注的「文案属主」一段)。 */
|
|
34
|
+
export type SessionPermissionRulesProblem =
|
|
35
|
+
/** 整体不是一只普通对象(`null` / 数组 / 标量 / 缺席)。 */
|
|
36
|
+
{
|
|
37
|
+
kind: "not-an-object";
|
|
38
|
+
}
|
|
39
|
+
/** 某一桶在场但不是 `string[]`(字符串 / 数字 / 混杂数组都在此)。 */
|
|
40
|
+
| {
|
|
41
|
+
kind: "field-not-string-array";
|
|
42
|
+
field: keyof SessionPermissionRules;
|
|
43
|
+
};
|
|
44
|
+
/** {@link readSessionPermissionRules} 的读数:坏形给判词,好形给**已收窄**的规则体(调用方零 cast)。 */
|
|
45
|
+
export type SessionPermissionRulesRead = {
|
|
46
|
+
problem: SessionPermissionRulesProblem;
|
|
47
|
+
rules?: undefined;
|
|
48
|
+
} | {
|
|
49
|
+
problem?: undefined;
|
|
50
|
+
rules: SessionPermissionRules;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* 读一只 wire 上的 `SessionPermissionRules`:形不对给判词,形对给**只含五桶**的规则体。
|
|
54
|
+
*
|
|
55
|
+
* 认识面窄得刻意:**只判形**,不判内容(桶内字符串的 pattern 文法、路径是否可达、tighten-only 是否成立
|
|
56
|
+
* 都不在这里 —— 前两者归 core 的 rule 文法与 `normalizeRules`,后者归 `putRules` 的收紧门)。
|
|
57
|
+
* 未知键**照旧忽略**(不是新的拒因):core 的 `normalizeRules` 本来就只搬这五桶,本读者与它同口径。
|
|
58
|
+
*/
|
|
59
|
+
export declare function readSessionPermissionRules(raw: unknown): SessionPermissionRulesRead;
|
|
60
|
+
/** 一条 `SessionRulesRecord` 的形问题(`policy[]` 的元素级;桶级问题原样转载在 `rules` 位)。 */
|
|
61
|
+
export type SessionRulesRecordProblem =
|
|
62
|
+
/** `policy` 本身不是数组。 */
|
|
63
|
+
{
|
|
64
|
+
kind: "not-an-array";
|
|
65
|
+
}
|
|
66
|
+
/** 第 `index` 条记录不是一只普通对象。 */
|
|
67
|
+
| {
|
|
68
|
+
kind: "record-not-an-object";
|
|
69
|
+
index: number;
|
|
70
|
+
}
|
|
71
|
+
/** 第 `index` 条的 `principal` 在场但不是字符串(**缺席**才是「会话级默认行」的合法写法 —— 两个店的
|
|
72
|
+
* `listBySession` 对无 principal 的行都产 `undefined`,JSON 序列化后即缺席,所以 `null` 不在受理面)。 */
|
|
73
|
+
| {
|
|
74
|
+
kind: "principal-not-a-string";
|
|
75
|
+
index: number;
|
|
76
|
+
}
|
|
77
|
+
/** 第 `index` 条的 `rules` 形不对(转载桶级判词)。 */
|
|
78
|
+
| {
|
|
79
|
+
kind: "rules";
|
|
80
|
+
index: number;
|
|
81
|
+
problem: SessionPermissionRulesProblem;
|
|
82
|
+
};
|
|
83
|
+
/** {@link readSessionRulesRecords} 的读数。 */
|
|
84
|
+
export type SessionRulesRecordsRead = {
|
|
85
|
+
problem: SessionRulesRecordProblem;
|
|
86
|
+
records?: undefined;
|
|
87
|
+
} | {
|
|
88
|
+
problem?: undefined;
|
|
89
|
+
records: SessionRulesRecord[];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 读 sync/import 的 `policy` 面(`SessionRulesRecord[]`)。好形返回**已收窄**的记录数组,坏形给判词。
|
|
93
|
+
*
|
|
94
|
+
* `rev`:记录里带的 `rev` 是**导出端的**乐观锁读数,`putRules` 侧一进门就 `stripRev` 掉(`sameRulesContent`
|
|
95
|
+
* 也比的是去 rev 的字节形),所以它在本读者里只是过境位 —— 非数字即当缺席(不是拒因:一个坏 rev 改变不了
|
|
96
|
+
* 任何一次写入的结果,拒它只会把合法同步砖死)。
|
|
97
|
+
*/
|
|
98
|
+
export declare function readSessionRulesRecords(raw: unknown): SessionRulesRecordsRead;
|
|
99
|
+
/** 桶级判词 → 人话。闭集穷尽:{@link SessionPermissionRulesProblem} 加一种判词 ⇒ 本 switch 编译红。 */
|
|
100
|
+
export declare function describeSessionPermissionRulesProblem(p: SessionPermissionRulesProblem): string;
|
|
101
|
+
/** `policy[]` 判词 → 人话(sync/import 腿在它前面拼 `policy: `)。闭集穷尽同上。 */
|
|
102
|
+
export declare function describeSessionRulesRecordProblem(p: SessionRulesRecordProblem): string;
|
|
103
|
+
//# sourceMappingURL=session-policy-wire.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const RULE_FIELD_PRESENCE = {
|
|
2
|
+
toolAllow: true,
|
|
3
|
+
toolDeny: true,
|
|
4
|
+
allowDirs: true,
|
|
5
|
+
commandAllow: true,
|
|
6
|
+
commandDeny: true,
|
|
7
|
+
};
|
|
8
|
+
export const SESSION_PERMISSION_RULE_FIELDS = Object.keys(RULE_FIELD_PRESENCE);
|
|
9
|
+
export function readSessionPermissionRules(raw) {
|
|
10
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
11
|
+
return { problem: { kind: "not-an-object" } };
|
|
12
|
+
const src = raw;
|
|
13
|
+
const rules = {};
|
|
14
|
+
for (const field of SESSION_PERMISSION_RULE_FIELDS) {
|
|
15
|
+
const v = src[field];
|
|
16
|
+
if (v === undefined)
|
|
17
|
+
continue;
|
|
18
|
+
if (!Array.isArray(v) || !v.every((x) => typeof x === "string"))
|
|
19
|
+
return { problem: { kind: "field-not-string-array", field } };
|
|
20
|
+
rules[field] = v;
|
|
21
|
+
}
|
|
22
|
+
return { rules };
|
|
23
|
+
}
|
|
24
|
+
export function readSessionRulesRecords(raw) {
|
|
25
|
+
if (!Array.isArray(raw))
|
|
26
|
+
return { problem: { kind: "not-an-array" } };
|
|
27
|
+
const records = [];
|
|
28
|
+
for (let index = 0; index < raw.length; index++) {
|
|
29
|
+
const e = raw[index];
|
|
30
|
+
if (e === null || typeof e !== "object" || Array.isArray(e))
|
|
31
|
+
return { problem: { kind: "record-not-an-object", index } };
|
|
32
|
+
const { principal, rules, rev } = e;
|
|
33
|
+
if (principal !== undefined && typeof principal !== "string")
|
|
34
|
+
return { problem: { kind: "principal-not-a-string", index } };
|
|
35
|
+
const read = readSessionPermissionRules(rules);
|
|
36
|
+
if (read.problem !== undefined)
|
|
37
|
+
return { problem: { kind: "rules", index, problem: read.problem } };
|
|
38
|
+
const innerRev = rules?.rev;
|
|
39
|
+
const carried = typeof innerRev === "number" ? innerRev : typeof rev === "number" ? rev : 0;
|
|
40
|
+
records.push({ principal, rules: { ...read.rules, rev: carried } });
|
|
41
|
+
}
|
|
42
|
+
return { records };
|
|
43
|
+
}
|
|
44
|
+
export function describeSessionPermissionRulesProblem(p) {
|
|
45
|
+
switch (p.kind) {
|
|
46
|
+
case "not-an-object": return "rules must be a SessionPermissionRules object";
|
|
47
|
+
case "field-not-string-array": return `${p.field} must be an array of strings`;
|
|
48
|
+
default: {
|
|
49
|
+
const unhandled = p;
|
|
50
|
+
throw new Error(`unhandled SessionPermissionRules problem: ${JSON.stringify(unhandled)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function describeSessionRulesRecordProblem(p) {
|
|
55
|
+
switch (p.kind) {
|
|
56
|
+
case "not-an-array": return "must be an array of {principal, rules} records";
|
|
57
|
+
case "record-not-an-object": return `[${p.index}] must be a {principal, rules} record`;
|
|
58
|
+
case "principal-not-a-string": return `[${p.index}].principal must be a string when present (omit it for the session-wide row)`;
|
|
59
|
+
case "rules": return `[${p.index}].rules: ${describeSessionPermissionRulesProblem(p.problem)}`;
|
|
60
|
+
default: {
|
|
61
|
+
const unhandled = p;
|
|
62
|
+
throw new Error(`unhandled SessionRulesRecord problem: ${JSON.stringify(unhandled)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=session-policy-wire.js.map
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -48,7 +48,16 @@ import { type KeyClosureIssue } from "./request-key-closure.js";
|
|
|
48
48
|
* deployment approval/governance baseline is composed OUTSIDE this derive and is untouchable from a settings stamp),
|
|
49
49
|
* and `auto` adds the same ask gate as `default` with core's auto-mode classifier screening the asks upstream
|
|
50
50
|
* (entitlement-gated in core, [ref]②). */
|
|
51
|
-
export
|
|
51
|
+
export declare const SETTINGS_PERMISSION_MODES: readonly ["default", "acceptEdits", "plan", "bypassPermissions", "auto"];
|
|
52
|
+
/** 五模式闭集的型形 —— **从值表派生**(S-496 / [ref]):改前本仓有 **4 份**同一张五词表(本处的手写联合 /
|
|
53
|
+
* 下面 `coercePermissionModeStrict` 的运行期字面量链 / `http/admission.ts` 顶层 `permissionMode` 的比较链 /
|
|
54
|
+
* 同文件 `settings.permissions.defaultMode` 的比较链),外加两句错误文案里各抄一遍词。现在值表一份、
|
|
55
|
+
* 型与文案都从它派生,比较链改调 {@link isSettingsPermissionMode}。 */
|
|
56
|
+
export type SettingsPermissionMode = (typeof SETTINGS_PERMISSION_MODES)[number];
|
|
57
|
+
/** 五词表的 wire 文案形(`"a" | "b" | …`)—— 两条 400 的句子从这里取词,不再各抄一份。 */
|
|
58
|
+
export declare const SETTINGS_PERMISSION_MODE_WORDS: string;
|
|
59
|
+
/** 闭集成员判定(`unknown` 入口用)—— 词表的**唯一**运行期读者:wire 门与 coerce 两族同调。 */
|
|
60
|
+
export declare function isSettingsPermissionMode(raw: unknown): raw is SettingsPermissionMode;
|
|
52
61
|
/**
|
|
53
62
|
* R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends the
|
|
54
63
|
* CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
|
|
@@ -68,7 +77,8 @@ export type SettingsPermissionMode = "default" | "acceptEdits" | "plan" | "bypas
|
|
|
68
77
|
*/
|
|
69
78
|
export declare function coercePermissionMode(raw: unknown): SettingsPermissionMode | undefined;
|
|
70
79
|
/** 五模式闭集的**严格**读法([ref] ④:`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,
|
|
71
|
-
* 不走上面那条给 resume 重放留的 `default` 宽臂)
|
|
80
|
+
* 不走上面那条给 resume 重放留的 `default` 宽臂)。上面的宽读只是本函数外再包一层折叠。
|
|
81
|
+
* S-496:词表单点已上移到 {@link SETTINGS_PERMISSION_MODES},本函数只是它的 `undefined` 形读者。 */
|
|
72
82
|
export declare function coercePermissionModeStrict(raw: unknown): SettingsPermissionMode | undefined;
|
|
73
83
|
/**
|
|
74
84
|
* S-433 —— 一份**被批准的计划**之后,这条会话继续用的 permission mode(`plan_review` 决裁体的
|
package/dist/task-settings.js
CHANGED
|
@@ -3,6 +3,11 @@ import { findUnmatchableToolNames, formatUnmatchableToolNames } from "./config.j
|
|
|
3
3
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
4
4
|
import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "./request-key-closure.js";
|
|
5
5
|
import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
6
|
+
export const SETTINGS_PERMISSION_MODES = ["default", "acceptEdits", "plan", "bypassPermissions", "auto"];
|
|
7
|
+
export const SETTINGS_PERMISSION_MODE_WORDS = SETTINGS_PERMISSION_MODES.map((m) => `"${m}"`).join(" | ");
|
|
8
|
+
export function isSettingsPermissionMode(raw) {
|
|
9
|
+
return typeof raw === "string" && SETTINGS_PERMISSION_MODES.includes(raw);
|
|
10
|
+
}
|
|
6
11
|
export function coercePermissionMode(raw) {
|
|
7
12
|
const strict = coercePermissionModeStrict(raw);
|
|
8
13
|
if (strict !== undefined)
|
|
@@ -12,9 +17,7 @@ export function coercePermissionMode(raw) {
|
|
|
12
17
|
return undefined;
|
|
13
18
|
}
|
|
14
19
|
export function coercePermissionModeStrict(raw) {
|
|
15
|
-
|
|
16
|
-
return raw;
|
|
17
|
-
return undefined;
|
|
20
|
+
return isSettingsPermissionMode(raw) ? raw : undefined;
|
|
18
21
|
}
|
|
19
22
|
export const PLAN_APPROVE_PERMISSION_MODES_AFTER = ["default", "acceptEdits"];
|
|
20
23
|
export function isPlanApprovePermissionModeAfter(raw) {
|
|
@@ -154,8 +157,7 @@ export function parseTaskSettings(raw) {
|
|
|
154
157
|
const allow = cleanStringArray(p.allow);
|
|
155
158
|
const deny = cleanStringArray(p.deny);
|
|
156
159
|
const ask = cleanStringArray(p.ask);
|
|
157
|
-
const
|
|
158
|
-
const defaultMode = mode === "default" || mode === "acceptEdits" || mode === "plan" || mode === "bypassPermissions" || mode === "auto" ? mode : undefined;
|
|
160
|
+
const defaultMode = coercePermissionModeStrict(p.defaultMode);
|
|
159
161
|
const rawKill = p.disableAutoMode;
|
|
160
162
|
const disableAutoMode = rawKill === "disable" || rawKill === true || rawKill === false ? rawKill : undefined;
|
|
161
163
|
if (allow || deny || ask || defaultMode || disableAutoMode !== undefined) {
|
|
@@ -130,8 +130,8 @@ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "seve
|
|
|
130
130
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
|
|
131
131
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
132
132
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
133
|
-
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";
|
|
134
|
-
type TaskSpecUnwired = "
|
|
133
|
+
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" | "excludeAllTools";
|
|
134
|
+
type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
|
|
135
135
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
136
136
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
137
137
|
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";
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -769,6 +769,13 @@ export declare function compactedEventData(ev: {
|
|
|
769
769
|
clampReason?: "budget" | "tolerance";
|
|
770
770
|
tokensAfter?: number;
|
|
771
771
|
triggerTokensBefore?: number;
|
|
772
|
+
/** core 7.24.0 [ref]:引擎**自己**算的结构性净减(pre-messages 的结构和 减去 post-messages 的同一个和,
|
|
773
|
+
* 铸点处 clamp ≥0),三条压缩车道(turn 边界 / prompt-too-long 恢复 / 任务末 settle)同一只
|
|
774
|
+
* `mintCompactedEvent` 出。🔴 **缺席 = post 测量失败,不是 0**:上面那两个旧键(`tokensAfter` /
|
|
775
|
+
* `triggerTokensBefore`)自本版起在 core 的类型上写明是**诊断位、不可相减**(触发闸读的是 usage 锚定
|
|
776
|
+
* 的数、post 恒是结构和 + 固定提示词开销,两者的差混坐标,实测在普通 run 上是负数)。本仓的处置因此
|
|
777
|
+
* 是**三条都逐字透传、一条都不合成**:谁要画上下文表就读 `freedTokens`,读不到就渲染 unknown。 */
|
|
778
|
+
freedTokens?: number;
|
|
772
779
|
durationMs?: number;
|
|
773
780
|
phaseDurations?: {
|
|
774
781
|
prepareMs?: number;
|
package/dist/trace/project.js
CHANGED
|
@@ -656,6 +656,7 @@ export function compactedEventData(ev) {
|
|
|
656
656
|
...(ev.clampReason !== undefined ? { clampReason: ev.clampReason } : {}),
|
|
657
657
|
...(num(ev.tokensAfter) !== undefined ? { tokensAfter: num(ev.tokensAfter) } : {}),
|
|
658
658
|
...(num(ev.triggerTokensBefore) !== undefined ? { triggerTokensBefore: num(ev.triggerTokensBefore) } : {}),
|
|
659
|
+
...(num(ev.freedTokens) !== undefined ? { freedTokens: num(ev.freedTokens) } : {}),
|
|
659
660
|
...(num(ev.durationMs) !== undefined ? { durationMs: num(ev.durationMs) } : {}),
|
|
660
661
|
...(phaseDurations !== undefined ? { phaseDurations } : {}),
|
|
661
662
|
};
|