@sema-agent/server 7.99.0 → 7.100.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 +13 -0
- package/USAGE.md +75 -52
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +74 -1
- package/dist/approval-card.js +41 -8
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +39 -81
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +2 -2
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +12 -13
- package/dist/config-center/apply-effective.d.ts +34 -39
- package/dist/config-center/apply-effective.js +100 -64
- package/dist/config-center/apply-ledger.d.ts +35 -16
- package/dist/config-center/apply-ledger.js +25 -18
- package/dist/config-center/facade.d.ts +11 -5
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/types.d.ts +3 -3
- package/dist/config-types.d.ts +22 -7
- package/dist/cross-session-settings.js +1 -1
- package/dist/host-lsp-manager.d.ts +29 -0
- package/dist/host-lsp-manager.js +14 -0
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +8 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/memory-compliance.js +5 -0
- package/dist/http/routes/memory-origin.js +5 -0
- package/dist/http/routes/sessions.js +6 -0
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-preflight.d.ts +46 -0
- package/dist/memory-layer-preflight.js +121 -0
- package/dist/memory-operator-faces.js +17 -1
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +8 -4
- package/dist/observability/fail-open.js +8 -4
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -0
- package/dist/observability/secret-env-scrub.js +1 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/web-search.d.ts +42 -22
- package/dist/plugins/web-search.js +141 -49
- package/dist/run-local.js +6 -5
- package/dist/runs.js +6 -5
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/server-secret-env.d.ts +4 -2
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +107 -19
- package/dist/tool-approval.js +64 -15
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +3 -3
|
@@ -46,11 +46,24 @@ import type { SqlDialect } from "./sql-driver.js";
|
|
|
46
46
|
*
|
|
47
47
|
* 🔴 **别名是产物的一部分,不是调用点的自由**:MySQL 协议下 `CAST(rules AS CHAR)` 的回报字段名是
|
|
48
48
|
* `CAST(rules AS CHAR)` 而不是 `rules` —— 把别名留给调用点写,就等于给每一处读腿留一个「忘了写别名,
|
|
49
|
-
* 行上于是没有这个键、读回恒 `undefined
|
|
49
|
+
* 行上于是没有这个键、读回恒 `undefined`」的静默洞。别名在这里一次钉死,行的键恒等于列名(成员形
|
|
50
|
+
* {@link JsonColumnMember}:恒等于声明的小写别名)。
|
|
50
51
|
*
|
|
51
52
|
* 🔴 **`CAST` 不影响索引**:投影不在 `WHERE` 里;大体量列多一次文本拷贝,量级与现状同(驱动本来也解一次)。
|
|
52
53
|
*/
|
|
53
|
-
export declare function jsonColumnText(dialect: SqlDialect, column: string): string;
|
|
54
|
+
export declare function jsonColumnText(dialect: SqlDialect, column: string, member?: JsonColumnMember): string;
|
|
55
|
+
/**
|
|
56
|
+
* 一列 JSON 的**一个顶层成员**(NP-3,7.100.0)—— 读腿只要文档里的一棵子树时用它,整只文档不出引擎。
|
|
57
|
+
* · `key` = 成员名(裸标识符;进 MySQL 路径 `'$.<key>'` 与 PG 字面 `'<key>'`,所以只认裸标识符,不转义不猜);
|
|
58
|
+
* · `as` = 行上的键(**小写**裸标识符:PG 把不带引号的别名折成小写,别的拼法会让两方言的行键不同)。
|
|
59
|
+
* 产物仍是**文本**(同一条「按文本投影、恰好解一次」),成员缺席 / 文档不是对象 ⇒ SQL NULL(两引擎同答),
|
|
60
|
+
* 所以读腿用 {@link decodeNullableJsonColumn}:NULL = 「这份文档没有这棵子树」,JSON `null` 字面 = 一个值(交形读者判)。
|
|
61
|
+
* 首个读者:`checkpoint-store-sql.ts` 的 `listPending`(运维队列 / `/v1/approvals/stream` 热读)只取 `pendingAction`。
|
|
62
|
+
*/
|
|
63
|
+
export interface JsonColumnMember {
|
|
64
|
+
readonly key: string;
|
|
65
|
+
readonly as: string;
|
|
66
|
+
}
|
|
54
67
|
/**
|
|
55
68
|
* 同一只投影的**裸表达式**(不带别名)—— 给 `WHERE` / `UPDATE … WHERE` 这种**没有别名位**的地方用。
|
|
56
69
|
*
|
|
@@ -68,7 +81,7 @@ export declare function jsonColumnText(dialect: SqlDialect, column: string): str
|
|
|
68
81
|
* 子树,本函数的插值对它是一个不透明表达式。所以「误用进 SELECT 投影且没给别名」这一形没有机器门 ——
|
|
69
82
|
* 误用的落点是真库上当场的 `SqlJsonColumnError("not-text")` 响亮抛(行上没有那个键 ⇒ `undefined`),不是静默。
|
|
70
83
|
*/
|
|
71
|
-
export declare function jsonColumnTextExpr(dialect: SqlDialect, column: string): string;
|
|
84
|
+
export declare function jsonColumnTextExpr(dialect: SqlDialect, column: string, memberKey?: string): string;
|
|
72
85
|
/**
|
|
73
86
|
* 一次读回失败的**种别**(店层码,**闭集**;运行期词表在这里,类型从它推导 —— 一张词表只有一个写者):
|
|
74
87
|
* · `not-text` —— 拿到的不是串 = **读腿漏了投影**,那是**码的问题不是数据的问题** ⇒ 响亮的程序错,
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
const COLUMN_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
|
-
export function jsonColumnText(dialect, column) {
|
|
4
|
-
|
|
3
|
+
export function jsonColumnText(dialect, column, member) {
|
|
4
|
+
if (member === undefined)
|
|
5
|
+
return `${jsonColumnTextExpr(dialect, column)} AS ${column}`;
|
|
6
|
+
if (!MEMBER_ALIAS_RE.test(member.as)) {
|
|
7
|
+
throw new Error(`jsonColumnText: member alias ${JSON.stringify(member.as)} must be a lower-case bare identifier — PostgreSQL folds unquoted aliases to lower case, so any other spelling would give the two dialects different row keys`);
|
|
8
|
+
}
|
|
9
|
+
return `${jsonColumnTextExpr(dialect, column, member.key)} AS ${member.as}`;
|
|
5
10
|
}
|
|
6
|
-
|
|
11
|
+
const MEMBER_ALIAS_RE = /^[a-z_][a-z0-9_]*$/;
|
|
12
|
+
export function jsonColumnTextExpr(dialect, column, memberKey) {
|
|
7
13
|
if (!COLUMN_NAME_RE.test(column)) {
|
|
8
14
|
throw new Error(`jsonColumnTextExpr: ${JSON.stringify(column)} is not a bare column identifier — refusing to splice it into SQL text`);
|
|
9
15
|
}
|
|
10
|
-
|
|
16
|
+
if (memberKey === undefined)
|
|
17
|
+
return dialect === "pg" ? `${column}::text` : `CAST(${column} AS CHAR)`;
|
|
18
|
+
if (!COLUMN_NAME_RE.test(memberKey)) {
|
|
19
|
+
throw new Error(`jsonColumnTextExpr: member ${JSON.stringify(memberKey)} is not a bare identifier — refusing to splice it into a JSON path`);
|
|
20
|
+
}
|
|
21
|
+
return dialect === "pg" ? `(${column}::jsonb -> '${memberKey}')::text` : `CAST(JSON_EXTRACT(${column}, '$.${memberKey}') AS CHAR)`;
|
|
11
22
|
}
|
|
12
23
|
export const SQL_JSON_COLUMN_PROBLEMS = ["not-text", "not-json", "malformed"];
|
|
13
24
|
export class SqlJsonColumnError extends Error {
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* backend here is what makes `assembleFullBodyTools({ webSearch })` actually mount the WebSearch tool.
|
|
5
5
|
*
|
|
6
6
|
* Multi-provider, picked by `WEB_SEARCH_PROVIDER`:
|
|
7
|
-
* - `brave` — Brave Web Search API (GET, `X-Subscription-Token` header)
|
|
7
|
+
* - `brave` — Brave Web Search API (GET, `X-Subscription-Token` header); with `WEB_SEARCH_ENDPOINT` set to a
|
|
8
|
+
* Brave-compatible gateway's full search URL (e.g. a self-hosted Nólë gateway, S-669) the same leg
|
|
9
|
+
* serves that backend — no new provider word, `capabilities.webSearch.backend` still reads `brave`
|
|
8
10
|
* - `tavily` — Tavily Search API (POST, `Authorization: Bearer`, native include/exclude_domains)
|
|
9
11
|
* - `searxng` — a self-hosted SearXNG instance (GET `?format=json`, no auth)
|
|
10
12
|
*
|
|
@@ -140,20 +142,6 @@ export declare function setWebSearchBadPayloadObserver(fn: ((provider: string) =
|
|
|
140
142
|
* stays captured in this closure — it is never surfaced to the model or the tool args.
|
|
141
143
|
*/
|
|
142
144
|
export declare function createWebSearchBackend(cfg: WebSearchBackendConfig): WebSearchBackend;
|
|
143
|
-
/**
|
|
144
|
-
* `WEB_SEARCH_SEARXNG_PARAMS` / `settings.webSearch.searxngParams` 的解析 —— SearXNG 实例侧参数
|
|
145
|
-
* (`engines=` / `language=` 等)。
|
|
146
|
-
*
|
|
147
|
-
* 🔴 [ref] cli 逐串直证:3.18.0 我加了 `searxngParams` 字段和消费点,**却没开任何配置入口** ——
|
|
148
|
-
* 两条门都没有位置填它,于是我在 [ref] 说的「这条是给你们的」是句空话。单测直接构造
|
|
149
|
-
* `WebSearchBackendConfig` 传进去,**天然跳过了配置入口这一层**,于是「能力在」与「配得进」之间
|
|
150
|
-
* 断开,而两边的绿都是真的。判据:**按消费端实际能填的那个入口验,不是按自己构造的对象验。**
|
|
151
|
-
*
|
|
152
|
-
* 形状:`k=v` 用 `;` 分隔(`engines=bing,duckduckgo;language=zh-CN`)——值里本来就常含逗号,
|
|
153
|
-
* 所以分隔符取 `;` 而不是 `,`。整串解析不出任何一对 ⇒ **返回 undefined,键整个不铸**
|
|
154
|
-
* (不产出空对象:空对象会让下游以为「配了但是空的」,与本仓「缺席就要缺席得干净」同源)。
|
|
155
|
-
*/
|
|
156
|
-
export declare function parseSearxngParams(raw: unknown): Record<string, string> | undefined;
|
|
157
145
|
/**
|
|
158
146
|
* #81④ 探活门控 —— **默认 OFF**,显式开才探。
|
|
159
147
|
*
|
|
@@ -165,14 +153,46 @@ export declare function parseSearxngParams(raw: unknown): Record<string, string>
|
|
|
165
153
|
* 而猜错的方向是「凭空出网」。缺席要缺席得干净,含糊也一样。
|
|
166
154
|
*/
|
|
167
155
|
export declare function shouldProbeWebSearchOnBoot(env?: NodeJS.ProcessEnv): boolean;
|
|
168
|
-
/**
|
|
156
|
+
/** env 载体形错的机读码(拼法随 `config.<族>_invalid` 既有族,如 `config.model_provider_invalid`)。码按**规则**
|
|
157
|
+
* 命名、不按键名 —— 键名逐字在拒因里。 */
|
|
158
|
+
export declare const WEB_SEARCH_CONFIG_INVALID_CODE = "config.web_search_invalid";
|
|
159
|
+
/**
|
|
160
|
+
* `WEB_SEARCH_PROVIDER` 这一格的判官(provider 字段的 env 载体):缺席 / 词表外 ⇒ `undefined`(= 不装配,缺席语义)。
|
|
161
|
+
*
|
|
162
|
+
* 🔴 **读面只问这一只**(codex R1-F2):`config-catalog` 的 `WEB_SEARCH_PROVIDER` 行要的是「装配了哪只后端」这一格,
|
|
163
|
+
* 不是整份配置。首版它调的是 {@link webSearchConfigFromEnv} —— 那只对旁的 `WEB_SEARCH_*` 形错**抛**(拒启);嵌入式
|
|
164
|
+
* `createHttpServer` 不走 stage-07,形错的环境可以活在一只跑着的 server 里,于是读 provider 这一格就把整张目录打成
|
|
165
|
+
* 500。形错拒启归装配点(stage-07 / run-local 调整份解析),读面不替装配点再判一遍。
|
|
166
|
+
*/
|
|
167
|
+
export declare function webSearchProviderFromEnv(env?: NodeJS.ProcessEnv): WebSearchProvider | undefined;
|
|
168
|
+
/**
|
|
169
|
+
* 部署 env ⇒ 后端配置。`WEB_SEARCH_PROVIDER` 缺席 / 词表外 ⇒ `undefined`(WebSearch 不装配,**不**读其余旋钮 ——
|
|
170
|
+
* 不消费的键不判);其余 `WEB_SEARCH_*` 任一形错 ⇒ **抛**(拒启,见 {@link envValue})。
|
|
171
|
+
*/
|
|
169
172
|
export declare function webSearchConfigFromEnv(env?: NodeJS.ProcessEnv): WebSearchBackendConfig | undefined;
|
|
170
173
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
174
|
+
* 每请求 `body.settings.webSearch` 的判决 —— 受理面与装配点**同问这一只**:
|
|
175
|
+
* · `absent` —— 没带(或 `null`)⇒ 用部署后端;
|
|
176
|
+
* · `honored` —— 每个字段形对,且 `provider` 点名了一只本部署会说的后端 ⇒ 这次调用用它;
|
|
177
|
+
* · `malformed` —— 任一字段形错 ⇒ 受理面 400 `request.field_invalid`(`issue` 逐字点名字段)。
|
|
178
|
+
* **`provider` 缺席或不在词表里也是形错**(S-199 主会话改判,codex 验修 V1 [high]):一段 `settings.webSearch` 就是
|
|
179
|
+
* 调用方在点名目的地(`endpoint` / `apiKey` 都挂在它点名的那只后端上),认不出就拒,不静默换成部署后端 —— 改判前
|
|
180
|
+
* 这一形回落部署后端,`{provider:"searx", endpoint:<私有地址>}` 的查询于是发到了部署配的后端。env 腿的同一只
|
|
181
|
+
* {@link isWebSearchProvider} 守卫对**部署**的词表外答「不装配」,那是部署自己的缺席,不是换别人的目的地。
|
|
182
|
+
* 规则集因此只剩三态,没有「形对但不采纳」这一臂。
|
|
183
|
+
*
|
|
184
|
+
* 🔒 采纳与否是**调用方**的事:装配点把整条腿闸在单用户车道(每请求 `endpoint`/`apiKey` 是能力配置;多租上一个
|
|
185
|
+
* 租户把 `searxng` 指向内网地址就是 SSRF,所以多租只认部署 env 的后端)。受理面的形判**不分车道**(与
|
|
186
|
+
* `settings.hooks` 同姿势:形在受理面判,采不采纳在装配点决定)。
|
|
176
187
|
*/
|
|
177
|
-
export
|
|
188
|
+
export type RequestWebSearchVerdict = {
|
|
189
|
+
readonly kind: "absent";
|
|
190
|
+
} | {
|
|
191
|
+
readonly kind: "honored";
|
|
192
|
+
readonly config: WebSearchBackendConfig;
|
|
193
|
+
} | {
|
|
194
|
+
readonly kind: "malformed";
|
|
195
|
+
readonly issue: string;
|
|
196
|
+
};
|
|
197
|
+
export declare function judgeRequestWebSearch(raw: unknown): RequestWebSearchVerdict;
|
|
178
198
|
//# sourceMappingURL=web-search.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createSearxngSearchBackend } from "@sema-agent/core";
|
|
2
|
+
import { isHierarchicalUrl } from "../trace/redact.js";
|
|
2
3
|
export const WEB_SEARCH_PROVIDERS = ["brave", "tavily", "searxng"];
|
|
3
4
|
function isWebSearchProvider(word) {
|
|
4
5
|
return WEB_SEARCH_PROVIDERS.includes(word);
|
|
5
6
|
}
|
|
7
|
+
const providerWord = (raw) => raw.trim().toLowerCase();
|
|
6
8
|
export function projectWebSearchCapability(provider) {
|
|
7
9
|
return provider === undefined ? { backend: "none" } : { backend: provider };
|
|
8
10
|
}
|
|
@@ -26,6 +28,10 @@ function withTimeout(signal, ms) {
|
|
|
26
28
|
async function errorText(res) {
|
|
27
29
|
return (await res.text().catch(() => "")).slice(0, 200);
|
|
28
30
|
}
|
|
31
|
+
async function httpFailure(provider, res) {
|
|
32
|
+
const body = await errorText(res);
|
|
33
|
+
return new Error(`${provider} search failed: HTTP ${res.status}${body ? ` — ${body}` : ""}`);
|
|
34
|
+
}
|
|
29
35
|
let onBadPayload;
|
|
30
36
|
export function setWebSearchBadPayloadObserver(fn) {
|
|
31
37
|
onBadPayload = fn;
|
|
@@ -62,7 +68,7 @@ async function braveSearch(fetchImpl, cfg, query, max, signal) {
|
|
|
62
68
|
url.searchParams.set("count", String(Math.min(20, max)));
|
|
63
69
|
const res = await fetchImpl(url, { headers: { Accept: "application/json", "X-Subscription-Token": cfg.apiKey }, signal });
|
|
64
70
|
if (!res.ok)
|
|
65
|
-
throw
|
|
71
|
+
throw await httpFailure("brave", res);
|
|
66
72
|
const data = (await res.json());
|
|
67
73
|
return normalize(data.web?.results, (r) => ({ title: r.title, url: r.url, snippet: r.description }), max, "brave");
|
|
68
74
|
}
|
|
@@ -81,7 +87,7 @@ async function tavilySearch(fetchImpl, cfg, query, max, signal, opts) {
|
|
|
81
87
|
signal,
|
|
82
88
|
});
|
|
83
89
|
if (!res.ok)
|
|
84
|
-
throw
|
|
90
|
+
throw await httpFailure("tavily", res);
|
|
85
91
|
const data = (await res.json());
|
|
86
92
|
return normalize(data.results, (r) => ({ title: r.title, url: r.url, snippet: r.content }), max, "tavily");
|
|
87
93
|
}
|
|
@@ -132,64 +138,150 @@ export function createWebSearchBackend(cfg) {
|
|
|
132
138
|
};
|
|
133
139
|
return { search, maxResults };
|
|
134
140
|
}
|
|
135
|
-
export function parseSearxngParams(raw) {
|
|
136
|
-
if (typeof raw === "object" && raw !== null) {
|
|
137
|
-
const entries = Object.entries(raw);
|
|
138
|
-
if (entries.length === 0 || entries.some(([, v]) => typeof v !== "string"))
|
|
139
|
-
return undefined;
|
|
140
|
-
return Object.fromEntries(entries);
|
|
141
|
-
}
|
|
142
|
-
if (typeof raw !== "string")
|
|
143
|
-
return undefined;
|
|
144
|
-
const out = {};
|
|
145
|
-
for (const pair of raw.split(";")) {
|
|
146
|
-
const i = pair.indexOf("=");
|
|
147
|
-
if (i <= 0)
|
|
148
|
-
continue;
|
|
149
|
-
const k = pair.slice(0, i).trim();
|
|
150
|
-
const v = pair.slice(i + 1).trim();
|
|
151
|
-
if (k && v)
|
|
152
|
-
out[k] = v;
|
|
153
|
-
}
|
|
154
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
155
|
-
}
|
|
156
141
|
export function shouldProbeWebSearchOnBoot(env = process.env) {
|
|
157
142
|
const raw = env.WEB_SEARCH_PROBE_ON_BOOT?.trim().toLowerCase();
|
|
158
143
|
return raw === "true" || raw === "1";
|
|
159
144
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
145
|
+
const ABSENT = { ok: true };
|
|
146
|
+
const valueOf = (j) => (j.ok ? j.value : undefined);
|
|
147
|
+
const isBlank = (s) => s.trim() === "";
|
|
148
|
+
function judgeText(raw) {
|
|
149
|
+
if (raw === undefined || raw === null)
|
|
150
|
+
return ABSENT;
|
|
151
|
+
if (typeof raw !== "string")
|
|
152
|
+
return { ok: false, issue: "must be a string" };
|
|
153
|
+
return isBlank(raw) ? ABSENT : { ok: true, value: raw };
|
|
154
|
+
}
|
|
155
|
+
const ENDPOINT_FORM = "must be an absolute URL with a scheme and a host, such as https://search.example.com (a bare host or host:port is not one)";
|
|
156
|
+
function judgeEndpoint(raw) {
|
|
157
|
+
if (raw === undefined || raw === null)
|
|
158
|
+
return ABSENT;
|
|
159
|
+
if (typeof raw !== "string")
|
|
160
|
+
return { ok: false, issue: ENDPOINT_FORM };
|
|
161
|
+
if (isBlank(raw))
|
|
162
|
+
return ABSENT;
|
|
163
|
+
return isHierarchicalUrl(raw) ? { ok: true, value: raw } : { ok: false, issue: ENDPOINT_FORM };
|
|
164
|
+
}
|
|
165
|
+
const COUNT_FORM = "must be a number ≥ 1";
|
|
166
|
+
function judgeCount(raw) {
|
|
167
|
+
if (raw === undefined || raw === null)
|
|
168
|
+
return ABSENT;
|
|
169
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw >= 1 ? { ok: true, value: Math.floor(raw) } : { ok: false, issue: COUNT_FORM };
|
|
170
|
+
}
|
|
171
|
+
function envNumber(raw) {
|
|
172
|
+
return raw === undefined || isBlank(raw) ? undefined : Number(raw.trim());
|
|
173
|
+
}
|
|
174
|
+
const SEARXNG_PARAM_NAME = /^[A-Za-z0-9_.-]+$/;
|
|
175
|
+
const SEARXNG_PARAMS_FORM = 'must be an object of parameter name → non-empty string, or a "name=value;name=value" string';
|
|
176
|
+
function judgeSearxngParams(raw) {
|
|
177
|
+
if (raw === undefined || raw === null)
|
|
178
|
+
return ABSENT;
|
|
179
|
+
const pairs = [];
|
|
180
|
+
if (typeof raw === "string") {
|
|
181
|
+
const segments = raw.split(";");
|
|
182
|
+
for (const [i, seg] of segments.entries()) {
|
|
183
|
+
if (isBlank(seg))
|
|
184
|
+
continue;
|
|
185
|
+
const eq = seg.indexOf("=");
|
|
186
|
+
if (eq < 0)
|
|
187
|
+
return { ok: false, issue: `${SEARXNG_PARAMS_FORM} — segment ${i + 1} has no "="` };
|
|
188
|
+
pairs.push([seg.slice(0, eq).trim(), seg.slice(eq + 1).trim()]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
192
|
+
const entries = Object.entries(raw);
|
|
193
|
+
for (const [k, v] of entries) {
|
|
194
|
+
if (typeof v !== "string")
|
|
195
|
+
return { ok: false, issue: `${SEARXNG_PARAMS_FORM} — the value of ${JSON.stringify(k)} is not a string` };
|
|
196
|
+
pairs.push([k, v.trim()]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
return { ok: false, issue: `${SEARXNG_PARAMS_FORM} — got ${Array.isArray(raw) ? "an array" : `a ${typeof raw}`}` };
|
|
201
|
+
}
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
for (const [k, v] of pairs) {
|
|
204
|
+
if (!SEARXNG_PARAM_NAME.test(k))
|
|
205
|
+
return { ok: false, issue: `${SEARXNG_PARAMS_FORM} — ${JSON.stringify(k)} is not a parameter name (letters, digits, "_", "-", ".")` };
|
|
206
|
+
if (v === "")
|
|
207
|
+
return { ok: false, issue: `${SEARXNG_PARAMS_FORM} — ${k} has an empty value` };
|
|
208
|
+
if (seen.has(k))
|
|
209
|
+
return { ok: false, issue: `${SEARXNG_PARAMS_FORM} — ${k} is given more than once` };
|
|
210
|
+
seen.add(k);
|
|
211
|
+
}
|
|
212
|
+
return pairs.length === 0 ? ABSENT : { ok: true, value: Object.fromEntries(pairs) };
|
|
213
|
+
}
|
|
214
|
+
function assembleBackendConfig(provider, f) {
|
|
167
215
|
return {
|
|
168
216
|
provider,
|
|
169
|
-
...(
|
|
170
|
-
...(
|
|
171
|
-
...(searxngParams ? { searxngParams } : {}),
|
|
172
|
-
...(
|
|
173
|
-
...(
|
|
217
|
+
...(f.apiKey !== undefined ? { apiKey: f.apiKey } : {}),
|
|
218
|
+
...(f.endpoint !== undefined ? { endpoint: f.endpoint } : {}),
|
|
219
|
+
...(f.searxngParams !== undefined ? { searxngParams: f.searxngParams } : {}),
|
|
220
|
+
...(f.maxResults !== undefined ? { maxResults: f.maxResults } : {}),
|
|
221
|
+
...(f.timeoutMs !== undefined ? { timeoutMs: f.timeoutMs } : {}),
|
|
174
222
|
};
|
|
175
223
|
}
|
|
176
|
-
export
|
|
177
|
-
|
|
224
|
+
export const WEB_SEARCH_CONFIG_INVALID_CODE = "config.web_search_invalid";
|
|
225
|
+
function envValue(key, j) {
|
|
226
|
+
if (!j.ok) {
|
|
227
|
+
throw new Error(`env ${key} ${j.issue} [${WEB_SEARCH_CONFIG_INVALID_CODE}] — fix it or unset it (an empty value counts as unset). ` +
|
|
228
|
+
"WebSearch reads its WEB_SEARCH_* knobs only when WEB_SEARCH_PROVIDER names a backend, and every knob it reads must be well-formed: " +
|
|
229
|
+
"a malformed value used to be absorbed silently (garbage parameters sent to the search instance, or a quiet fall-back to a default nobody chose).");
|
|
230
|
+
}
|
|
231
|
+
return j.value;
|
|
232
|
+
}
|
|
233
|
+
export function webSearchProviderFromEnv(env = process.env) {
|
|
234
|
+
const raw = env.WEB_SEARCH_PROVIDER;
|
|
235
|
+
const word = raw === undefined ? undefined : providerWord(raw);
|
|
236
|
+
return word !== undefined && isWebSearchProvider(word) ? word : undefined;
|
|
237
|
+
}
|
|
238
|
+
export function webSearchConfigFromEnv(env = process.env) {
|
|
239
|
+
const provider = webSearchProviderFromEnv(env);
|
|
240
|
+
if (provider === undefined)
|
|
178
241
|
return undefined;
|
|
242
|
+
return assembleBackendConfig(provider, {
|
|
243
|
+
apiKey: envValue("WEB_SEARCH_API_KEY", judgeText(env.WEB_SEARCH_API_KEY)),
|
|
244
|
+
endpoint: envValue("WEB_SEARCH_ENDPOINT", judgeEndpoint(env.WEB_SEARCH_ENDPOINT)),
|
|
245
|
+
searxngParams: envValue("WEB_SEARCH_SEARXNG_PARAMS", judgeSearxngParams(env.WEB_SEARCH_SEARXNG_PARAMS)),
|
|
246
|
+
maxResults: envValue("WEB_SEARCH_MAX_RESULTS", judgeCount(envNumber(env.WEB_SEARCH_MAX_RESULTS))),
|
|
247
|
+
timeoutMs: envValue("WEB_SEARCH_TIMEOUT_MS", judgeCount(envNumber(env.WEB_SEARCH_TIMEOUT_MS))),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const SETTINGS_WEB_SEARCH_FORM = "settings.webSearch must be an object { provider, apiKey?, endpoint?, searxngParams?, maxResults? }";
|
|
251
|
+
function judgeRequestProvider(raw) {
|
|
252
|
+
const got = raw === undefined || raw === null ? "it is missing" : typeof raw === "string" ? `got ${JSON.stringify(raw.slice(0, 40))}` : `got a ${Array.isArray(raw) ? "array" : typeof raw}`;
|
|
253
|
+
const word = typeof raw === "string" ? providerWord(raw) : undefined;
|
|
254
|
+
return word !== undefined && isWebSearchProvider(word)
|
|
255
|
+
? { ok: true, value: word }
|
|
256
|
+
: { ok: false, issue: `must name one of ${WEB_SEARCH_PROVIDERS.join(" | ")} (${got}) — it names where this request's searches go, so an unknown or missing provider is refused rather than replaced by this deployment's own backend` };
|
|
257
|
+
}
|
|
258
|
+
export function judgeRequestWebSearch(raw) {
|
|
259
|
+
if (raw === undefined || raw === null)
|
|
260
|
+
return { kind: "absent" };
|
|
261
|
+
if (typeof raw !== "object" || Array.isArray(raw))
|
|
262
|
+
return { kind: "malformed", issue: SETTINGS_WEB_SEARCH_FORM };
|
|
179
263
|
const s = raw;
|
|
180
|
-
const provider =
|
|
181
|
-
if (!
|
|
182
|
-
return
|
|
183
|
-
const
|
|
264
|
+
const provider = judgeRequestProvider(s.provider);
|
|
265
|
+
if (!provider.ok)
|
|
266
|
+
return { kind: "malformed", issue: `settings.webSearch.provider ${provider.issue}` };
|
|
267
|
+
const fields = {
|
|
268
|
+
apiKey: judgeText(s.apiKey),
|
|
269
|
+
endpoint: judgeEndpoint(s.endpoint),
|
|
270
|
+
searxngParams: judgeSearxngParams(s.searxngParams),
|
|
271
|
+
maxResults: judgeCount(s.maxResults),
|
|
272
|
+
};
|
|
273
|
+
for (const [field, j] of Object.entries(fields)) {
|
|
274
|
+
if (!j.ok)
|
|
275
|
+
return { kind: "malformed", issue: `settings.webSearch.${field} ${j.issue}` };
|
|
276
|
+
}
|
|
184
277
|
return {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
})
|
|
192
|
-
...(max ? { maxResults: max } : {}),
|
|
278
|
+
kind: "honored",
|
|
279
|
+
config: assembleBackendConfig(provider.value, {
|
|
280
|
+
apiKey: valueOf(fields.apiKey),
|
|
281
|
+
endpoint: valueOf(fields.endpoint),
|
|
282
|
+
searxngParams: valueOf(fields.searxngParams),
|
|
283
|
+
maxResults: valueOf(fields.maxResults),
|
|
284
|
+
}),
|
|
193
285
|
};
|
|
194
286
|
}
|
|
195
287
|
//# sourceMappingURL=web-search.js.map
|
package/dist/run-local.js
CHANGED
|
@@ -8,8 +8,8 @@ import { realpathSync } from "node:fs";
|
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { loadRemoteExec } from "@sema-agent/settings-schema/node";
|
|
11
|
-
import { Runner, uuidv7, parseModelMention, FileStorageBackend, NodeExecutionEnv,
|
|
12
|
-
import {
|
|
11
|
+
import { Runner, uuidv7, parseModelMention, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
|
|
12
|
+
import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
|
|
13
13
|
import { createBrain } from "./brain.js";
|
|
14
14
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
15
15
|
import { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
@@ -48,6 +48,7 @@ import { fatalExit } from "./boot/fatal-exit.js";
|
|
|
48
48
|
import { installProcessExitBoundary } from "./boot/exit-boundary.js";
|
|
49
49
|
import { createMetrics } from "./observability/metrics.js";
|
|
50
50
|
import { installSecretEnvScrubRecorder } from "./observability/secret-env-scrub.js";
|
|
51
|
+
import { createHostLspManager } from "./host-lsp-manager.js";
|
|
51
52
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
52
53
|
export function parseArgs(argv) {
|
|
53
54
|
let objective;
|
|
@@ -195,7 +196,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
195
196
|
for (const de of r.domainErrors ?? [])
|
|
196
197
|
logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: "domain fell to schema defaults; other domains applied normally" });
|
|
197
198
|
effective = r.effective;
|
|
198
|
-
applyEffective(config, r.effective, logger);
|
|
199
|
+
applyEffective(config, r.effective, logger, { leg: "boot" });
|
|
199
200
|
if (effective?.mcp)
|
|
200
201
|
config.mcpServers = resolveMcpServers(effective.mcp, logger);
|
|
201
202
|
if (effective?.a2a)
|
|
@@ -242,7 +243,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
242
243
|
}
|
|
243
244
|
const ungatedCompensation = buildOnlySensitiveBaselineWarning(config, {
|
|
244
245
|
durableEnabled: false,
|
|
245
|
-
singleUserAutoAcceptBaseline:
|
|
246
|
+
singleUserAutoAcceptBaseline: singleUserAutoAcceptBaselineApplies(config),
|
|
246
247
|
});
|
|
247
248
|
if (ungatedCompensation)
|
|
248
249
|
logger.warn(ungatedCompensation.event, ungatedCompensation.fields);
|
|
@@ -285,7 +286,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
285
286
|
...(hostRx.commandTimeoutMs != null ? { commandTimeoutMs: hostRx.commandTimeoutMs } : {}),
|
|
286
287
|
})
|
|
287
288
|
: undefined;
|
|
288
|
-
const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ?
|
|
289
|
+
const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ? createHostLspManager((event, fields) => logger.info(event, fields)) : undefined;
|
|
289
290
|
const memoryEngine = memoryEngineBackendFor(config, root, memoryEngineIncident);
|
|
290
291
|
let memorySync;
|
|
291
292
|
if (config.memorySync && memoryEngine) {
|
package/dist/runs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runWithVerification, runCascade, defaultTaskRegistry, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
|
|
1
|
+
import { runWithVerification, runCascade, defaultTaskRegistry, taskNotificationDedupKey, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
|
|
2
2
|
import { failureCodeOf, sealResultForExit, outwardPlane } from "./terminal.js";
|
|
3
3
|
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
4
4
|
import { redactErrorMessage } from "./observability/run-terminal.js";
|
|
@@ -12,7 +12,7 @@ import { CANCELLED_CODE, mintCancelledResult } from "./run-cancel-context.js";
|
|
|
12
12
|
import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
|
|
13
13
|
import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
|
|
14
14
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
15
|
-
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry,
|
|
15
|
+
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
|
|
16
16
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
17
17
|
import { errorText as errText } from "./observability/err-text.js";
|
|
18
18
|
import { LIVENESS_HEARTBEAT_MS } from "./config-invariants.js";
|
|
@@ -256,8 +256,9 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
256
256
|
const { type, ...rest } = frame;
|
|
257
257
|
const f = frame;
|
|
258
258
|
const write = append(type, rest);
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
const fKey = f.type === "task_notification" ? taskNotificationStreamKeyOf(f) : undefined;
|
|
260
|
+
if (fKey !== undefined)
|
|
261
|
+
notifiedKeys.set(fKey, write.then(() => true, () => false));
|
|
261
262
|
return write;
|
|
262
263
|
}, completionDiagLog ? { route: "bg-run-start", connection: "durable-append", log: completionDiagLog } : undefined);
|
|
263
264
|
const runBody = async () => {
|
|
@@ -285,7 +286,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
285
286
|
void workflowCompletionInbox.enqueue(taskNotificationInboxEntry(spec.sessionId, elicitOwner ?? owner, n, Date.now(), taskId, opts?.priority)).catch((err) => completionDiagLog?.("park_enqueue_failed", { route: "bg-run-start", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
|
|
286
287
|
}
|
|
287
288
|
else if (legLive) {
|
|
288
|
-
const key =
|
|
289
|
+
const key = taskNotificationDedupKey(n);
|
|
289
290
|
if (notifiedKeys.get(key) === undefined) {
|
|
290
291
|
notifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
|
|
291
292
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* path-prefixed commands all bypass the argv[0] match (they route to `ask`, fail-closed, but are not blocked by
|
|
24
24
|
* name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
|
|
25
25
|
*/
|
|
26
|
-
import { type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
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
29
|
/** 运维 autonomy 四档的**闭集**(同上)。`config.ts` 的 `parseAutonomy` 也从这里读 —— 四档词表全仓一份。 */
|
|
@@ -142,13 +142,60 @@ export declare function autonomyOverrides(autonomy: Autonomy | undefined): Parti
|
|
|
142
142
|
* 并不要求 always),之后运维把 autonomy 热改成 `ask`,那条老行会被标成治理出身 —— 五个前提要**同时**
|
|
143
143
|
* 成立,且该行仍未决。
|
|
144
144
|
* 根治要**在 mint 那一刻把出身写进行里**(core 的 `CheckpointGate` 属主面,不是本仓能单方面做的);
|
|
145
|
-
*
|
|
146
|
-
*
|
|
145
|
+
* 在那之前这一位的定位是**分诊提示**,不参与任何门/CAS/resume 判定 —— 唯一的例外是**只收窄**的那一个读者:
|
|
146
|
+
* 耐久 `/decide` 腿的会话级 remember 可记性(NP-1,7.100.0,`tool-approval.ts` 的 `parkRowStandingAnswerFacts`)。
|
|
147
|
+
* 在那里两个失真方向的代价都不越过修前:在场向 ⇒ 一次 `remember:"session"` 只放行本次、不铸会话免询问(安全
|
|
148
|
+
* 方向,代价 = 下一次再问一遍);缺席向 ⇒ 照旧可记(= NP-1 之前的行为)。它从不**放宽**任何裁决。
|
|
149
|
+
* 登记在此,不加特征化测试(把已知残留钉成契约是另一种病)。
|
|
147
150
|
*/
|
|
148
151
|
export declare function governanceMandatesShellGateAlways(governance: {
|
|
149
152
|
autonomy?: Autonomy;
|
|
150
153
|
manualModeShellGate?: "always" | "classify";
|
|
151
154
|
}): boolean;
|
|
155
|
+
/** [ref] 归因的部署侧入参(两个治理旋钮;判据属主见 {@link governanceMandatesShellGateAlways})。 */
|
|
156
|
+
export interface GovernancePosture {
|
|
157
|
+
autonomy?: Autonomy;
|
|
158
|
+
manualModeShellGate?: "always" | "classify";
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* [ref]:推「这道门是运维治理层下的吗」。判据是**合取**,两半各管一件事:
|
|
162
|
+
*
|
|
163
|
+
* 行上的取证格 `gate.riskDescriptor.shellGateDoctrine === "always"`
|
|
164
|
+
* ∧ 本部署的治理层本来就要求 always({@link governanceMandatesShellGateAlways},那里写着为什么单看行不够 ——
|
|
165
|
+
* SUP 路由姿态同样产 `"always"`)
|
|
166
|
+
*
|
|
167
|
+
* 第一半:core 在 mint 这道门时把「当时活着的 doctrine」如实写进 `riskDescriptor.shellGateDoctrine`
|
|
168
|
+
* (2026-08-05 取证裁定;`prepare-task` 只在**被 shell 门铸**的 ask 上写它),所以持久行自己带着证据 ——
|
|
169
|
+
* park 腿天然跨副本、跨重启,活卡腿那张 ALS 标记表在这里结构上够不着。
|
|
170
|
+
* 第二半:把「有效档 always」收窄到「治理层是真成因」,否则一条 SUP 路由出来的门会被谎报成治理强制。
|
|
171
|
+
*
|
|
172
|
+
* ⚠️ 残留(双向失真的窄在场向 + 根治路径)与「只收窄」的那一个裁决读者,都登记在
|
|
173
|
+
* {@link governanceMandatesShellGateAlways} 的顶注,别在这里复述。
|
|
174
|
+
*
|
|
175
|
+
* 🔴 `"classify"` 档**刻意不标**:那一档由 core 的分类器逐调用裁决,一次 shell ask 可能出自分类器(治理)
|
|
176
|
+
* 也可能出自 `APPROVAL_REQUIRE` 这类别的门,行上无从分辨 —— 与活卡腿同一条「宁缺毋假」纪律。
|
|
177
|
+
*
|
|
178
|
+
* 🔴 **单一属主,四个读者**(不在任何读面另写一份判据 —— 这一位恰恰是靠「行上取证 ∧ 部署姿态」的合取才不
|
|
179
|
+
* 谎报的,两份必漂):409 体的待批门材料(`http/active-run-conflict.ts`)、两条 durable 读面
|
|
180
|
+
* `GET /v1/approvals` / `/v1/approvals/stream` 的 `projectPendingForWire`([ref]②)、耐久 `/decide` 腿的
|
|
181
|
+
* remember 可记性(NP-1,经 `tool-approval.ts` 的 `parkRowStandingAnswerFacts`)。
|
|
182
|
+
* 住在本文件而不是 `http/active-run-conflict.ts` 的理由:它的另一半判据属主就在这里,而第四个读者
|
|
183
|
+
* (`tool-approval.ts`)从 `http/` 那侧值引会闭合一条 ESM 值环(`active-run-conflict` → `checkpoint-store-sql` →
|
|
184
|
+
* `tool-approval`),本文件是三者共同的叶。
|
|
185
|
+
*
|
|
186
|
+
* ⚠️ **askId 为什么不在这里**([ref] 的另一半,如实记账):待批 ask 的 `askId` 只活在 `approval_ask` 行上
|
|
187
|
+
* (checkpoint 行没有这一列,core 的 `CheckpointGate`/`PendingAction`/`Checkpoint` 顶层都不带它)。反向
|
|
188
|
+
* (checkpoint → ask)今天**没有读口**:两条 list 读口都硬过滤 `state='STREAM_PENDING'`,而 park 完的行是
|
|
189
|
+
* `PARKED`;唯一精确的连接键是 ask 行上的 `gate_token`,要按它反查得给店加一个新读口(SQL 面 = 双库集成
|
|
190
|
+
* 门)。派生 `deriveAskId(...)` 也不行:它吃 `legKey`(= 上一腿 resume token 的摘要)与 `parentToolCallId`,
|
|
191
|
+
* 这两维从 checkpoint 行推不出来,二腿/子代形上会算出一个**错**的 id —— 在「等的谁」这条通道上,错 id
|
|
192
|
+
* 比缺席坏得多(`http/active-run-conflict.ts` 的 `resumeEntryForGate` 的 default 臂是同一条纪律)。故诚实缺席。
|
|
193
|
+
*/
|
|
194
|
+
export declare function governanceOriginOf(gate: {
|
|
195
|
+
riskDescriptor?: {
|
|
196
|
+
shellGateDoctrine?: ShellGateDoctrine;
|
|
197
|
+
};
|
|
198
|
+
} | undefined, governance: GovernancePosture | undefined): true | undefined;
|
|
152
199
|
/** spec 序里的秩(0 起,越大越严)。入参收 `string`(fail-loud 边界:schema `trustRank` 同款姿势)——词表外 ⇒
|
|
153
200
|
* RangeError,绝不 undefined/NaN 让比较静默成 false。导出只为对表钉(test/safety-merge-spec-consumption.test.ts,
|
|
154
201
|
* [ref] 件4)——运行时消费仍走上表(键类型闭集)。 */
|
|
@@ -116,6 +116,11 @@ export function autonomyOverrides(autonomy) {
|
|
|
116
116
|
export function governanceMandatesShellGateAlways(governance) {
|
|
117
117
|
return autonomyOverrides(governance.autonomy).shellGate === "always" || governance.manualModeShellGate === "always";
|
|
118
118
|
}
|
|
119
|
+
export function governanceOriginOf(gate, governance) {
|
|
120
|
+
if (gate?.riskDescriptor?.shellGateDoctrine !== "always")
|
|
121
|
+
return undefined;
|
|
122
|
+
return governanceMandatesShellGateAlways(governance ?? {}) ? true : undefined;
|
|
123
|
+
}
|
|
119
124
|
function createGovernanceAskMarkingPolicy(inner, marksOf) {
|
|
120
125
|
const note = (d, req) => {
|
|
121
126
|
marksOf()?.setMarked(req.toolCallId, d.action === "ask");
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* ## 规则(一句)
|
|
12
12
|
* 本仓**知道**哪些 env 键是凭据 —— 配置目录(`config-catalog.ts`)里 `type:"secret"` 的每一行,从目录机器派生,
|
|
13
13
|
* 不手抄第二份名单 —— 这些键按名剥;其余照旧交 core 按形剥。{@link scrubServerSecrets} = 目录派生剥除 ∘ core
|
|
14
|
-
* `scrubSecretEnv
|
|
15
|
-
*
|
|
14
|
+
* `scrubSecretEnv`,本仓三条子进程腿(host shell / git 子进程 / 本地语言服务器,后者见 `host-lsp-manager.ts`)都只调它。
|
|
15
|
+
* core 形状规则的缺口由 core 源头修 —— 7.30.1 #1072 已补 `KEYS|TOKENS|PASSWORDS`(S-616 提货),目录臂独担的只剩
|
|
16
|
+
* `OTEL_EXPORTER_OTLP_HEADERS`(`*_HEADERS` 不在形状规则射程,是边界不是缺陷票)。本层不改 core 的判据,也不自造
|
|
17
|
+
* 第二份形状规则。目录里加一个 secret 型键,三条腿当场就剥它。
|
|
16
18
|
*
|
|
17
19
|
* ## 不变的面
|
|
18
20
|
* `inheritEnv:"all"`(显式全继承)与显式 allowlist 两形不经本函数,行为逐字不变;调用方显式传入的 env
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -264,7 +264,7 @@ export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
|
|
|
264
264
|
export declare const MAX_SETTINGS_ENV_KEY_CHARS = 256;
|
|
265
265
|
/** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
|
|
266
266
|
* `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode` 与两个 `skillListing*` 在
|
|
267
|
-
* {@link parseTaskSettings};`webSearch` 在 `capabilities/scenarios.ts`(`
|
|
267
|
+
* {@link parseTaskSettings};`webSearch` 在 `capabilities/scenarios.ts`(判决 `judgeRequestWebSearch`,单用户闸;形错在受理面 400,S-199)。
|
|
268
268
|
* 加键必须同时加消费点,否则就是往闭集里塞一个新的静默丢。
|
|
269
269
|
*
|
|
270
270
|
* 🔴 两个 `skillListing*`([ref],7.77.0)= **CC settings 同名键**,值域抄 CC 的 schema 声明
|