@sema-agent/server 7.129.1 → 7.131.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 +102 -0
- package/README.md +8 -5
- package/README.zh-CN.md +6 -4
- package/USAGE.md +48 -9
- package/dist/audit.js +4 -1
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/resolve-spec.js +43 -16
- package/dist/boot/stage-02-web-admin-pull.js +9 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +7 -2
- package/dist/capabilities/builtin-tools.js +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/hands-lane.js +2 -2
- package/dist/capabilities/host-tool-names.d.ts +17 -0
- package/dist/capabilities/host-tool-names.js +15 -0
- package/dist/capabilities/mcp-probe.js +19 -6
- package/dist/capabilities/repo-tools.js +4 -3
- package/dist/capabilities/scenarios.d.ts +34 -0
- package/dist/capabilities/scenarios.js +20 -4
- package/dist/capabilities/select-environment-tool.js +2 -1
- package/dist/capabilities/send-user-file-tool.d.ts +0 -1
- package/dist/capabilities/send-user-file-tool.js +2 -2
- package/dist/capabilities/skills.js +1 -1
- package/dist/capabilities/tool-defer.js +2 -1
- package/dist/config-catalog.js +16 -5
- package/dist/config-invariants.js +2 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +6 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +18 -17
- package/dist/elicitation.d.ts +7 -0
- package/dist/elicitation.js +20 -5
- package/dist/exec-bounds.d.ts +6 -6
- package/dist/hooks/hook-llm.d.ts +11 -1
- package/dist/hooks/hook-llm.js +11 -14
- package/dist/hooks/hook-runner.d.ts +55 -9
- package/dist/hooks/hook-runner.js +86 -16
- package/dist/http/admission.js +2 -2
- package/dist/http/approval-sweeps.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +12 -11
- package/dist/http/checkpoint-ctx.js +4 -4
- package/dist/http/dispatch.js +1 -1
- package/dist/http/resume-legs.js +6 -5
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/capabilities.js +13 -5
- package/dist/http/routes/runs.js +18 -14
- package/dist/http/routes/sessions.js +22 -10
- package/dist/http/routes/tasks.js +16 -11
- package/dist/http/scenario-read-face.d.ts +14 -0
- package/dist/http/scenario-read-face.js +5 -0
- package/dist/http/send.d.ts +10 -0
- package/dist/http/send.js +6 -0
- package/dist/http/server.d.ts +16 -4
- package/dist/http/server.js +2 -2
- package/dist/http/stored-body-replay.d.ts +10 -3
- package/dist/http/stored-body-replay.js +27 -6
- package/dist/http/wire-types.d.ts +18 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/mcp-assertion-header.d.ts +2 -2
- package/dist/mcp-assertion-header.js +3 -3
- package/dist/model-entry-refusal.d.ts +1 -7
- package/dist/model-route-endpoint.d.ts +51 -11
- package/dist/model-route-endpoint.js +45 -23
- package/dist/model-route-gap.d.ts +5 -4
- package/dist/model-route-gap.js +1 -1
- package/dist/model-select.d.ts +58 -0
- package/dist/model-select.js +31 -0
- package/dist/namespaced-rule-name.d.ts +7 -4
- package/dist/namespaced-rule-name.js +4 -3
- package/dist/observability/fail-open.d.ts +16 -7
- package/dist/observability/fail-open.js +10 -2
- package/dist/plugins/local-session-store.js +2 -2
- package/dist/plugins/one-time-migrations.d.ts +21 -1
- package/dist/plugins/one-time-migrations.js +33 -1
- package/dist/plugins/remote-env-adb.js +5 -4
- package/dist/plugins/remote-env-local-docker.js +5 -4
- package/dist/plugins/remote-env-ssh.js +5 -4
- package/dist/question.d.ts +18 -9
- package/dist/question.js +21 -13
- package/dist/request-key-closure.d.ts +2 -2
- package/dist/request-key-closure.js +16 -9
- package/dist/rule-entry-list.d.ts +124 -17
- package/dist/rule-entry-list.js +140 -24
- package/dist/run-local.d.ts +3 -3
- package/dist/run-local.js +8 -7
- package/dist/runs.d.ts +42 -2
- package/dist/runs.js +45 -2
- package/dist/runtime-caps-resolver.d.ts +7 -0
- package/dist/runtime-caps-resolver.js +1 -0
- package/dist/session-titler.d.ts +4 -1
- package/dist/session-titler.js +13 -3
- package/dist/spec-fields.d.ts +8 -4
- package/dist/stored-request-hooks-m20.d.ts +23 -0
- package/dist/stored-request-hooks-m20.js +124 -0
- package/dist/stored-request-tool-axes-m19.d.ts +29 -0
- package/dist/stored-request-tool-axes-m19.js +27 -0
- package/dist/task-mcp.d.ts +55 -60
- package/dist/task-mcp.js +197 -96
- package/dist/task-settings.d.ts +41 -5
- package/dist/task-settings.js +39 -25
- package/dist/tool-axis-words.d.ts +14 -17
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-sink.d.ts +29 -0
- package/dist/trace/ledger-sink.js +11 -0
- package/dist/trace/project.d.ts +6 -0
- package/dist/trace/project.js +11 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/user-authored-text.d.ts +7 -0
- package/dist/user-authored-text.js +3 -0
- package/dist/web-admin-client/apply-effective.js +10 -5
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +3 -2
package/dist/config-catalog.js
CHANGED
|
@@ -16,7 +16,7 @@ import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
|
|
|
16
16
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
17
17
|
import { describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
|
|
18
18
|
import { buildOutboundProxyPosture } from "./outbound-dispatcher.js";
|
|
19
|
-
import {
|
|
19
|
+
import { modelImageInput, modelReasoningDecl } from "./model-select.js";
|
|
20
20
|
export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
|
|
21
21
|
export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
|
|
22
22
|
export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
|
|
@@ -303,14 +303,18 @@ export const CONFIG_CATALOG = [
|
|
|
303
303
|
r("MODEL_FIRST_BYTE_TIMEOUT_MS", "modelPlane", "number", `请求发出 → 响应头的等待(ms;缺席 = core 缺省 + 上传宽限;到点 core 自有一次加宽重试,配了 MODEL_GATEWAY_FALLBACK_URLS 也一样(之后才切备;server 不铸 firstByteRetries);0 = 该段不设引擎钟、传输层的响应头 / 响应体钟也关(TCP + TLS 建连仍归传输层自己的钟);自托管慢网关按排队 + prefill 留足;failover 拓扑想快切须显式设小;须为 [${FIRST_BYTE_TIMEOUT_BOUNDS.minMs}, ${FIRST_BYTE_TIMEOUT_BOUNDS.maxMs}] 内十进制整数,其余拒启)`, { derivedDefaultNote: "未设/空串 ⇒ 不铸键,跟随 core 缺省(本仓不复制上游数字;core 改缺省本旋钮照映不改)", resolve: (c) => c.resilience.firstByteTimeoutMs ?? null }),
|
|
304
304
|
r("MODEL_FIRST_TOKEN_TIMEOUT_MS", "modelPlane", "number", "首 token 超时(ms;0 = 该段不设引擎钟、传输层的响应体钟也关 —— 开流不吐字的唯一看门狗;自托管长 prefill 后端按此档留足)", { staticDefault: String(RESILIENCE_DEFAULTS.firstTokenTimeoutMs.value) }),
|
|
305
305
|
r("MODEL_FREQUENCY_PENALTY", "modelPlane", "number", "frequency_penalty 直通(压过 EXTRA_BODY 同键)"),
|
|
306
|
-
r("MODEL_GATEWAY_BASEURL", "modelPlane", "url", "openai 兼容网关 base URL(S-623:**无出厂缺省** ——
|
|
306
|
+
r("MODEL_GATEWAY_BASEURL", "modelPlane", "url", "openai 兼容网关 base URL(S-623:**无出厂缺省** —— 将要服务的那张表(目录被采纳判目录表,否则判 env 表)里每条 openai 兼容路由都必须解析到显式端点(条目自己的 baseUrl,或本键),否则拒启 / 热路径整批拒,机读码 `config.model_route_endpoint_missing`,拒因点名条目并给出本机写法 `MODEL_GATEWAY_BASEURL=<本机网关>/v1`;anthropic 单路由部署不设本键合法;S-475:带 userinfo 拒启,机读码 `config.gateway_url_userinfo` —— WHATWG 禁止用带凭据的 URL 构造 fetch Request,凭据放 MODEL_API_KEY;S-483:解析不动/不透明形拒启,机读码 `config.model_route_url_invalid`;S-514:带 `#fragment` 拒启,机读码 `config.model_route_url_fragment`)", { derivedDefaultNote: "缺席 ⇒ 本部署没有网关(不铸键);有 openai 路由落不到端点 ⇒ 拒启", configKey: "gatewayBaseUrl", center: cModels("models[].baseUrl") }),
|
|
307
307
|
r("MODEL_GATEWAY_FALLBACK_URLS", "modelPlane", "url", "网关故障转移地址表(CSV;S-623:主网关 MODEL_GATEWAY_BASEURL 未设而本键设了 ⇒ 拒启,机读码 `config.gateway_fallback_without_primary` —— 备用是主网关的镜像;S-475:**逐项**判 userinfo,任一项带 ⇒ 拒启,机读码 `config.gateway_url_userinfo`;S-483:逐项判可解析性,机读码 `config.model_route_url_invalid`;S-514:**逐项**判 `#fragment`,机读码 `config.model_route_url_fragment`)", { configKey: "gatewayFallbackUrls" }),
|
|
308
|
-
r("MODEL_ID", "modelPlane", "string", "主模型 id(3.0.0
|
|
308
|
+
r("MODEL_ID", "modelPlane", "string", "主模型 id(3.0.0 起无出厂缺省;env 表的主槽。目录被采纳时整张换掉 env 表、本键不服务,目录第一次采纳那一拍说一行 `config_env_set_but_ineffective`;目录缺席时它是兜底,两者皆无 ⇒ 拒启指路)", { configKey: "model", resolve: (c) => c.model.id, center: cModels("models[].id") }),
|
|
309
309
|
r("MODEL_IDLE_TIMEOUT_MS", "modelPlane", "number", "流中静默看门狗(ms;0 = 该段不设引擎钟、传输层的响应体钟也关)", { staticDefault: String(RESILIENCE_DEFAULTS.idleTimeoutMs.value) }),
|
|
310
310
|
r("MODEL_MAX_TOKENS", "modelPlane", "number", "主模型输出上限", { derivedDefaultNote: "缺席 ⇒ min(32000, contextWindow)", resolve: (c) => c.model.maxTokens ?? null, center: cModels("models[].maxTokens") }),
|
|
311
311
|
r("MODEL_PRESENCE_PENALTY", "modelPlane", "number", "presence_penalty 直通"),
|
|
312
312
|
r("MODEL_PROVIDER", "modelPlane", "enum", "主模型 provider(路由选 brain;S-461 起是闭集,词表外的值启动期拒启并点名机读码 `config.model_provider_invalid`)", { enumValues: MODEL_PROVIDER_WORDS, derivedDefaultNote: "缺席:ANTHROPIC base+cred 同在 ⇒ anthropic,否则 gateway", resolve: (c) => c.model.provider, center: cModels("models[].provider") }),
|
|
313
|
-
r("MODEL_REASONING", "modelPlane", "boolean", "主模型 reasoning 声明
|
|
313
|
+
r("MODEL_REASONING", "modelPlane", "boolean", "主模型 reasoning 声明(三态:true / false / 缺席 = 未声明)", {
|
|
314
|
+
derivedDefaultNote: "缺席 ⇒ 不铸 `Model.reasoning`(没有 true 缺省);引擎按真值读,缺席与 false 同读 —— 思考档不随请求发出;要开思考写 MODEL_REASONING=true。`GET /v1/models` 该行不带 `reasoning` 键",
|
|
315
|
+
resolve: (c) => modelReasoningDecl(c.model) ?? null,
|
|
316
|
+
center: cModels("models[].reasoning"),
|
|
317
|
+
}),
|
|
314
318
|
r("MODEL_REASONING_EFFORT_LEVELS", "modelPlane", "csv", "openai 端点接受的思考档集(六档子集;高档 clamp-down 不 422;anthropic 腿不铸此键;未知词拒启)", {
|
|
315
319
|
derivedDefaultNote: "缺席 ⇒ 不铸 compat,core 走保守梯(minimal|low|medium|high),xhigh/max 被 clamp 下来",
|
|
316
320
|
resolve: (c) => {
|
|
@@ -332,7 +336,14 @@ export const CONFIG_CATALOG = [
|
|
|
332
336
|
},
|
|
333
337
|
}),
|
|
334
338
|
r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
|
|
335
|
-
r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(
|
|
339
|
+
r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(三态:true / false / 缺席 = 未声明;false = 明说不收图:带图请求的图被换成占位句交给模型,提交时计数并告警)", {
|
|
340
|
+
derivedDefaultNote: "缺席 ⇒ 不铸 `Model.input`(没有 true 缺省);引擎与带图预检都按「收」读(图原样交给模型,不出信号);`GET /v1/models` 该行不带 `vision` 键。要让预检对纯文本模型出信号写 MODEL_VISION=false",
|
|
341
|
+
resolve: (c) => {
|
|
342
|
+
const v = modelImageInput(c.model);
|
|
343
|
+
return v === "undeclared" ? null : v === "yes";
|
|
344
|
+
},
|
|
345
|
+
center: cModels("models[].vision"),
|
|
346
|
+
}),
|
|
336
347
|
r("MYSQL_DATABASE", "store", "string", "MySQL 协议库名(选中该引擎时必填)", { danger: DATA }),
|
|
337
348
|
r("MYSQL_HOST", "store", "string", "MySQL 协议主机(在场即算 SQL 信号,参与 DB_BACKEND 默认推导)", { danger: DATA }),
|
|
338
349
|
r("MYSQL_PASSWORD", "store", "secret", "MySQL 协议口令", { danger: SEC }),
|
|
@@ -232,8 +232,8 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
232
232
|
id: "model-route-endpoint",
|
|
233
233
|
keys: ["gatewayBaseUrl", "gatewayFallbackUrls", "anthropic", "models"],
|
|
234
234
|
restartReason: "model-route-endpoint",
|
|
235
|
-
bootAnchor: { file: "src/
|
|
236
|
-
check: ({ live, next }) => (next.models === live.models ? undefined : modelRouteEndpointRefusal(next)),
|
|
235
|
+
bootAnchor: { file: "src/boot/stage-02-web-admin-pull.ts", marker: "CROSS-INVARIANT:model-route-endpoint" },
|
|
236
|
+
check: ({ live, next }) => (next.models === live.models ? undefined : modelRouteEndpointRefusal(next, "catalog")),
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
239
|
id: "default-model-resolvable",
|
package/dist/config-provider.js
CHANGED
|
@@ -149,8 +149,8 @@ export function mapToServiceEffective(eff, version) {
|
|
|
149
149
|
...(m.sealedApiKey !== undefined ? { sealedApiKey: m.sealedApiKey } : {}),
|
|
150
150
|
...(m.quotaWeight !== undefined ? { quotaWeight: m.quotaWeight } : {}),
|
|
151
151
|
tier: m.tier,
|
|
152
|
-
reasoning: m.reasoning,
|
|
153
|
-
vision: m.vision,
|
|
152
|
+
...(m.reasoning !== undefined ? { reasoning: m.reasoning } : {}),
|
|
153
|
+
...(m.vision !== undefined ? { vision: m.vision } : {}),
|
|
154
154
|
...(m.contextWindow !== undefined ? { contextWindow: m.contextWindow } : {}),
|
|
155
155
|
...(m.maxTokens !== undefined ? { maxTokens: m.maxTokens } : {}),
|
|
156
156
|
...(typeof m.autoCompactTokens === "number" ? { autoCompactTokens: m.autoCompactTokens } : {}),
|
package/dist/config-types.d.ts
CHANGED
|
@@ -548,6 +548,12 @@ export interface ServiceConfigFlat {
|
|
|
548
548
|
*
|
|
549
549
|
* 🔴 **必填、无可选形**:一个 `?` 会立刻招来一条「缺席就回落 `config.model`」的臂,而那条臂正是本键
|
|
550
550
|
* 要消灭的东西([ref]:同一语义面禁两个写者)。测试夹具与 `loadConfig` 一样,与 `model` 同源铸。
|
|
551
|
+
*
|
|
552
|
+
* 🔴 **同引用是契约**:env 表服务时 `model` 与 `envModel` **恒是同一个对象**(`loadConfig` 把同一只模型放进两个键)。
|
|
553
|
+
* `servedModelTableLeg`(`model-select.ts`)拿这条**引用相等**当「这个进程服务的是哪张表」的唯一状态位 —— `model` 换成目录缺省 ⇒ 不再同引用 ⇒ 服务的是目录表。
|
|
554
|
+
* 所以写口恰好两处:`config.ts` 的铸造点(两键同一个对象)与 `web-admin-client/apply-effective.ts` 的 `commitStaged`(只换 `model`,`envModel` 永不被写);
|
|
555
|
+
* 克隆模型对象、或嵌入方手搓 `ServiceConfig` 时把两键指向两只对象,这一位会被误读成「目录在服务」(拒因句选错视图、`MODEL_ID` 通告误发)。
|
|
556
|
+
* 源码钉(`test/served-model-route.test.ts` 的类钉)把这两处写口列成白名单。
|
|
551
557
|
*/
|
|
552
558
|
envModel: Model;
|
|
553
559
|
/** Model catalog by name (1.24): always `default` + the main model's id, plus an optional cheap model.
|
package/dist/config.d.ts
CHANGED
|
@@ -229,7 +229,7 @@ export declare function parseEnvNameAllowlist(knob: RunnerEnvNameAllowlistKnob):
|
|
|
229
229
|
* Shared by the env lane (main + cheap models) and the center lane (web-admin toModel). */
|
|
230
230
|
export declare function applyAutoCompactWindow(m: Model, explicit?: number): "explicit" | "sonnet5" | "removed-1m" | "removed";
|
|
231
231
|
/**
|
|
232
|
-
* **严格名册布防**的唯一判据(boot 就绪门 `boot/web-admin.ts`
|
|
232
|
+
* **严格名册布防**的唯一判据(boot 就绪门 `boot/web-admin.ts` 与装配第 2 段的路由端点判官同调这一只)。
|
|
233
233
|
*
|
|
234
234
|
* 布防 = 远程 registry ∧ 非 dryRun ∧ `CONFIG_REQUIRE_ROSTER=true`(严格 `"true"`,直读 env、不进 ServiceConfig —— 纯
|
|
235
235
|
* boot 期姿势旋钮,原判据逐字搬来,零语义变化)。布防时运维声明 env MODEL_ID 是**占位**:名册落地前计费提交 503、
|
|
@@ -332,7 +332,7 @@ export declare function unmatchableToolNamesRefusal(source: string, names: reado
|
|
|
332
332
|
* 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据是**引擎的读法**,与请求的两张
|
|
333
333
|
* allow 表同一份(`rule-entry-list.ts`):
|
|
334
334
|
* ① 值不是字符串表 ⇒ 拒(env 的 CSV 恒给字符串表;判在这里是为了「值一旦换了来源,第一道判仍在」);
|
|
335
|
-
* ② 引擎读成空的条目(`compileAllowList(names, "allow", 名册
|
|
335
|
+
* ② 引擎读成空的条目(`compileAllowList(names, "allow", 名册).inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字,以及
|
|
336
336
|
* core 7.43.0 起条目名过引擎判官读成 `tool_name` 的退役名 / 无前缀短名 / 对端段铸不出,core 7.48.0 #1400 起含工具段为空的 `mcp__<srv>__`;
|
|
337
337
|
* 7.116.0–7.122.x 那一形由本仓先拒,随之退役)⇒ 拒,句子点名条目、引擎给的原因词(名字层带判词与现名)、
|
|
338
338
|
* 该怎么写。请求的 allow 侧对 ② 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,而引擎对读成空的部署条目只在每一跑发一条通告。
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF } from "./model-select.js";
|
|
2
|
+
import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF, mintCapabilityKeys } from "./model-select.js";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, posix as posixPath } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable } from "@sema-agent/core";
|
|
6
|
+
import { compileAllowList, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable } from "@sema-agent/core";
|
|
7
7
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES, WEB_ADMIN_ENV, retiredEnvRefusal } from "@sema-agent/settings-schema";
|
|
8
8
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
9
9
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
@@ -22,7 +22,7 @@ import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat,
|
|
|
22
22
|
import {} from "./posture-source.js";
|
|
23
23
|
import { COST_QUOTA_WINDOW_SEC_RANGE, costQuotaWindowSecInRange } from "./observability/cost-quota.js";
|
|
24
24
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
25
|
-
import { describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
|
|
25
|
+
import { TOOL_NAME_ROSTER, describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
|
|
26
26
|
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
|
|
27
27
|
import { declaredSemaConfigDir } from "./sema-config-dir.js";
|
|
28
28
|
import { lkgPathOverrideOf } from "./config-lkg.js";
|
|
@@ -33,14 +33,14 @@ import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE
|
|
|
33
33
|
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
34
34
|
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
35
35
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
36
|
-
import { allowLayerEntryCountIssue, describeInertRuleEntries, inertRuleEntriesOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
|
|
36
|
+
import { MAX_NAMED_INERT_ENTRIES, allowLayerEntryCountIssue, describeInertRuleEntries, inertRuleEntriesOf, inertRuleEntryGuidance, ruleEntryListShapeOk } from "./rule-entry-list.js";
|
|
37
37
|
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
38
38
|
import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
|
|
39
39
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
40
40
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
41
41
|
import { httpUrlFlaws } from "@sema-agent/settings-schema";
|
|
42
42
|
import { buildHttpUrlRefusal } from "./url-refusal.js";
|
|
43
|
-
import { csvOf, envLaneTable,
|
|
43
|
+
import { csvOf, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
|
|
44
44
|
import { displayListEntry } from "./list-entry-display.js";
|
|
45
45
|
import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
|
|
46
46
|
import { allowlistedEnv } from "./server-secret-env.js";
|
|
@@ -925,14 +925,14 @@ function parseModelDomain() {
|
|
|
925
925
|
thinkingFormat: parseThinkingFormat(process.env.MODEL_THINKING_FORMAT),
|
|
926
926
|
});
|
|
927
927
|
const envCompat = provider === "anthropic" ? undefined : envCompatDeclared;
|
|
928
|
+
const modelCapability = { reasoning: parseBoolWord("MODEL_REASONING", process.env.MODEL_REASONING), vision: parseBoolWord("MODEL_VISION", process.env.MODEL_VISION) };
|
|
928
929
|
const model = {
|
|
929
930
|
id: modelId,
|
|
930
931
|
name: modelId,
|
|
931
932
|
api: provider === "anthropic" ? "anthropic-messages" : "openai-completions",
|
|
932
933
|
provider,
|
|
933
934
|
baseUrl: "",
|
|
934
|
-
|
|
935
|
-
input: boolEnv("MODEL_VISION", true) ? ["text", "image"] : ["text"],
|
|
935
|
+
...mintCapabilityKeys(modelCapability),
|
|
936
936
|
...buildModelCostKey(buildCostOrAbsent({
|
|
937
937
|
input: costEnv("MODEL_COST_INPUT", "unset"),
|
|
938
938
|
output: costEnv("MODEL_COST_OUTPUT", "unset"),
|
|
@@ -983,7 +983,7 @@ function parseModelDomain() {
|
|
|
983
983
|
...model,
|
|
984
984
|
id: cheapMintedId,
|
|
985
985
|
name: cheapMintedId,
|
|
986
|
-
|
|
986
|
+
...mintCapabilityKeys({ reasoning: parseBoolWord("MODEL_CHEAP_REASONING", process.env.MODEL_CHEAP_REASONING), vision: parseBoolWord("MODEL_CHEAP_VISION", process.env.MODEL_CHEAP_VISION) }),
|
|
987
987
|
...buildModelCostKey(buildCostOrAbsent({
|
|
988
988
|
input: costEnv("MODEL_CHEAP_COST_INPUT", "declares-zero"),
|
|
989
989
|
output: costEnv("MODEL_CHEAP_COST_OUTPUT", "declares-zero"),
|
|
@@ -993,7 +993,6 @@ function parseModelDomain() {
|
|
|
993
993
|
contextWindow: numEnvOr("MODEL_CHEAP_CONTEXT_WINDOW", model.contextWindow),
|
|
994
994
|
maxTokens: numEnvOr("MODEL_CHEAP_MAX_TOKENS", model.maxTokens),
|
|
995
995
|
...(optFinitePositiveEnv("MODEL_CHEAP_CHARS_PER_TOKEN") !== undefined ? { charsPerToken: optFinitePositiveEnv("MODEL_CHEAP_CHARS_PER_TOKEN") } : {}),
|
|
996
|
-
reasoning: parseBoolWord("MODEL_CHEAP_REASONING", process.env.MODEL_CHEAP_REASONING) ?? model.reasoning,
|
|
997
996
|
}
|
|
998
997
|
: undefined;
|
|
999
998
|
const mainAct = optFinitePositiveEnv("MODEL_AUTO_COMPACT_TOKENS");
|
|
@@ -1183,8 +1182,15 @@ function findUnmatchableToolNames(names, opts) {
|
|
|
1183
1182
|
out.push({ name, guidance: uncompilable });
|
|
1184
1183
|
continue;
|
|
1185
1184
|
}
|
|
1186
|
-
if (verdict !== undefined)
|
|
1185
|
+
if (verdict !== undefined) {
|
|
1187
1186
|
out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
const nearMiss = compileAllowList([subject], "deny", TOOL_NAME_ROSTER).inert.find((i) => i.reason === "near_miss");
|
|
1190
|
+
if (nearMiss?.currentName !== undefined) {
|
|
1191
|
+
const rest = name.startsWith(subject) ? name.slice(subject.length) : "";
|
|
1192
|
+
out.push({ name, guidance: inertRuleEntryGuidance({ ...nearMiss, currentName: `${nearMiss.currentName}${rest}` }) });
|
|
1193
|
+
}
|
|
1188
1194
|
}
|
|
1189
1195
|
return out;
|
|
1190
1196
|
}
|
|
@@ -1192,9 +1198,10 @@ export function unmatchableToolNamesRefusal(source, names, judgment) {
|
|
|
1192
1198
|
const bad = findUnmatchableToolNames(names, judgment);
|
|
1193
1199
|
if (bad.length === 0)
|
|
1194
1200
|
return undefined;
|
|
1201
|
+
const named = bad.slice(0, MAX_NAMED_INERT_ENTRIES).map((b) => `${displayListEntry(b.name)} → ${b.guidance}`).join("; ");
|
|
1195
1202
|
const message = (`${source} has ${bad.length === 1 ? "an entry" : "entries"} that can never take effect — ${bad.length === 1 ? "it" : "each"} would never match a live tool or cannot be compiled into a rule ` +
|
|
1196
1203
|
`(names are read exactly as written: no alias folding, no protocol prefix added): ` +
|
|
1197
|
-
bad.
|
|
1204
|
+
(bad.length > MAX_NAMED_INERT_ENTRIES ? `${named}; and ${bad.length - MAX_NAMED_INERT_ENTRIES} more` : named)).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
1198
1205
|
return { message, names: bad.map((b) => b.name) };
|
|
1199
1206
|
}
|
|
1200
1207
|
const APPROVAL_GRANT_ENV = "APPROVAL_GRANT";
|
|
@@ -2521,12 +2528,6 @@ export function loadConfig() {
|
|
|
2521
2528
|
if (laneRequiredRefusal !== undefined) {
|
|
2522
2529
|
throw new Error(laneRequiredRefusal);
|
|
2523
2530
|
}
|
|
2524
|
-
{
|
|
2525
|
-
const routeGap = judgeModelRouteGap(process.env, { envLaneDispatchable: !isRosterGateArmed(integrations.webAdmin) });
|
|
2526
|
-
if (routeGap.gap !== "none") {
|
|
2527
|
-
throw new Error(routeGap.reason);
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
2531
|
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
2531
2532
|
return attachConfigGroups({
|
|
2532
2533
|
...store,
|
package/dist/elicitation.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ export interface ElicitationFrame {
|
|
|
19
19
|
* no core-stream anchor, so no `eventId` (absence is honest, not an omission); this is the frame's own time
|
|
20
20
|
* coordinate instead, same posture as the stream-approval frames' `serverNowMs`. */
|
|
21
21
|
serverNowMs?: number;
|
|
22
|
+
/** "elicitation" only (additive; the same window pair `question` carries): the coordinator's OWN answer window —
|
|
23
|
+
* at expiry the ask releases as `decline`. Minted from the SAME `ttlMs` the `setTimeout` below arms (one source), so
|
|
24
|
+
* a form replayed from the ledger can be judged still-waiting (no `elicitation_complete` with this id ∧
|
|
25
|
+
* `expiresAtMs` not reached) or lapsed even when the close row never landed (a lost write, a crashed replica). */
|
|
26
|
+
expiresInMs?: number;
|
|
27
|
+
/** "elicitation" only: `serverNowMs + expiresInMs` — the absolute deadline on the server's clock. */
|
|
28
|
+
expiresAtMs?: number;
|
|
22
29
|
}
|
|
23
30
|
/** The per-run context `onElicit` recovers via ALS. `emit` delivers a frame to the run's live stream;
|
|
24
31
|
* `abortSignal` (the run's own cancel signal) lets a run-abort promptly cancel an elicitation parked awaiting a
|
package/dist/elicitation.js
CHANGED
|
@@ -112,18 +112,33 @@ export class ElicitationCoordinator {
|
|
|
112
112
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
113
113
|
ctx.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
114
114
|
this.pending.set(id, { settle, owner: ctx.owner, taskId: ctx.taskId });
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
const emitted = (async () => {
|
|
116
|
+
const now = this.now();
|
|
117
|
+
return ctx.emit({ type: "elicitation", elicitationId: id, mcpServerName: req.server, message, ...(requestedSchema !== undefined ? { requestedSchema } : {}), mode: "form", serverNowMs: now, expiresInMs: this.throttle.ttlMs, expiresAtMs: now + this.throttle.ttlMs });
|
|
118
|
+
})().then(() => "ok", () => "failed");
|
|
119
|
+
const delivery = await Promise.race([emitted, answerP.then(() => "stalled")]);
|
|
120
|
+
if (delivery === "ok") {
|
|
117
121
|
rc.total += 1;
|
|
118
122
|
rc.lastByServer.set(req.server, t);
|
|
119
123
|
}
|
|
120
|
-
|
|
124
|
+
else if (delivery === "failed") {
|
|
121
125
|
recordFailOpen("server.elicitation.open-frame-undelivered", `elicitation=${id} server=${req.server}`);
|
|
122
126
|
settle({ action: "decline" });
|
|
123
127
|
}
|
|
124
128
|
const answer = await answerP;
|
|
125
|
-
void
|
|
126
|
-
.then(() =>
|
|
129
|
+
void emitted
|
|
130
|
+
.then((result) => {
|
|
131
|
+
if (result === "failed") {
|
|
132
|
+
if (delivery === "stalled")
|
|
133
|
+
recordFailOpen("server.elicitation.open-frame-undelivered", `elicitation=${id} server=${req.server}`);
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
if (delivery === "stalled") {
|
|
137
|
+
rc.total += 1;
|
|
138
|
+
rc.lastByServer.set(req.server, t);
|
|
139
|
+
}
|
|
140
|
+
return ctx.emit({ type: "elicitation_complete", elicitationId: id, mcpServerName: req.server, action: answer.action, serverNowMs: this.now() });
|
|
141
|
+
})
|
|
127
142
|
.catch(() => recordFailOpen("server.elicitation.complete-breadcrumb-dropped", `elicitation=${id}`));
|
|
128
143
|
return answer;
|
|
129
144
|
};
|
package/dist/exec-bounds.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 执行车道上「什么约束一条命令」的**唯一一条律**(host / e2b / k8s
|
|
2
|
+
* 执行车道上「什么约束一条命令」的**唯一一条律**(host / e2b / k8s / ssh / adb / local-docker 六条车道的 adapter 同读这一只;纯叶,零依赖):
|
|
3
3
|
*
|
|
4
4
|
* 命令由调用方**说出口**的界约束 —— 墙钟 `timeout`(buffered 与流式两面)与 / 或它自己的静默界 `readTimeoutMs`
|
|
5
5
|
* (流式面)。调用方**两样都没说**时,才由车道自己的活性界兜底:无输出满这么久即判挂死(k8s `K8S_LIVENESS_MS` /
|
|
6
|
-
* e2b `E2B_LIVENESS_MS`,缺省 {@link LIVENESS_MS_DEFAULT};host 没有这一界)—— 那时它是这条命令唯一的挂死护栏。
|
|
6
|
+
* e2b `E2B_LIVENESS_MS`,缺省 {@link LIVENESS_MS_DEFAULT};host / ssh / adb / local-docker 没有这一界)—— 那时它是这条命令唯一的挂死护栏。
|
|
7
7
|
* 切断时报的是**真正触发的那一界**(活性切断的句子只在 {@link livenessCutMessage} 铸)。
|
|
8
8
|
*
|
|
9
9
|
* 为什么「说了墙钟就不再活性切断」:墙钟本身就是挂死护栏(它到点必切),而调用方说出一个墙钟,就是在说「这条命令可以跑
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 是它报的那一界。
|
|
13
13
|
*
|
|
14
14
|
* 与 core 远端契约(`ExecStreamOptions.readTimeoutMs`:「缺省也 SHOULD 有活性界,一条命令不得等到环境死」)的对齐:
|
|
15
|
-
*
|
|
15
|
+
* 说了墙钟的命令被墙钟兜住,不会无限等;两界都没说的命令,在有活性界的车道(k8s / e2b)上仍由它兜底。切断码沿用 core 的 `timeout`(core 远端码表
|
|
16
16
|
* 把墙钟与活性两种超界都归这一词);两者靠句子区分 —— 独立的活性码属于 core 的码表。
|
|
17
17
|
*
|
|
18
18
|
* 不在本律里的界(adapter 自己的上限,不是调用方说的话,不关活性界):k8s 文件读写的数据上限 `dataTimeoutMs`、e2b 交给
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
/** 车道活性界的缺省(ms)—— k8s / e2b 两只 adapter 与配置目录的「缺席 ⇒ 默认」说法同读这一只。 */
|
|
23
23
|
export declare const LIVENESS_MS_DEFAULT = 120000;
|
|
24
|
-
/**
|
|
24
|
+
/** 有活性界的车道(k8s / e2b)各自的旋钮名(闭集;切断句点名它,运维据此知道调哪一只)。 */
|
|
25
25
|
export type LivenessKnob = "K8S_LIVENESS_MS" | "E2B_LIVENESS_MS";
|
|
26
|
-
/** 一条车道自己的活性界:k8s / e2b 有,host 没有(传 `undefined`)。只经 {@link laneLivenessOf} 铸(值已判过)。 */
|
|
26
|
+
/** 一条车道自己的活性界:k8s / e2b 有,host / ssh / adb / local-docker 没有(传 `undefined`)。只经 {@link laneLivenessOf} 铸(值已判过)。 */
|
|
27
27
|
export interface LaneLiveness {
|
|
28
28
|
readonly ms: number;
|
|
29
29
|
readonly knob: LivenessKnob;
|
|
@@ -64,6 +64,6 @@ export declare function firstBoundOf(bounds: CommandBounds): {
|
|
|
64
64
|
readonly ms: number;
|
|
65
65
|
readonly cut: "wall" | LivenessBound;
|
|
66
66
|
} | undefined;
|
|
67
|
-
/** 活性切断的句子 ——
|
|
67
|
+
/** 活性切断的句子 —— 六条车道(host / e2b / k8s / ssh / adb / local-docker)唯一铸点(车道兜底的界点名旋钮与出路;调用方自己说的界点名那一参数)。 */
|
|
68
68
|
export declare function livenessCutMessage(bound: LivenessBound): string;
|
|
69
69
|
//# sourceMappingURL=exec-bounds.d.ts.map
|
package/dist/hooks/hook-llm.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { HookLlmCall } from "./hook-runner.js";
|
|
|
5
5
|
* hookLlm(prompt 条目/session titler 的模型载体)= 直连一次非流式 completion,从 main.ts 抽出成可注入
|
|
6
6
|
* 单元(fetchImpl/keyResolver 可假),因为这条 wire 在每个默认部署上都活(titler),必须可单测。
|
|
7
7
|
*
|
|
8
|
-
* api 形分派(2026-07-15,黑板 [ref]③b
|
|
8
|
+
* api 形分派(2026-07-15,黑板 [ref]③b 真缺陷修;**选线依据 S-1630 起改为引擎主路由同一只判词**,见下):被选模型按所落的 brain 腿分家 ——
|
|
9
9
|
* - `openai-completions` ⇒ POST {base}/chat/completions(现状,字节不变);
|
|
10
10
|
* - `anthropic-messages` ⇒ POST {base}/v1/messages(x-api-key / Authorization: Bearer + anthropic-version
|
|
11
11
|
* 头,messages 数组,回包取 content[].text)。此前 hookLlm 只会说 openai 形且缺省 baseUrl 恒落
|
|
@@ -13,6 +13,16 @@ import type { HookLlmCall } from "./hook-runner.js";
|
|
|
13
13
|
* provider=anthropic 走 ANTHROPIC_BASE_URL,没配 MODEL_GATEWAY_BASEURL)下 titler 每会话打死
|
|
14
14
|
* localhost:8000 ⇒ `session_title_failed: TypeError: fetch failed`;即使端点活着,anthropic-messages
|
|
15
15
|
* 模型也不能用 /chat/completions 形调。
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **选线 = 引擎主路由同一只判词**(S-1630):「这只模型走 openai-completions 还是 anthropic-messages」不是本面的事,
|
|
18
|
+
* 是引擎主路由的事 —— `brain.ts` 按 `model.provider` 在「anthropic 路由(仅当 ANTHROPIC 凭据把它装上)/ 网关栈」之间选,
|
|
19
|
+
* 路由没装时整台机器只有网关这一只 brain,**连 `provider:"anthropic"` 的模型也落网关**(`dispatchBrainLane` 的合取,
|
|
20
|
+
* 端点判官 / 降级腿 / 启动摘要读的是同一只)。本面此前自己按 `provider === "anthropic"` / `api === "anthropic-messages"`
|
|
21
|
+
* 字面判线 ⇒ 在 `MODEL_PROVIDER=anthropic` 而只配了网关的部署上,主请求去网关、标题与 hooks 的 llm 面却要找一条没装的
|
|
22
|
+
* anthropic 路由,整条拒发(零请求,只落 fail-open 标签)。现在本面只问那一只判词,不留第二个读法 —— 含 `api` 词:
|
|
23
|
+
* 引擎不按 `api` 选线(core 的 `createRoutingBrain` 缺省按 `provider` 键),settings-schema 的 `ModelEntry` 又强制两个词同真同假。
|
|
24
|
+
* (core 目前只导出 `createRoutingBrain` 这个组合子,没有「给定模型与已装路由 → 哪条腿 / 哪个根」的判官;它导出之日,
|
|
25
|
+
* `dispatchBrainLane` 连同本调用点一并改为问它。)
|
|
16
26
|
*/
|
|
17
27
|
export type HookModelPick = {
|
|
18
28
|
ok: true;
|
package/dist/hooks/hook-llm.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { adjudicateModelRoute, joinRequestUrl, resolveRouteCredential, routeKeyOf, routeRefusalText } from "@sema-agent/core";
|
|
2
|
-
import { matchCatalogModel, refusedCatalogEntry } from "../model-select.js";
|
|
2
|
+
import { matchCatalogModel, refusedCatalogEntry, servedModelTableLeg } from "../model-select.js";
|
|
3
3
|
import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { resolveOpenaiRouteEndpoint } from "../model-route-endpoint.js";
|
|
5
|
+
import { dispatchBrainLane } from "../model-provider.js";
|
|
5
6
|
import { egressRedirectRefusal } from "../egress-redirect.js";
|
|
6
7
|
import { buildDisplaySafeRefusal, redactSecrets } from "../trace/redact.js";
|
|
7
8
|
export function buildHookFailureText(raw) {
|
|
@@ -16,21 +17,17 @@ export function buildHookFailureText(raw) {
|
|
|
16
17
|
}
|
|
17
18
|
export function createHookLlm(deps) {
|
|
18
19
|
const { config, metrics } = deps;
|
|
19
|
-
const anthropicRouteBaseUrl = () => config.anthropic
|
|
20
|
+
const anthropicRouteBaseUrl = () => config.anthropic?.baseUrl || "https://api.anthropic.com";
|
|
20
21
|
const shapeFrom = (catalogRef, m, providerModelId) => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
22
|
+
const lane = dispatchBrainLane(m?.provider, config.anthropic !== undefined);
|
|
23
|
+
switch (lane) {
|
|
24
|
+
case "anthropic":
|
|
25
|
+
return { ok: true, catalogRef, providerModelId, baseUrl: m?.baseUrl || anthropicRouteBaseUrl(), api: "anthropic-messages" };
|
|
26
|
+
case "gateway": {
|
|
27
|
+
const endpoint = resolveOpenaiRouteEndpoint(catalogRef, m, config.gatewayBaseUrl, servedModelTableLeg(config));
|
|
28
|
+
return endpoint.ok ? { ok: true, catalogRef, providerModelId, baseUrl: endpoint.baseUrl, api: "openai-completions" } : { ok: false, error: endpoint.refusal };
|
|
29
29
|
}
|
|
30
|
-
return { ok: true, catalogRef, providerModelId, baseUrl, api };
|
|
31
30
|
}
|
|
32
|
-
const endpoint = resolveOpenaiRouteEndpoint(catalogRef, m?.baseUrl, config.gatewayBaseUrl);
|
|
33
|
-
return endpoint.ok ? { ok: true, catalogRef, providerModelId, baseUrl: endpoint.baseUrl, api } : { ok: false, error: endpoint.refusal };
|
|
34
31
|
};
|
|
35
32
|
const hookModelFor = (requested) => {
|
|
36
33
|
const shape = (ref) => shapeFrom(ref, config.models[ref], config.models[ref]?.id ?? ref);
|
|
@@ -135,7 +132,7 @@ export function createHookLlm(deps) {
|
|
|
135
132
|
const r = await fetchImpl(url, { method: "POST", headers, body, signal: ac.signal, redirect: "manual" });
|
|
136
133
|
const redirected = egressRedirectRefusal(r, "declare the final endpoint as this route's base URL");
|
|
137
134
|
if (redirected !== undefined)
|
|
138
|
-
return { ok: false, error: `hook llm ${redirected}
|
|
135
|
+
return { ok: false, error: `hook llm ${redirected}`, code: "redirect_refused" };
|
|
139
136
|
if (!r.ok)
|
|
140
137
|
return { ok: false, error: `hook llm HTTP ${r.status}` };
|
|
141
138
|
let text;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type HookEntry, type HookEntryType, type HookEventName, type HooksDroppedPiece, type HooksRejection } from "@sema-agent/settings-schema/hooks";
|
|
2
|
+
import { type ConfigPath } from "@sema-agent/settings-schema";
|
|
2
3
|
import type { Hooks } from "@sema-agent/core";
|
|
3
4
|
import type { Logger } from "../observability/logger.js";
|
|
4
5
|
import type { HookNoticeObservation } from "../fleet/fleet-bus.js";
|
|
@@ -31,10 +32,45 @@ export declare const MAX_HOOK_OUTPUT_BYTES: number;
|
|
|
31
32
|
* 且 4096 的单条帽保证**前两条**钩子的话仍整段进得去(截断是配置广度的代价,不是常态)。
|
|
32
33
|
*/
|
|
33
34
|
export declare const MAX_HOOK_CONTEXT_TOTAL_CHARS = 10000;
|
|
35
|
+
/**
|
|
36
|
+
* 本仓对契约每一种 hook 条目类型的表态 —— **一张表**:受理面({@link parseHooksConfig})与执行侧({@link RunnableHookEntry},以及每个按条目类型分派的臂)读的都是它。
|
|
37
|
+
*
|
|
38
|
+
* `Record<HookEntryType, boolean>` 穷举:契约新增一种条目类型而这里没登记 ⇒ 编译红。`true` ⇒ 执行侧有这一型的臂 —— 把某一型改成 `true` 而执行侧没补臂,
|
|
39
|
+
* 分派处(`hookDisplayName` / `runMatchingEntries` 的类型分派)编译红;`false` ⇒ 本仓不跑它,受理面把含它的配置**整份拒**(提交 400),
|
|
40
|
+
* 不是记一条运维日志然后跳过:守卫事件里一条被跳过的条目,是「写的人以为在拦、其实没拦」。
|
|
41
|
+
*/
|
|
42
|
+
declare const HOOK_ENTRY_TYPE_RUNS: {
|
|
43
|
+
readonly command: true;
|
|
44
|
+
readonly http: true;
|
|
45
|
+
readonly prompt: true;
|
|
46
|
+
readonly agent: true;
|
|
47
|
+
readonly mcp_tool: false;
|
|
48
|
+
};
|
|
49
|
+
/** {@link HOOK_ENTRY_TYPE_RUNS} 里表态为 `true` 的类型(由表派生,不另写一份)。 */
|
|
50
|
+
export type SupportedHookEntryType = {
|
|
51
|
+
[T in HookEntryType]: (typeof HOOK_ENTRY_TYPE_RUNS)[T] extends true ? T : never;
|
|
52
|
+
}[HookEntryType];
|
|
53
|
+
/** 本仓会真跑的条目类型闭集(契约 {@link HOOK_ENTRY_TYPES} 的次序)—— 拒句 `supported: …` 的词表与 {@link RunnableHookEntry} 的类型都出自这一张表。 */
|
|
54
|
+
export declare const SUPPORTED_HOOK_ENTRY_TYPES: readonly SupportedHookEntryType[];
|
|
55
|
+
/** 本 runner 会真跑的条目(契约 `HookEntry` 取表态为 `true` 的那几型)。 */
|
|
56
|
+
export type RunnableHookEntry = Extract<HookEntry, {
|
|
57
|
+
type: SupportedHookEntryType;
|
|
58
|
+
}>;
|
|
59
|
+
/** 一个匹配器组,条目只含 {@link RunnableHookEntry}。 */
|
|
60
|
+
export interface RunnableMatcherGroup {
|
|
61
|
+
matcher?: string;
|
|
62
|
+
hooks: RunnableHookEntry[];
|
|
63
|
+
}
|
|
64
|
+
/** 受理面通过后的 hooks 配置:每个事件下的条目都是本仓会跑的型 —— 执行侧({@link createTaskHooks})只收这一型,「不认识的类型」到不了它手里。 */
|
|
65
|
+
export type RunnableHooksConfig = Partial<Record<HookEventName, RunnableMatcherGroup[]>>;
|
|
66
|
+
/** 分类器点名的一块(被剔的块或整份被拒的理由):码 + 坐标 + 坏字段。 */
|
|
67
|
+
export type HooksRefusalPiece = HooksDroppedPiece | HooksRejection;
|
|
34
68
|
export interface ParseHooksConfigResult {
|
|
35
|
-
config?:
|
|
36
|
-
/**
|
|
69
|
+
config?: RunnableHooksConfig;
|
|
70
|
+
/** 人可读的第一条拒因(HTTP 层用它 400 fail-loud;resume 防御路径用它 warn)。 */
|
|
37
71
|
error?: string;
|
|
72
|
+
/** 拒因来自**分类器**时,它点名的每一块(数据,文档序);本仓自己的拒(条目类型 / 服务侧上限)不带这一位。一次性迁移读它按名剥键(`stored-request-hooks-m20.ts`)—— 迁移不另起读者。 */
|
|
73
|
+
pieces?: readonly HooksRefusalPiece[];
|
|
38
74
|
}
|
|
39
75
|
/**
|
|
40
76
|
* 🔴 **S-924 → S-995(clay [ref] ③;八病审计 SG H6-d;test 7.112.0 验收 OBS ①)—— 工具事件的 matcher 选中了工具**别名**、却选不中它的正名 ⇒ fresh 提交面响亮拒。**
|
|
@@ -184,10 +220,19 @@ export declare function buildToolMatcherRefusal(issue: ToolMatcherIssue): ToolMa
|
|
|
184
220
|
* `budget` **仅供测试注入**({@link MatcherEvalBudget});生产调用点只传前两个实参。
|
|
185
221
|
* **只有 `admission.ts` 的 fresh 提交门调它** —— resume 重放不调(理由见上面的头注)。未判的 matcher 过 `recordFailOpen`(tag `MATCHER_UNJUDGED_TAG`);超预算的不是未判,是拒。
|
|
186
222
|
*/
|
|
187
|
-
export declare function toolMatcherIssue(config:
|
|
223
|
+
export declare function toolMatcherIssue(config: RunnableHooksConfig, depth: MatcherEvalDepth, budget?: MatcherEvalBudget): ToolMatcherIssue | undefined;
|
|
224
|
+
/** 坐标出门形:`settings.` + 契约包唯一的 stringify 点({@link formatConfigPath}),事件名等键段是用户文本 ⇒ 先过 {@link outgoingPath}(凭据形不原样回显)。 */
|
|
225
|
+
export declare function hooksCoordinate(path: ConfigPath): string;
|
|
188
226
|
/**
|
|
189
|
-
* 把 wire 上的 untrusted `settings.hooks`
|
|
190
|
-
*
|
|
227
|
+
* 把 wire 上的 untrusted `settings.hooks` 判成 {@link RunnableHooksConfig} + 服务侧上限 —— **hooks 配置在本仓的唯一入口**。
|
|
228
|
+
*
|
|
229
|
+
* · **一只读者**:合不合法只问契约包的 {@link classifyHooksConfig}(入参是 `{ hooks: raw }`,与契约 zod 面 `HooksConfig` 是同一读法的投影)。本仓只收 `loaded`:
|
|
230
|
+
* 一块被剔(`loaded-partial`)或整份被拒(`rejected`)都是拒 —— wire 面「剔掉坏块、其余照装」会让写的人以为生效的钩子无声消失(分类器的宽松粒度是读设置文件的宿主用的)。
|
|
231
|
+
* 拒句 = 分类器给的**码 + 坐标 + 坏字段**经 {@link hooksPieceRefusal} 铸(分类器不写句子)。
|
|
232
|
+
* · **本仓不执行的条目类型整份拒**:分类器受理契约的全部五型,而本仓只跑 {@link SUPPORTED_HOOK_ENTRY_TYPES}。含其余类型的配置整份拒,不留跳过臂。
|
|
233
|
+
* · **服务侧上限**(广度闸;契约本身不设量纲)。
|
|
234
|
+
*
|
|
235
|
+
* 错误返回 error 字符串而不 throw——submit 路径 400 fail-loud、resume 防御路径 warn-drop(hooks 整只不采纳),由调用方选;**读者与句子两腿同一只**,后果按腿分。
|
|
191
236
|
*/
|
|
192
237
|
export declare function parseHooksConfig(raw: unknown): ParseHooksConfigResult;
|
|
193
238
|
/**
|
|
@@ -270,7 +315,7 @@ export type HookLlmCall = (opts: {
|
|
|
270
315
|
}>;
|
|
271
316
|
/** 失败判别码(B8:判别一律走码,禁按 error 文案分支——v3.1 批2,统检第二波 high)。error 仍是给人看的
|
|
272
317
|
* 自由文本;code 是给控制流的。缺席=未分类失败(不重试、不特判)。 */
|
|
273
|
-
export type HookLlmFailureCode = "no_content" | "hard_timeout";
|
|
318
|
+
export type HookLlmFailureCode = "no_content" | "hard_timeout" | "redirect_refused";
|
|
274
319
|
/**
|
|
275
320
|
* hook 上下文片段 → 喂给模型的单串。**所有** `additionalContext` 聚合点的唯一属主(此前是同一个
|
|
276
321
|
* `contexts.join("\n")` 表达式抄在二十余处,总量帽无处可挂 —— 那正是漂移成因)。
|
|
@@ -320,10 +365,10 @@ export interface LlmHookExtra {
|
|
|
320
365
|
transcript?: string;
|
|
321
366
|
}
|
|
322
367
|
/**
|
|
323
|
-
* 把校验过的 {@link
|
|
368
|
+
* 把校验过的 {@link RunnableHooksConfig} 翻成 core `Hooks` 回调(阶段二:engine-owned 9 事件全点亮)。
|
|
324
369
|
* 没有可点亮的条目 → undefined(spec 不挂 hooks,deps 路径原样)。
|
|
325
370
|
*/
|
|
326
|
-
export declare function createTaskHooks(config:
|
|
371
|
+
export declare function createTaskHooks(config: RunnableHooksConfig, ctx: HookRunnerContext): Hooks | undefined;
|
|
327
372
|
/**
|
|
328
373
|
* 把部署级 hooks 基线(deps.hooks,如 TOOL_TRACE 观测)折进 task 级 hooks——core 是整槽覆盖
|
|
329
374
|
* (`spec.hooks ?? deps.hooks`),不折叠就静默 shadow 部署观测(runtask gateBaseline 注释点名的坑)。
|
|
@@ -338,4 +383,5 @@ export declare function createTaskHooks(config: HooksConfig, ctx: HookRunnerCont
|
|
|
338
383
|
* 单在则透传。
|
|
339
384
|
*/
|
|
340
385
|
export declare function composeHooks(deployment: Hooks | undefined, task: Hooks): Hooks;
|
|
386
|
+
export {};
|
|
341
387
|
//# sourceMappingURL=hook-runner.d.ts.map
|