@sema-agent/server 7.116.0 → 7.117.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +98 -0
- package/USAGE.md +32 -27
- package/dist/approval-card.d.ts +43 -9
- package/dist/approval-card.js +36 -4
- package/dist/boot/budget-tracing.js +2 -3
- package/dist/boot/config-center.d.ts +2 -2
- package/dist/boot/config-center.js +5 -7
- package/dist/boot/resolve-spec.js +10 -3
- package/dist/boot/runner-deps.d.ts +5 -3
- package/dist/boot/runner-deps.js +4 -2
- package/dist/brain.d.ts +17 -8
- package/dist/brain.js +10 -7
- package/dist/config-catalog.js +3 -2
- package/dist/config-center/apply-effective.d.ts +6 -16
- package/dist/config-center/apply-effective.js +0 -6
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/restart-signal.d.ts +1 -1
- package/dist/config-types.d.ts +12 -16
- package/dist/config.d.ts +34 -27
- package/dist/config.js +23 -19
- package/dist/deployment-governance.d.ts +11 -12
- package/dist/deployment-governance.js +4 -9
- package/dist/governance-overrides.d.ts +20 -0
- package/dist/governance-overrides.js +8 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +15 -12
- package/dist/http/checkpoint-ctx.d.ts +83 -24
- package/dist/http/checkpoint-ctx.js +68 -8
- package/dist/http/resume-legs.js +11 -15
- package/dist/http/retired-skill-manifest.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/routes/capabilities.js +3 -3
- package/dist/http/routes/runs.js +12 -0
- package/dist/model-select.d.ts +16 -5
- package/dist/model-select.js +7 -3
- package/dist/namespaced-rule-name.d.ts +7 -5
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/observability/run-terminal.js +21 -1
- package/dist/outbound-dispatcher.d.ts +18 -0
- package/dist/outbound-dispatcher.js +96 -10
- package/dist/permission-rule-list.d.ts +20 -31
- package/dist/plugins/one-time-migrations.d.ts +20 -0
- package/dist/plugins/one-time-migrations.js +84 -4
- package/dist/router/route-orchestration.d.ts +2 -23
- package/dist/rule-entry-list.d.ts +99 -0
- package/dist/rule-entry-list.js +110 -0
- package/dist/run-local.d.ts +4 -2
- package/dist/run-local.js +3 -3
- package/dist/runs.js +1 -0
- package/dist/runtime-governance.d.ts +5 -21
- package/dist/runtime-governance.js +4 -8
- package/dist/spec-fields.d.ts +15 -24
- package/dist/spec-fields.js +16 -17
- package/dist/task-settings.d.ts +18 -18
- package/dist/task-settings.js +57 -30
- package/dist/tool-approval.d.ts +24 -33
- package/dist/tool-approval.js +5 -7
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +4 -6
- package/dist/trace/engine-notice-wire.js +14 -1
- package/package.json +3 -3
- package/scripts/drain-check-local.mjs +31 -6
- package/scripts/drain-check.mysql.sql +31 -6
- package/scripts/drain-check.pg.sql +38 -8
package/dist/http/admission.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isThinkingLevel, expandTiers } from "@sema-agent/core";
|
|
1
|
+
import { MCP_IMAGE_MAX_BASE64, isThinkingLevel, expandTiers } from "@sema-agent/core";
|
|
2
2
|
import { isModelAllowlisted, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
|
|
3
3
|
import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { HttpError } from "../security.js";
|
|
@@ -8,7 +8,7 @@ import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEn
|
|
|
8
8
|
import * as facts from "../capability-facts.js";
|
|
9
9
|
import { buildDeadToolMatcherRefusal, deadToolMatcherIssue, parseHooksConfig } from "../hooks/hook-runner.js";
|
|
10
10
|
import { judgeRequestWebSearch } from "../plugins/web-search.js";
|
|
11
|
-
import {
|
|
11
|
+
import { normalizeApproachNotice, skillToolListOk, skillToolListRefusal, unknownTaskAgentModelRef, validateTaskAgents } from "../spec-fields.js";
|
|
12
12
|
import { isValidCwd, MAX_ADDITIONAL_DIRS, MAX_CWD_CHARS } from "../task-cwd.js";
|
|
13
13
|
import { verifyRoundsFromBody } from "./verify-rounds.js";
|
|
14
14
|
import { skillCarriesRetiredManifest } from "./retired-skill-manifest.js";
|
|
@@ -39,15 +39,15 @@ export function validateUserSkills(skills) {
|
|
|
39
39
|
if (s[key] !== undefined && !skillToolListOk(s[key]))
|
|
40
40
|
return `skill.${key} must be an array of strings (permission-rule spellings: a tool name, mcp__<server> / mcp__<server>__*, Bash(<prefix>:*), Read(//<dir>/**))`;
|
|
41
41
|
}
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
42
|
+
const listRefusal = skillToolListRefusal(s);
|
|
43
|
+
if (listRefusal !== undefined)
|
|
44
|
+
return listRefusal;
|
|
45
45
|
}
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
export const SKILL_MANIFEST_RETIRED = "skill.manifest was retired in core 7.41.0 — a skill's tools are its allowedTools / disallowedTools now (an allow-rule and a deny-rule source for the run the skill is activated in, never a fence); move manifest.allowTools to allowedTools. To fence tools use deny rules (settings.permissions.deny) or a delegated agent's allowTools; to fence write paths use the session rules' allowDirs";
|
|
49
49
|
const MAX_IMAGES_PER_REQUEST = 20;
|
|
50
|
-
const MAX_IMAGE_BASE64_BYTES =
|
|
50
|
+
const MAX_IMAGE_BASE64_BYTES = MCP_IMAGE_MAX_BASE64;
|
|
51
51
|
function mentionableNames(config, catalog) {
|
|
52
52
|
const allowlist = config.atModelAllowlist ?? [];
|
|
53
53
|
return Object.keys(catalog).filter((n) => n !== "default" && isModelAllowlisted(n, catalog, allowlist, config.modelRefusals));
|
|
@@ -238,7 +238,7 @@ export function createAdmission(ctx) {
|
|
|
238
238
|
for (const img of body.images) {
|
|
239
239
|
const data = img.data;
|
|
240
240
|
if (typeof data === "string" && Buffer.byteLength(data, "utf8") > MAX_IMAGE_BASE64_BYTES) {
|
|
241
|
-
sendError(res, 413, "request.payload_too_large", `each inline image must be at most ${MAX_IMAGE_BASE64_BYTES} base64 bytes`);
|
|
241
|
+
sendError(res, 413, "request.payload_too_large", `each inline image must be at most ${MAX_IMAGE_BASE64_BYTES} base64 bytes (the engine's inline-image limit — the model API's 5 MB image cap)`);
|
|
242
242
|
return null;
|
|
243
243
|
}
|
|
244
244
|
}
|
|
@@ -8,7 +8,7 @@ import { hasServiceAuth } from "./dispatch.js";
|
|
|
8
8
|
import { gatedPrincipal } from "./principal-gate.js";
|
|
9
9
|
import { readinessOf, billableRefusalOf } from "./readiness.js";
|
|
10
10
|
import { readResumeCtx } from "./resume-ctx-read.js";
|
|
11
|
-
import {
|
|
11
|
+
import { storedBodyReplayRefusal } from "./checkpoint-ctx.js";
|
|
12
12
|
const BLOCKED_SCALAR_KEYS = ["errorCode", "blockedBy", "field", "reason", "count", "cap", "index"];
|
|
13
13
|
function blockedScalars(body) {
|
|
14
14
|
const out = {};
|
|
@@ -66,21 +66,24 @@ export function createApprovalSweeps(ctx) {
|
|
|
66
66
|
return false;
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
|
-
const
|
|
69
|
+
const storedBodyUnservable = async (sessionId) => {
|
|
70
70
|
const read = await readResumeCtx(cs.getCtx(sessionId));
|
|
71
71
|
if (read.kind === "corrupt")
|
|
72
|
-
return
|
|
73
|
-
|
|
72
|
+
return undefined;
|
|
73
|
+
if (read.kind === "absent")
|
|
74
|
+
return undefined;
|
|
75
|
+
return storedBodyReplayRefusal(read.ctx);
|
|
74
76
|
};
|
|
75
77
|
let parkedSkipped = 0;
|
|
76
|
-
const
|
|
78
|
+
const replaySkipped = [];
|
|
77
79
|
for (const { sessionId } of expired) {
|
|
78
80
|
if (await isParkedOwned(sessionId)) {
|
|
79
81
|
parkedSkipped += 1;
|
|
80
82
|
continue;
|
|
81
83
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
const unservable = await storedBodyUnservable(sessionId);
|
|
85
|
+
if (unservable !== undefined) {
|
|
86
|
+
replaySkipped.push({ sessionId, kind: unservable.kind });
|
|
84
87
|
continue;
|
|
85
88
|
}
|
|
86
89
|
await resumeCheckpoint(sessionId, "deny", "approval SLA expired — auto-denied", { decidedBy: "sla_timeout" }).then((r) => { if (r.status >= 400)
|
|
@@ -88,11 +91,11 @@ export function createApprovalSweeps(ctx) {
|
|
|
88
91
|
}
|
|
89
92
|
if (parkedSkipped > 0)
|
|
90
93
|
deps.logger?.info?.("deny_sweep_parked_skipped", { count: parkedSkipped });
|
|
91
|
-
if (
|
|
92
|
-
deps.logger?.warn?.("
|
|
93
|
-
count:
|
|
94
|
-
|
|
95
|
-
note: "these expired approvals belong to tasks
|
|
94
|
+
if (replaySkipped.length > 0) {
|
|
95
|
+
deps.logger?.warn?.("deny_sweep_stored_body_skipped", {
|
|
96
|
+
count: replaySkipped.length,
|
|
97
|
+
skipped: replaySkipped,
|
|
98
|
+
note: "these expired approvals belong to tasks whose stored request this version does not replay (kind allow_list_reading: settings.permissions.allow written under an earlier reading; kind skill_deny_inert: a skill disallowedTools entry the engine now reads as empty), so the SLA deny cannot be delivered. They stay suspended until the terminal backstop reaps them or someone cancels them; nothing was retried",
|
|
96
99
|
});
|
|
97
100
|
}
|
|
98
101
|
}
|
|
@@ -1,43 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 续跑重建输入(`checkpoint_ctx` 的行体,{@link CheckpointCtx})的**写形**,以及持久化体里 `settings.permissions.allow`
|
|
2
|
+
* 续跑重建输入(`checkpoint_ctx` 的行体,{@link CheckpointCtx})的**写形**,以及持久化体里 `settings.permissions.allow` 的**读法记号**。
|
|
3
3
|
*
|
|
4
|
-
* ## 病
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* ## 病(两代,同一形)
|
|
5
|
+
* `settings.permissions.allow` 的读法换过两次:≤7.114.0 是「名单」(名单外的工具一律拒);7.115.0–7.116.x 是「表态」(只回答没人表态的那一问,
|
|
6
|
+
* 写 / shell 这类普通问照问);7.117.0 起整份交给引擎的 allow 层(`TaskSpec.allowedTools`:表里的调用免问 —— 普通问与没人表态两类都清,内容形也收)。
|
|
7
|
+
* 续跑家族重建 spec 时把**持久化的提交体**原样重放进 `resolveSpec`(`resume-legs.ts` 的 `resolveResumeSpec`)—— 一条在旧读法下停驻、体里带非空 `allow`
|
|
8
|
+
* 的行,升级后续跑就被按新读法读,每一次换代都是放宽方向(名单 → 表态:名单外的工具放开;表态 → allow 层:普通问不再问)。持久面上的静默语义翻转。
|
|
9
9
|
*
|
|
10
10
|
* ## 规则(一条;持久数据的「响亮拒 + 一次性迁移」,零双读)
|
|
11
|
-
* 续跑重放持久化体之前问一句:**体里的 `allow` 在生效(非空数组)
|
|
11
|
+
* 续跑重放持久化体之前问一句:**体里的 `allow` 在生效(非空数组)而本行的读法记号不是当前这一个词 ⇒ 拒**。
|
|
12
12
|
* 行留停驻、字节不动(拒发生在认领之前,run 行、卡、ctx 一个字节都不写);出路 = 作为新任务重提,这条任务若还停驻着先取消(`task_done` 唤醒停驻与 workflow 宿主那条腿没有可取消的 run)。
|
|
13
|
-
*
|
|
14
|
-
* · 记号 =
|
|
13
|
+
* **没有**「按旧读法继续读」的臂(那是第二种读法),也**没有**「按新读法静默续跑」的臂(那是对一条已提交任务的静默放宽)。
|
|
14
|
+
* · 记号 = 当前写点在体里带非空 `allow` 时写在 ctx 上的 `permissionsAllowReading`,闭集只有当前读法一个词({@link PERMISSIONS_ALLOW_READINGS})。
|
|
15
15
|
* 写点 = 每一次**新的**持久化写({@link buildCheckpointCtx} 是它们的唯一构造口);续跑腿的写回传的是读进来的那只 ctx(或其展开),记号随行,不重新判。
|
|
16
|
-
* ·
|
|
16
|
+
* · 拒句按「这行是在哪种读法下写的」分两句({@link ALLOW_LIST_READING_ERAS} 穷举):无记号(或记号认不得)= 名单时代;`"statement"` = 表态时代。
|
|
17
|
+
* · 体里没有 `allow`(或空数组 —— 三代都把空数组读成「没有」)的行不写记号、不受本规则影响。
|
|
17
18
|
*
|
|
18
|
-
* ##
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* ## 第二问(7.117.0,引擎 7.42 逐字读规则条目):技能拒侧读成空的存量体
|
|
20
|
+
* 同一面上的第二个「新提交会被拒、存量体重放不能放过」:提交体里技能(顶层 / 子代)的 `disallowedTools` 有引擎读成空的条目(`"Bash "` 这类 7.41 去白后有效、
|
|
21
|
+
* 7.42 什么也不拒的条目)⇒ 提交 400(`resolveSpec`,拒点);存量停驻体重放 ⇒ 409 `resume_blocked_by_policy`。判据函数 = `rule-entry-list.ts` `skillDenyInertEntryOf`。
|
|
22
|
+
*
|
|
23
|
+
* ## 一只判据、五个问点({@link storedBodyReplayRefusal} / {@link storedBodyReplayAnswer})
|
|
24
|
+
* 「这条停驻行的持久化体在本版上能否重放」只有**一个**读法:上面两问合在 {@link storedBodyReplayRefusal} 一处,答复合在 {@link storedBodyReplayAnswer} 一处。
|
|
25
|
+
* 问点五个,同调同答:续跑驱动段(`resume-legs.ts` `resolveResumeSpecOrBlocked`,另四条腿的唯一判点)· plan_review 的 prepare 段(读类拒,在 nonce 消费之前)·
|
|
26
|
+
* SLA 拒扫腿的跳过臂(`approval-sweeps.ts`)· 终态 run 的 steer 铸唤醒卡之前(`routes/runs.ts`:铸卡前先证明它赎得回,证不出 ⇒ 不铸、当场同一个拒)·
|
|
27
|
+
* `resolveSpec` 本身留作真正的拒点(提交腿 400;续跑腿在到它之前已被本判据答掉)。再开第六个问点必须调这两只,不许各判各的 —— 此前铸卡前只判了
|
|
28
|
+
* allow 记号,技能拒侧那一问漏在外面,卡铸出来了、`/wake` 恒 409([ref] 合并复审两腿同报)。
|
|
21
29
|
*
|
|
22
30
|
* ## 何时删(一次性迁移,不是长期双读)
|
|
23
|
-
* 本判据、拒臂、记号写点与 `CheckpointCtx.permissionsAllowReading`
|
|
24
|
-
* (
|
|
25
|
-
*
|
|
31
|
+
* 本判据、拒臂、记号写点与 `CheckpointCtx.permissionsAllowReading` 一起删,删除条件与窗登记在 `plugins/one-time-migrations.ts` 的
|
|
32
|
+
* 一次性迁移登记表(本条那一行):所有旧读法下停驻的行都已结束或被收割(停驻行的终局 = 落盘起 `TERMINAL_BACKSTOP_MS`,见
|
|
33
|
+
* `plugins/checkpoint-store-sql.ts`)之后的那个版本。删的时候记号的读者一起删。
|
|
26
34
|
*/
|
|
27
35
|
import type { CheckpointCtx } from "./route-ctx.js";
|
|
28
36
|
import type { TaskRequestBody } from "./wire-types.js";
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
import { type SkillDenyInertEntry } from "../rule-entry-list.js";
|
|
38
|
+
/** 读法记号的闭集(一词 = 当前读法):这一行体里的 `settings.permissions.allow` 是按「交给引擎的 allow 层」写下、也该按它读的
|
|
39
|
+
* (与能力位 `settings.permissions.allowLayer` 同一个说法)。 */
|
|
40
|
+
export declare const PERMISSIONS_ALLOW_READINGS: readonly ["allow_layer"];
|
|
31
41
|
export type PermissionsAllowReading = (typeof PERMISSIONS_ALLOW_READINGS)[number];
|
|
42
|
+
/** 被拒的行是在哪种读法下写的(闭集;拒句按它穷举):`list` = 无记号或记号认不得(≤7.114.0 的名单读法;认不得的记号不是任何一代的证据,
|
|
43
|
+
* 按最早那一代说话);`statement` = 7.115.0–7.116.x 写下的表态读法。 */
|
|
44
|
+
export declare const ALLOW_LIST_READING_ERAS: readonly ["list", "statement"];
|
|
45
|
+
export type AllowListReadingEra = (typeof ALLOW_LIST_READING_ERAS)[number];
|
|
32
46
|
/**
|
|
33
|
-
* 体里的 `settings.permissions.allow` 是否**在生效**:非空数组(
|
|
47
|
+
* 体里的 `settings.permissions.allow` 是否**在生效**:非空数组(三代同一个「在」—— 空数组 = 没有)。
|
|
34
48
|
* 入参是持久化的 JSON(可能来自任何旧版本),逐层判形,不信型面。
|
|
35
49
|
*/
|
|
36
50
|
export declare function bodyCarriesAllowList(body: TaskRequestBody): boolean;
|
|
37
51
|
/**
|
|
38
52
|
* 一次**新的**持久化写的 ctx(提交腿 / `POST /v1/runs` 族 / leader worker)—— 写记号的唯一口。
|
|
39
53
|
*
|
|
40
|
-
*
|
|
54
|
+
* 调用方是「本进程刚按当前读法读过这份体」的写点:fresh 提交(受理门按当前读法读过 `allow`),或已经过了续跑拒臂的重放腿
|
|
41
55
|
* (`startFreshHostTurn` 把宿主的存量体交给 `POST /v1/runs` 同一条链 —— 它在那之前调过 `resolveResumeSpecOrBlocked`,没记号却带 `allow` 的体到不了这里)。
|
|
42
56
|
* 续跑腿的**写回**不经本函数:它传读进来的 ctx(或其展开),记号随行;在写回口重新判会把一条没记号的旧行「洗」成新行。
|
|
43
57
|
*/
|
|
@@ -47,17 +61,20 @@ export declare function buildCheckpointCtx(fields: {
|
|
|
47
61
|
unattendedLeg?: true;
|
|
48
62
|
}): CheckpointCtx;
|
|
49
63
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
64
|
+
* 本行能否被按当前读法重放:体里 `allow` 在生效而行上的记号不是当前这一个词 ⇒ **不能**(`true` = 拒)。
|
|
65
|
+
* 记号缺席、是旧词(`"statement"`)或认不得(手改 / 将来的写者)⇒ 同样拒:只有当前这个词是「按当前读法写下」的证据。
|
|
52
66
|
*/
|
|
53
67
|
export declare function allowListReadingUnstated(ctx: CheckpointCtx): boolean;
|
|
68
|
+
/** 被拒的行是哪一代写的(只在 {@link allowListReadingUnstated} 为真时有意义):记号是 `"statement"` ⇒ 表态时代;其余(缺席 / 认不得)⇒ 名单时代。 */
|
|
69
|
+
export declare function allowListReadingEraOf(ctx: CheckpointCtx): AllowListReadingEra;
|
|
54
70
|
/**
|
|
55
71
|
* 拒的答复 —— 码与落点照「退役 mode-seed 行」的先例(`checkpoint.unsupported_version`,409,`retriable:true` = 那张卡仍 pending;**不是**「原样重试会成功」)。
|
|
56
|
-
* 拒在认领之前,所以没有 `taskId`(行没抢到就不造键)
|
|
72
|
+
* 拒在认领之前,所以没有 `taskId`(行没抢到就不造键)。句子按这一行是哪一代写的分两句(闭集穷举,加一代 ⇒ 编译红):说原因(这条任务停在哪种读法上、
|
|
73
|
+
* 照当前读法续跑会放宽什么)与出路(作为新任务重提,仍停驻的先取消)。
|
|
57
74
|
*
|
|
58
75
|
* 🔴 形是给冻结门看的(`api-error-text-freeze` 的 resume 家族收集器):`status: <三位数>, body: { error: "<整句双引号字面>", errorCode: "<码>"`,中间只许有空白。
|
|
59
76
|
*/
|
|
60
|
-
export declare function allowListReadingRefusal(): {
|
|
77
|
+
export declare function allowListReadingRefusal(era: AllowListReadingEra): {
|
|
61
78
|
status: number;
|
|
62
79
|
body: {
|
|
63
80
|
error: string;
|
|
@@ -65,4 +82,46 @@ export declare function allowListReadingRefusal(): {
|
|
|
65
82
|
retriable: true;
|
|
66
83
|
};
|
|
67
84
|
};
|
|
85
|
+
/** 一条停驻行的持久化体在本版上**不能**重放时,是哪一条规则拒的(闭集;加一条 ⇒ {@link storedBodyReplayAnswer} 的 switch 编译红)。 */
|
|
86
|
+
export type StoredBodyReplayRefusal = {
|
|
87
|
+
readonly kind: "allow_list_reading";
|
|
88
|
+
readonly era: AllowListReadingEra;
|
|
89
|
+
} | {
|
|
90
|
+
readonly kind: "skill_deny_inert";
|
|
91
|
+
readonly entry: SkillDenyInertEntry;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* 「存量体能否重放」的**唯一**判据(文件头「一只判据、五个问点」):不能 ⇒ 返回拒的那条规则;能 ⇒ `undefined`。
|
|
95
|
+
* 顺序:先 allow 读法记号,再技能拒侧读成空(两问都中时答第一问 —— 任一问中都不重放,句子只需一句)。
|
|
96
|
+
*/
|
|
97
|
+
export declare function storedBodyReplayRefusal(ctx: CheckpointCtx): StoredBodyReplayRefusal | undefined;
|
|
98
|
+
/** 续跑家族交回的拒答复形(`{ status, body }`,由 `sendResumeOutcome` / 各路由发送;`body` 至少 `error` + `errorCode`)。 */
|
|
99
|
+
export interface ResumeRefusalAnswer {
|
|
100
|
+
readonly status: number;
|
|
101
|
+
readonly body: {
|
|
102
|
+
readonly error: string;
|
|
103
|
+
readonly errorCode: string;
|
|
104
|
+
} & Record<string, unknown>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 「续跑被一条准入策略挡住」的**唯一**铸点:`resolveSpec` 在重放存量体时抛出的 `HttpError`(400 族)折成 409 `resume_blocked_by_policy`,
|
|
108
|
+
* `blockedBy` = 那条 400 的码(属主 `http/send.ts` `httpErrorCode`),`extra` 原样随体。{@link storedBodyReplayAnswer} 的技能拒侧那一臂与
|
|
109
|
+
* `resume-legs.ts` 的折叠口都调它 —— 句子与码只在这里写一次(冻结门 `api-error-text-freeze` 的模板骨架表锚这一处)。
|
|
110
|
+
*/
|
|
111
|
+
export declare function resumeBlockedByPolicyAnswer(message: string, blockedBy: string, extra?: Record<string, unknown> | undefined): ResumeRefusalAnswer;
|
|
112
|
+
/**
|
|
113
|
+
* {@link storedBodyReplayRefusal} 的**日志字段**(三处问点同一形,各自只再加坐标键 `sessionId` / `taskId`):`kind` 判别,allow 臂带 `era`、技能臂带 `layer`,`note` 一句出路。
|
|
114
|
+
* 事件名三个(问点各一:`resume_refused_stored_body` / `steer_wake_park_refused_stored_body` / `deny_sweep_stored_body_skipped`),字段形只有这一只。
|
|
115
|
+
*/
|
|
116
|
+
export declare function storedBodyReplayLogFields(refusal: StoredBodyReplayRefusal): {
|
|
117
|
+
kind: StoredBodyReplayRefusal["kind"];
|
|
118
|
+
era?: AllowListReadingEra;
|
|
119
|
+
layer?: SkillDenyInertEntry["layer"];
|
|
120
|
+
note: string;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* {@link storedBodyReplayRefusal} 的答复:每条规则一只既有铸点(allow 记号 ⇒ {@link allowListReadingRefusal};技能拒侧 ⇒ {@link resumeBlockedByPolicyAnswer},与提交腿的 400
|
|
124
|
+
* `request.field_invalid` 同码同句 —— 体上只有这一个问题时;存量体同时还踩中 `resolveSpec` 更早的拒〔三桶超帽 / 托管姿态〕时,提交侧答更早那条、续跑侧答本句,两边都是拒,指路不同)。
|
|
125
|
+
*/
|
|
126
|
+
export declare function storedBodyReplayAnswer(refusal: StoredBodyReplayRefusal): ResumeRefusalAnswer;
|
|
68
127
|
//# sourceMappingURL=checkpoint-ctx.d.ts.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { skillDenyInertEntryOf } from "../rule-entry-list.js";
|
|
2
|
+
export const PERMISSIONS_ALLOW_READINGS = ["allow_layer"];
|
|
3
|
+
export const ALLOW_LIST_READING_ERAS = ["list", "statement"];
|
|
2
4
|
export function bodyCarriesAllowList(body) {
|
|
3
5
|
const settings = body?.settings;
|
|
4
6
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings))
|
|
@@ -14,7 +16,7 @@ export function buildCheckpointCtx(fields) {
|
|
|
14
16
|
body: fields.body,
|
|
15
17
|
...(fields.memoryScope !== undefined ? { memoryScope: fields.memoryScope } : {}),
|
|
16
18
|
...(fields.unattendedLeg === true ? { unattendedLeg: true } : {}),
|
|
17
|
-
...(bodyCarriesAllowList(fields.body) ? { permissionsAllowReading: "
|
|
19
|
+
...(bodyCarriesAllowList(fields.body) ? { permissionsAllowReading: "allow_layer" } : {}),
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
export function allowListReadingUnstated(ctx) {
|
|
@@ -23,14 +25,72 @@ export function allowListReadingUnstated(ctx) {
|
|
|
23
25
|
const reading = ctx.permissionsAllowReading;
|
|
24
26
|
return !(typeof reading === "string" && PERMISSIONS_ALLOW_READINGS.includes(reading));
|
|
25
27
|
}
|
|
26
|
-
export function
|
|
28
|
+
export function allowListReadingEraOf(ctx) {
|
|
29
|
+
const reading = ctx.permissionsAllowReading;
|
|
30
|
+
return reading === "statement" ? "statement" : "list";
|
|
31
|
+
}
|
|
32
|
+
export function allowListReadingRefusal(era) {
|
|
33
|
+
switch (era) {
|
|
34
|
+
case "list":
|
|
35
|
+
return {
|
|
36
|
+
status: 409,
|
|
37
|
+
body: {
|
|
38
|
+
error: "this task was suspended by an earlier server version that did not record how it read settings.permissions.allow (7.114.0 and earlier read it as a whitelist: every tool not on the list was refused); this version hands that list to the engine's allow layer, where each entry is a standing yes for the calls it covers, so continuing would give the task more tools than it was submitted with. Nothing was decided and nothing was resumed, and retrying will be refused the same way: submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel). To allow only certain tools, use the session rules' toolAllow (PUT /v1/sessions/:id/policy) or the request's excludeTools.",
|
|
39
|
+
errorCode: "checkpoint.unsupported_version",
|
|
40
|
+
retriable: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
case "statement":
|
|
44
|
+
return {
|
|
45
|
+
status: 409,
|
|
46
|
+
body: {
|
|
47
|
+
error: "this task was suspended by an earlier server version (7.115.0 to 7.116.x) that read settings.permissions.allow as a statement that the named tools are allowed, which answered only the question nobody else had answered and never cleared an ordinary question such as a file write or a shell command; this version hands that list to the engine's allow layer, where an entry also clears those ordinary questions, so continuing would run calls without asking that the task was submitted to ask about. Nothing was decided and nothing was resumed, and retrying will be refused the same way: submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel).",
|
|
48
|
+
errorCode: "checkpoint.unsupported_version",
|
|
49
|
+
retriable: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
default: {
|
|
53
|
+
const unhandled = era;
|
|
54
|
+
throw new Error(`allow-list reading era ${String(unhandled)} has no refusal sentence`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function storedBodyReplayRefusal(ctx) {
|
|
59
|
+
if (allowListReadingUnstated(ctx))
|
|
60
|
+
return { kind: "allow_list_reading", era: allowListReadingEraOf(ctx) };
|
|
61
|
+
const entry = skillDenyInertEntryOf(ctx.body);
|
|
62
|
+
if (entry !== undefined)
|
|
63
|
+
return { kind: "skill_deny_inert", entry };
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
export function resumeBlockedByPolicyAnswer(message, blockedBy, extra) {
|
|
27
67
|
return {
|
|
28
68
|
status: 409,
|
|
29
|
-
body: {
|
|
30
|
-
error: "this task was suspended by an earlier server version, which read settings.permissions.allow as a whitelist (every tool not on the list was refused); this version reads that list as a statement that the named tools are allowed, not as a whitelist, so continuing would give the task more tools than it was submitted with. Nothing was decided and nothing was resumed, and retrying will be refused the same way: submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel). To allow only certain tools, use the session rules' toolAllow (PUT /v1/sessions/:id/policy) or the request's excludeTools.",
|
|
31
|
-
errorCode: "checkpoint.unsupported_version",
|
|
32
|
-
retriable: true,
|
|
33
|
-
},
|
|
69
|
+
body: { error: `resume blocked by an admission policy change: ${message}`, errorCode: "resume_blocked_by_policy", blockedBy, ...(extra ?? {}) },
|
|
34
70
|
};
|
|
35
71
|
}
|
|
72
|
+
export function storedBodyReplayLogFields(refusal) {
|
|
73
|
+
switch (refusal.kind) {
|
|
74
|
+
case "allow_list_reading":
|
|
75
|
+
return { kind: refusal.kind, era: refusal.era, note: "this session's stored request carries settings.permissions.allow written under an earlier reading (list era: a whitelist; statement era: a statement that cleared only the unstated question); it is not replayed under the current reading — the task stays suspended until someone cancels it" };
|
|
76
|
+
case "skill_deny_inert":
|
|
77
|
+
return { kind: refusal.kind, layer: refusal.entry.layer, note: "this session's stored request carries a skill disallowedTools entry the engine now reads as empty (an earlier engine trimmed it into a working deny); it is not replayed — the task stays suspended until someone cancels it and resubmits with the entry fixed" };
|
|
78
|
+
default: {
|
|
79
|
+
const unhandled = refusal;
|
|
80
|
+
throw new Error(`stored-body replay refusal ${JSON.stringify(unhandled)} has no log fields`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function storedBodyReplayAnswer(refusal) {
|
|
85
|
+
switch (refusal.kind) {
|
|
86
|
+
case "allow_list_reading":
|
|
87
|
+
return allowListReadingRefusal(refusal.era);
|
|
88
|
+
case "skill_deny_inert":
|
|
89
|
+
return resumeBlockedByPolicyAnswer(refusal.entry.message, "request.field_invalid");
|
|
90
|
+
default: {
|
|
91
|
+
const unhandled = refusal;
|
|
92
|
+
throw new Error(`stored-body replay refusal ${JSON.stringify(unhandled)} has no answer`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
36
96
|
//# sourceMappingURL=checkpoint-ctx.js.map
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -42,7 +42,7 @@ import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js
|
|
|
42
42
|
import { commitSessionInheritEnv } from "./session-inherit-env.js";
|
|
43
43
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
44
44
|
import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
45
|
-
import {
|
|
45
|
+
import { resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields, storedBodyReplayRefusal } from "./checkpoint-ctx.js";
|
|
46
46
|
import { httpErrorCode } from "./send.js";
|
|
47
47
|
function isCheckpointReopenedFailure(r) {
|
|
48
48
|
const code = failureCodeOf(r.terminal);
|
|
@@ -408,26 +408,21 @@ export function createResumeLegs(ctx) {
|
|
|
408
408
|
});
|
|
409
409
|
return { spec, engineNotices };
|
|
410
410
|
};
|
|
411
|
-
const
|
|
412
|
-
deps.logger?.warn?.("
|
|
413
|
-
return
|
|
411
|
+
const refuseStoredBodyReplay = (sessionId, refusal) => {
|
|
412
|
+
deps.logger?.warn?.("resume_refused_stored_body", { sessionId, ...storedBodyReplayLogFields(refusal) });
|
|
413
|
+
return storedBodyReplayAnswer(refusal);
|
|
414
414
|
};
|
|
415
415
|
const resolveResumeSpecOrBlocked = async (ctx, req, auth) => {
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
const replay = storedBodyReplayRefusal(ctx);
|
|
417
|
+
if (replay !== undefined)
|
|
418
|
+
return { ok: false, answer: refuseStoredBodyReplay(auth.sessionId, replay) };
|
|
418
419
|
try {
|
|
419
420
|
const { spec, engineNotices } = await resolveResumeSpec(ctx, req, auth);
|
|
420
421
|
return { ok: true, spec, engineNotices };
|
|
421
422
|
}
|
|
422
423
|
catch (e) {
|
|
423
424
|
if (e instanceof HttpError) {
|
|
424
|
-
return {
|
|
425
|
-
ok: false,
|
|
426
|
-
answer: {
|
|
427
|
-
status: 409,
|
|
428
|
-
body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", blockedBy: httpErrorCode(e.status, e.code), ...(e.extra ?? {}) },
|
|
429
|
-
},
|
|
430
|
-
};
|
|
425
|
+
return { ok: false, answer: resumeBlockedByPolicyAnswer(e.message, httpErrorCode(e.status, e.code), e.extra) };
|
|
431
426
|
}
|
|
432
427
|
throw e;
|
|
433
428
|
}
|
|
@@ -1348,8 +1343,9 @@ export function createResumeLegs(ctx) {
|
|
|
1348
1343
|
return refuse(resumeCtxCorruptAnswer(ctxRead.where));
|
|
1349
1344
|
if (ctxRead.kind === "absent")
|
|
1350
1345
|
return refuse({ status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } });
|
|
1351
|
-
|
|
1352
|
-
|
|
1346
|
+
const replay = storedBodyReplayRefusal(ctxRead.ctx);
|
|
1347
|
+
if (replay !== undefined)
|
|
1348
|
+
return refuse(refuseStoredBodyReplay(sessionId, replay));
|
|
1353
1349
|
return { ok: true, prepared: { sessionId, token, cp, ctx: ctxRead.ctx } };
|
|
1354
1350
|
}
|
|
1355
1351
|
async function drivePlanReview(prepared, decision, editedPlan, reason, req, acceptEarly, permissionModeAfter) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 「带」= 键在场、值是什么都算(`null` / 空对象同样是「我带了 manifest」)—— 与引擎的退役键表读法同形(`RETIRED_TASK_SPEC_KEYS["skills[].manifest"]`:`at !== undefined`)。
|
|
10
10
|
* 入参是持久化的 JSON(可能来自任何旧版本),逐层判形,不信型面。
|
|
11
11
|
*
|
|
12
|
-
* 🔴 本模块**零运行期 import**(
|
|
12
|
+
* 🔴 本模块**零运行期 import**(随包脚本要在没有服务其余部分的位置加载它;`checkpoint-ctx.js` 7.117.0 起经 `rule-entry-list.js` 依赖引擎包根 —— 包形里有它)—— 往里加 import 要先想想脚本那一头。
|
|
13
13
|
*/
|
|
14
14
|
/** 一条技能(提交体 `skills[]` / `agents[].skills[]` 的元素)是否还带退役的 `manifest` 键。 */
|
|
15
15
|
export declare function skillCarriesRetiredManifest(skill: unknown): boolean;
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -162,9 +162,9 @@ export interface CheckpointCtx {
|
|
|
162
162
|
*/
|
|
163
163
|
unattendedLeg?: true;
|
|
164
164
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
165
|
+
* 体里 `settings.permissions.allow` 的**读法记号**(闭集只有当前读法一词 `"allow_layer"`):在场 = 这一行是当前读法下写下的、体里带非空 `allow`,
|
|
166
|
+
* 它按「交给引擎的 allow 层」写、也按它读。**服务端写、不来自请求体**(与 {@link unattendedLeg} 同为 `body` 的兄弟键)。
|
|
167
|
+
* 缺席(≤7.114.0,名单时代)或旧词 `"statement"`(7.115.0–7.116.x,表态时代)而体里带非空 `allow` ⇒ 续跑腿响亮拒、行字节不动(规则与删除条件的属主 = `http/checkpoint-ctx.ts` 顶注)。
|
|
168
168
|
* 写点只有 `buildCheckpointCtx`(同模块;新的持久化写);续跑腿的写回传原 ctx / 其展开 ⇒ 记号随行。体里没有 `allow` 的行不写它。
|
|
169
169
|
*/
|
|
170
170
|
permissionsAllowReading?: import("./checkpoint-ctx.js").PermissionsAllowReading;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
|
-
import { isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
2
|
+
import { modelImageInput, isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
3
3
|
import { modelKeyPoisonOf, resolveModelApiKey } from "../../key-resolver.js";
|
|
4
4
|
import { projectSealedKeyCapability } from "../../sealed-key.js";
|
|
5
5
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
@@ -120,7 +120,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
120
120
|
subagentSteer: facts.subagentSteer(deps).available,
|
|
121
121
|
subagentResume: facts.subagentSteer(deps).available,
|
|
122
122
|
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: taskHooksHonored(deps.config) },
|
|
123
|
-
settings: { permissions: { allowGrant: true } },
|
|
123
|
+
settings: { permissions: { allowGrant: true, allowLayer: true } },
|
|
124
124
|
permissionModeAuto: {
|
|
125
125
|
accepted: true,
|
|
126
126
|
classifierSeat: deps.autoModeFace?.seatMounted === true,
|
|
@@ -238,7 +238,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
238
238
|
id: m.id,
|
|
239
239
|
provider: m.provider,
|
|
240
240
|
reasoning: m.reasoning,
|
|
241
|
-
vision:
|
|
241
|
+
...(modelImageInput(m) !== "undeclared" ? { vision: modelImageInput(m) === "yes" } : {}),
|
|
242
242
|
...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
|
|
243
243
|
...(m.maxTokens ? { maxOutputTokens: m.maxTokens } : {}),
|
|
244
244
|
...((levels) => (levels !== undefined ? { supportedEffortLevels: [...levels] } : {}))(reasoningEffortLevelsOf(m)),
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -30,6 +30,7 @@ import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
|
30
30
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
31
31
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
32
32
|
import { readResumeCtx } from "../resume-ctx-read.js";
|
|
33
|
+
import { storedBodyReplayAnswer, storedBodyReplayLogFields, storedBodyReplayRefusal } from "../checkpoint-ctx.js";
|
|
33
34
|
const ASK_PARKING_RETRY_AFTER_SEC = 2;
|
|
34
35
|
const ASK_ACTOR_LABEL_MAX = 256;
|
|
35
36
|
const AskDecisionActorSchema = ActorAssertionWire.strip();
|
|
@@ -634,6 +635,11 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
634
635
|
mintResult.noResumeCtx = true;
|
|
635
636
|
return;
|
|
636
637
|
}
|
|
638
|
+
const unservable = storedBodyReplayRefusal(resumeRead.ctx);
|
|
639
|
+
if (unservable !== undefined) {
|
|
640
|
+
mintResult.unservable = unservable;
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
637
643
|
const cpScope = encodeCheckpointScope(run.owner);
|
|
638
644
|
const wakeToken = mintCheckpointToken();
|
|
639
645
|
await cs.put(wakeToken, {
|
|
@@ -688,6 +694,12 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
688
694
|
sendError(res, 500, "corrupt", "run already ended and this session's stored resume context cannot be read — no wake card was minted and nothing was queued. Ask whoever operates this deployment to repair or remove the row named in `where` before steering this session again.", { where: mintResult.corruptCtxWhere });
|
|
689
695
|
return;
|
|
690
696
|
}
|
|
697
|
+
if (mintResult.unservable !== undefined) {
|
|
698
|
+
deps.logger?.warn?.("steer_wake_park_refused_stored_body", { taskId, sessionId: run.sessionId, ...storedBodyReplayLogFields(mintResult.unservable) });
|
|
699
|
+
const { status, body: { error, errorCode, ...rest } } = storedBodyReplayAnswer(mintResult.unservable);
|
|
700
|
+
sendError(res, status, errorCode, error, rest);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
691
703
|
if (mintResult.noResumeCtx) {
|
|
692
704
|
sendError(res, 409, "conflict.resume_context_unavailable", "run already ended and this session's resume context is gone — a steer queued now could never be delivered by POST /v1/sessions/:id/wake; submit a new task instead");
|
|
693
705
|
return;
|
package/dist/model-select.d.ts
CHANGED
|
@@ -136,12 +136,23 @@ export declare function terminalCatalogName(ref: string, catalog: Record<string,
|
|
|
136
136
|
* 它自己)。命中保留名 ⇒ undefined,调用方接着问 {@link refusedCatalogEntry} 拿拒因码。
|
|
137
137
|
*/
|
|
138
138
|
export declare function matchCatalogModel(ref: string, catalog: Record<string, unknown>, refusals?: Readonly<Record<string, ModelEntryRefusal>>): string | undefined;
|
|
139
|
+
/** 「这个目录条目收不收图」的三态(闭集):声明了 `input` 且含 `"image"` / 声明了且不含 / 没声明。 */
|
|
140
|
+
export declare const MODEL_IMAGE_INPUTS: readonly ["yes", "no", "undeclared"];
|
|
141
|
+
export type ModelImageInput = (typeof MODEL_IMAGE_INPUTS)[number];
|
|
139
142
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
143
|
+
* 「这个目录条目收不收图」的**唯一读法**。两个读者都投影它,不各写各的:
|
|
144
|
+
* · 目录行 `GET /v1/models` 的 `vision`:`yes` ⇒ `true`,`no` ⇒ `false`,`undeclared` ⇒ **键缺席**(不替没声明的模型答「收」也不答「不收」);
|
|
145
|
+
* · 运行期的带图预检 {@link modelSupportsImages}:只有明说 `no` 才出信号。
|
|
146
|
+
* 此前两处各判各的,且对「没声明」答法相反(目录行报 false、预检放行)——客户端只读得到目录那一位,于是在手写、没声明 `input` 的
|
|
147
|
+
* 条目上把用户的图拦在了本地,而引擎其实会把图交给模型。env 车道与配置中心车道恒写 `input`,分叉只落在手写目录条目上。
|
|
148
|
+
* `input` 不是数组(手写错形)与条目不在,同归 `undeclared`:没有可信的声明。
|
|
149
|
+
*/
|
|
150
|
+
export declare function modelImageInput(model: {
|
|
151
|
+
input?: unknown;
|
|
152
|
+
} | undefined): ModelImageInput;
|
|
153
|
+
/**
|
|
154
|
+
* 带图预检:只有目录条目**明说**不收图({@link modelImageInput} 为 `"no"`)才为假 —— 提交时据此计数并告警(引擎会把这些图换成给模型的
|
|
155
|
+
* 占位句);没声明的条目为真(不出信号,图原样交给模型)。
|
|
145
156
|
*/
|
|
146
157
|
export declare function modelSupportsImages(model: {
|
|
147
158
|
input?: unknown;
|
package/dist/model-select.js
CHANGED
|
@@ -90,9 +90,13 @@ export function matchCatalogModel(ref, catalog, refusals) {
|
|
|
90
90
|
}
|
|
91
91
|
return undefined;
|
|
92
92
|
}
|
|
93
|
-
export
|
|
93
|
+
export const MODEL_IMAGE_INPUTS = ["yes", "no", "undeclared"];
|
|
94
|
+
export function modelImageInput(model) {
|
|
94
95
|
if (!model || !Array.isArray(model.input))
|
|
95
|
-
return
|
|
96
|
-
return model.input.includes("image");
|
|
96
|
+
return "undeclared";
|
|
97
|
+
return model.input.includes("image") ? "yes" : "no";
|
|
98
|
+
}
|
|
99
|
+
export function modelSupportsImages(model) {
|
|
100
|
+
return modelImageInput(model) !== "no";
|
|
97
101
|
}
|
|
98
102
|
//# sourceMappingURL=model-select.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 协议规则名(`mcp__…` / `a2a__…`)够不够得着一只挂载工具 —— **一只**判官 + **一只**句子。调它的面(点名,不是「每个面」):
|
|
3
|
-
* ·
|
|
3
|
+
* · 运维三张名表(`APPROVAL_REQUIRE` / `DENY` / `NEVER_AUTO`)与中心下发的同名表、请求 `settings.permissions.{ask,deny}`(经 `config.ts` 的
|
|
4
|
+
* `unmatchableToolNamesRefusal`);
|
|
4
5
|
* · 会话规则写口两腿与 SQL 会话规则店的写口(经 `session-policy-wire.ts`);
|
|
5
|
-
* ·
|
|
6
|
-
* `agents[].skills[]`
|
|
6
|
+
* · 引擎 allow 层的三张条目表(core 7.42.0 起):请求的 `settings.permissions.allow`、运维的 `APPROVAL_GRANT`、技能的两只清单 `allowedTools` /
|
|
7
|
+
* `disallowedTools`(顶层 `skills[]` 与子代 `agents[].skills[]` 两处受理口)—— 经 `rule-entry-list.ts` 的 `unreachableRuleEntryOf`(同一只函数)。
|
|
7
8
|
* **不调它的面**(如实,未接):`/v1/rules` 的持久规则写口与 `cc-import` 导入口(`http/routes/rules.ts`)、子代的工具名表 `agents[].allowTools` /
|
|
8
9
|
* `denyTools`(`spec-fields.ts` 只判形与条数)。在这两处写铸不出的服务器段仍被收下、永不命中。
|
|
9
10
|
* 规则文本里「工具名那一段」怎么取也只有一种读法:{@link ruleEntryToolName}(配置表与技能清单同读)。
|
|
@@ -17,8 +18,9 @@
|
|
|
17
18
|
* (`mcp__<摘要>__*`)没人照做得了 ⇒ 判词单列 `wildcard_peer`,句子说实话(写具体的服务器名)。
|
|
18
19
|
* 判据不看规则是覆盖形还是精确名:两种形的对端段命运相同。精确名的**工具段**归一不在这里判(包根没有导出工具段的铸法,不手抄)。
|
|
19
20
|
*
|
|
20
|
-
* core
|
|
21
|
-
* `
|
|
21
|
+
* 翻转条件:core 的规则名判官(`judgeRuleToolName`)自己给出「对端段铸不出」这一判词 —— core 7.42.x 还没有(包根 `RULE_TOOL_NAME_VERDICTS`
|
|
22
|
+
* 只有 current / retired / unprefixed_namespace,亲核),等上游;到货那天本仓这只对照整只退役(连同会话规则写口这一问的码由 `request.field_invalid`
|
|
23
|
+
* 归回 `rules.legacy_tool_name`)。
|
|
22
24
|
*/
|
|
23
25
|
import { type ProtocolNamespace } from "@sema-agent/core";
|
|
24
26
|
/**
|
|
@@ -116,7 +116,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
116
116
|
};
|
|
117
117
|
readonly "server.approvals.risk-descriptor-cell-unreadable": {
|
|
118
118
|
readonly cls: "F";
|
|
119
|
-
readonly note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor
|
|
119
|
+
readonly note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor 的行之后、披露位(`allowRuleShadowed`;7.117.0 前叫 `shadowedRule`)无可脱敏、读面派生的 `governanceForced` 徽章不亮 —— 三者都是**展示/分诊**,不是执法判据(门早已 park,真按 `shellGateDoctrine` 判的 `active-run-conflict.ts` 读的是 checkpoint blob 的 `get()`,不经本读面),且 `governanceForced` 的成文语义本就是「缺席 = 没有治理来源的**证据**」。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 cell 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),连同其余所有行一起消失。故 F 类;tag 与候选那格**分开计**,并计会让「哪一格在坏」在遥测里读不出来。";
|
|
120
120
|
};
|
|
121
121
|
readonly "server.approvals.park-row-blob-unreadable": {
|
|
122
122
|
readonly cls: "F";
|
|
@@ -110,7 +110,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
110
110
|
},
|
|
111
111
|
"server.approvals.risk-descriptor-cell-unreadable": {
|
|
112
112
|
cls: "F",
|
|
113
|
-
note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor
|
|
113
|
+
note: "运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint.risk_descriptor` 这一格**不是合法 JSON 文本**(手改过的行 / 回滚残留 / 列语义漂;与同一 `rows.map` 里的 `rule_suggestions` 是同族坏法,两方言都是 TEXT 列)。放行的最坏后果 = **这一行**的分诊 descriptor 落 null:severity 折 0 排到有 descriptor 的行之后、披露位(`allowRuleShadowed`;7.117.0 前叫 `shadowedRule`)无可脱敏、读面派生的 `governanceForced` 徽章不亮 —— 三者都是**展示/分诊**,不是执法判据(门早已 park,真按 `shellGateDoctrine` 判的 `active-run-conflict.ts` 读的是 checkpoint blob 的 `get()`,不经本读面),且 `governanceForced` 的成文语义本就是「缺席 = 没有治理来源的**证据**」。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 cell 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),连同其余所有行一起消失。故 F 类;tag 与候选那格**分开计**,并计会让「哪一格在坏」在遥测里读不出来。",
|
|
114
114
|
},
|
|
115
115
|
"server.approvals.park-row-blob-unreadable": {
|
|
116
116
|
cls: "F",
|