/** * Request-body → TaskSpec field normalizers (shell-host contract). Kept out of main.ts (which auto-runs the service * on import) so they are unit-testable in isolation. Each is DEFENSIVE: a RESUME re-runs resolveSpec from the stored * body WITHOUT the HTTP-layer 400 validation, so a malformed value must fall back to a safe default, never throw. */ import { type AgentDefinition, type TaskSpec } from "@sema-agent/core"; /** * E12 — normalize `body.suggestNextPrompts` → `TaskSpec.suggestNextPrompts`. `true` = on with core defaults * (count 3, cheap role); an object tunes `count` (clamped 1..10) / `role` (a free string — core validates it against * `ModelRole`, an unknown role falls back to the task model). `false` / absent / non-object / garbage ⇒ undefined (off). */ export declare function normalizeSuggestNextPrompts(v: unknown): TaskSpec["suggestNextPrompts"]; /** * [ref] (core 1.246) — normalize `body.resilience` → `TaskSpec.resilience`. INTENT flags only: * `allowDegrade:false` / `allowFailover:false` let a bench/eval or critical task say "fail rather than fall back". * `bypassBreaker` is OPERATOR-FACING (不给 wire 用户直开 — it punches through a shared circuit breaker, * which exists to protect the provider): honored only when the request principal is an explicit operator; silently * dropped otherwise (not a 4xx — resume re-runs this without HTTP validation, and a stripped intent flag degrades * to today's behavior, core's own "all absent = byte-compat" posture). Only literal booleans pass; all-empty ⇒ * undefined (never an empty object — keeps the spec byte-identical for non-users). */ export declare function normalizeResilience(v: unknown, operator: boolean): TaskSpec["resilience"]; /** * [ref] (core 1.251) — normalize `body.attachments` → `TaskSpec.attachments`. Core's contract is * literal-true unions ("关闭=删 key,勿传 false"): only `true` (or, for changedFiles, an object with a sane * `maxFiles`) survives; `false`/garbage per key is DROPPED, not forwarded. maxFiles clamped 1..100 (core caps its * own stat cost at 20 by default; the clamp only guards against a resume replaying a wild persisted value). * All-empty ⇒ undefined. NB resume audit: our persisted body is the FULL request JSON and resolveSpec * is the single translation point, so consuming the field here IS the whitelist inclusion — nothing else to add. */ export declare function normalizeAttachments(v: unknown): TaskSpec["attachments"]; /** * [ref] rewind exclusive mode (core 1.292) — normalize `body.resumeAtMode` → `TaskSpec.resumeAtMode`. DEFENSIVE * (the resume path re-runs resolveSpec WITHOUT the HTTP 400 gate): only the two enum values survive, and only when * the resume-at handle actually RESOLVED (core ignores the field without `resumeAt`; keep the spec tight instead of * shipping a dangling qualifier). Absent/garbage ⇒ undefined = core default "at" (zero regression). */ export declare function normalizeResumeAtMode(v: unknown, resumeAtResolved: boolean): "at" | "before" | undefined; /** [ref] 防御上限:一份请求最多带多少个 agent 定义。core 侧无上限;这里封一个防御值 —— 每个定义都进 * 系统提示(whenToUse/skills/systemPrompt 全是 prompt 载荷),不设界就是 per-turn token 成本洞。 * 32 足够覆盖 CC 式 `.claude/agents/` 全家桶还有余量。⚠️ 这条帽守的是 **agent 定义**的条数 * (每个定义的 whenToUse/systemPrompt 都是无条件进系统提示的 prompt 载荷),与 [ref] 退役的 * **技能**条数帽不是同一轴:技能清单由引擎按预算渲染(超了裁描述),agent 定义没有那个预算面。 */ export declare const MAX_TASK_AGENTS = 32; /** [ref] 单条 agent 定义的形状裁决:null=合法;否则一句可 400 的英文错误(不带索引,调用方自己加)。 * 白名单外键 fail-loud —— 与 resilience/limits 的「容忍未知键」相反:agents 是能力定义,静默丢字段 = * 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。⚠️ 已知残账(core 半场): * core 只对 allowTools 未知项 fail-loud,denyTools 未知项(如 typo)静默失效 —— 已上板请 core 镜像。 * ⚠️ 键判断必须 Object.hasOwn:`in` 走原型链,JSON 里的 "toString"/"constructor"/"__proto__" 键会被 * 误判为白名单字段,随后 SHAPES[k].ok 不是函数 ⇒ TypeError(fresh 500 / resume 砖;cross-review 复现)。 */ export declare function taskAgentIssue(item: unknown): string | null; /** [ref] HTTP 门(fresh submit,server.ts prepareSpec):body.agents 在场必须是非空数组、每条过 * {@link taskAgentIssue}、name 数组内唯一、条数 ≤ {@link MAX_TASK_AGENTS}。返回 400 错误串或 null。 * RESUME 重放不过这道门 —— {@link taskAgentsFromBody} defensive(按条 DROP+warn,不砖 resume)。 */ export declare function validateTaskAgents(v: unknown): string | null; /** [ref] body.agents → `TaskSpec.agents`(resolveSpec 的唯一装配点;main.ts 只 spread)。两层门: * - 🔒 TOB 租户门:多租户(requirePrincipal===true)暂不放行 —— [ref] 原文「TOB 面按租户 caps 门」, * 当前没有 per-tenant caps 面可挂,先 fail-closed 关多租(warn `task_agents_ignored`,capabilities * 的 `taskAgents` 同步 advertise false);单机/单用户直通(caller 是自己 worker 的 super-admin, * 与 mcpInjection/hooks 同一姿势)。 * - defensive 归一化(RESUME 重放持久化 body 不过 HTTP 400 门):非法项按条 DROP + warn * `task_agent_dropped`(绝不 throw 砖 resume);重名后到者 DROP;超上限截断。幸存字段按白名单拷贝 * (不透传未知键 —— 非法项已整条 drop,这里只是让持久化 body 的杂键不上 spec)。全空 ⇒ undefined。 */ export declare function taskAgentsFromBody(raw: unknown, requirePrincipal: boolean | undefined, warn: (event: string, fields: Record) => void): AgentDefinition[] | undefined; /** [ref]②/[ref](cli 认领消费)—— fresh-submit 的 agents[].model **目录成员资格**门(形状门 * {@link validateTaskAgents} 通过之后跑;调用方铸 400 `request.unknown_reference` + `available` 枚举)。 * 病灶=报错太晚太远:server 持目录却不查,错到 core prepare 才炸 `config.agent.unknown_model` * (dist/agents/subagent.js),以「run failed」形出现而非 400;cli resolver 只挡词法形,目录 * 成员资格只有 server 持有。 * 三条判定纪律(全部 core dist 亲读): * - **只查 string 形**:core 只对 `typeof a.model === "string"` 走 `resolveModel`(subagent.js); * Model 对象形 core 原样信任不查目录([ref]④ 契约:自带完整 Model 配置,其 id 本就不必在目录)—— * 对象形没有「晚失败」病灶,这里同拒会是射程外的行为收紧,不做。 * - **`catalog` 必须是 tier 展开形**(调用方传 `expandTiers(config.models, config.tiers) ?? config.models`, * resolve-spec.ts wireCatalog 同源纪律):core Runner 构造期把 deps.models 换成展开目录 * (runtask.js),agentCatalog.models 即它 —— 裸 config.models 会把合法档位词/CC 别名误杀 400。 * - **判定=`Object.hasOwn` 纯键查**,刻意不用 `matchCatalogModel`:那是 body.model 门的四形判 * (name/tier/alias/**id**),而 core 的 `resolveModel` 是纯键查(roles.js:`models?.[ref]`)—— * 多认 id 形会放行一个 core prepare 必炸的值(门假绿,病灶照旧)。hasOwn 顺带挡原型链键。 * RESUME 重放不过此门({@link taskAgentsFromBody} 维持 defensive drop 现状;重放无 400 可回, * 模型事后被移出目录时由 core prepare 拒)。 */ export declare function unknownTaskAgentModelRef(agents: readonly unknown[], catalog: Record): { index: number; ref: string; } | undefined; /** [ref] main.ts resolveSpec 的 agents 装配片段(可 import 真 fold,[1.211 codex L]:此前 main.ts 用内联 * 闭包,测试只能 SOURCE PIN 文本+重写同构表达式——文本锚可被「移出 resolveSpec 但保留同文」静默骗过; * 现在行为测试直接调这个真出口,main.ts 只剩一次调用点可锚)。语义=taskAgentsFromBody 的条件 spread: * 有幸存 agents 才挂键(absent/全 drop ⇒ 空对象,spec byte-compat)。 */ export declare function taskAgentsSpecFragment(raw: unknown, requirePrincipal: boolean | undefined, warn: (event: string, fields: Record) => void): { agents?: AgentDefinition[]; }; /** [ref]② — body.retainBackgroundProcesses → `TaskSpec.retainBackgroundProcesses`。core 语义(1.295 * runtask/task-registry dist 亲读):`=== true` 时任务收尾不主动收割 session 后台进程 —— host lane 上是 * 资源驻留授予,与 backgroundShell 同款单用户闸:多租户 warn 忽略(`task_retain_bg_ignored`,capability * 同步 advertise false)。只 literal `true` 上 spec(false=core 缺省,不挂键保持 byte-compat);非 boolean * 由 HTTP 门 400,resume 重放这里 defensive DROP(不 throw)。 */ export declare function retainBackgroundProcessesFromBody(raw: unknown, requirePrincipal: boolean | undefined, warn: (event: string, fields: Record) => void): true | undefined; /** [ref]② — body.{excludeTools,deferTools} → `TaskSpec.{excludeTools,deferTools}`(core 1.314 工具面 * 控制批)。语义:excludeTools=roster 真卸载(schema 字节不上 wire,manifest 如实收窄);deferTools= * [ref] 延迟披露(占位上 wire,ToolSearch 激活 materialize;**core 7.15.0 [ref] 起它是唯一不受那道 * 10% 阈值门的延迟来源** —— 按字面恒延迟、不计量,引擎自选的候选集才受门)。两位 tighten-only(core 继承不变量: * union 进全部子 spawn 路径),per-run 工具面收窄无跨租户面 ⇒ 无租户门(interactiveTools 同姿势)。 * DEFENSIVE(resume 重放不过 HTTP 400 门):非数组 ⇒ undefined;项非非空字符串 ⇒ 逐项 DROP;空数组 ⇒ * undefined(不挂键=core 缺省,byte-compat)。HTTP 门另有 fail-loud 400(提交面邻居姿势)。 */ export declare function toolNameListFromBody(raw: unknown): string[] | undefined; /** [ref]/[ref] — body.promptProfile → `TaskSpec.promptProfile`(core 1.328 R2 主件,CC LT(model_id) * 同构双形轴)。语义:纯呈现轴不碰策略(simple=CC 212 短形面缺省;classic=长形面,可按任务/模型试分) * ⇒ 无租户门(deferTools 同姿势);全委托树继承由 core 管。缺省不挂键(引擎缺省 simple,byte-compat)。 * * ⚠️ **这不是一条 defer 注**([ref] 件⑤ 对表结论,core [ref] 同判):本段说的是 `promptProfile` 这根 * **呈现**轴,它与 `deferMode` / `deferTools` 的延迟披露轴**无耦合** —— 上一行提 deferTools 只是在借它 * 的「无租户门」姿势作类比。「引擎缺省 simple」这句本身在 core 5.40.0 上仍逐字为真 * (`core/types.d.ts`:default "simple"),本批亲验未变。 * 真正的 defer 轴对表(core 5.40.0 起 `deferMode:"auto"` 不再 sweep 内建;7.15.0 起它不再是第二道门, * 只是把候选集拓宽后走同一道阈值门)记在 * `src/capabilities/tool-defer.ts` 的头注 —— 别把两根轴的结论写到一处。 * DEFENSIVE(resume 重放不过 HTTP 400 门):非法值 ⇒ undefined;提交面另有 fail-loud 400(邻居姿势)。 */ export declare function promptProfileFromBody(raw: unknown): "simple" | "classic" | undefined; /** [ref]②(core 5.14.0 `TaskSpec.toolMaterializeStrategy`):deferred 工具激活后的 schema 供给策略。 * 与 {@link promptProfileFromBody} 同姿势的**防御性归一**——提交面的 fail-loud 400 在 HTTP 门,这里 * 只负责 resume 重放存量 body 时把不认识的值静默 DROP(回落 core 的 env/缺省链),而不是把它带上 spec。 */ export declare function toolMaterializeStrategyFromBody(raw: unknown): "static" | "swap" | undefined; /** [ref]④ — 每键的运营方上限旋钮(env,缺省不设=不封顶)。 */ export interface TaskLimitCaps { /** TASK_TIMEOUT_MAX_SEC(env 名不动,仍按**秒**配置;core 5.8.0 起封顶对象是 limits.maxWalltimeMs, * 封顶比较时 ×1000 换算——运维面零迁移)。 */ timeoutSec?: number; /** TASK_MAX_OUTPUT_TOKENS_MAX */ maxOutputTokens?: number; /** TASK_MAX_TURNS_MAX */ maxTurns?: number; } /** * [ref]④ — normalize `body.limits` → 请求方配速意图 {maxWalltimeMs?, maxOutputTokens?, maxTurns?}。DEFENSIVE * (resume 重放持久化 body 不过 HTTP 400 门):仅正的有限数字存活(floor 取整),0/负/垃圾按键 DROP —— core 侧 * 墙钟 > 0 才生效(0=不设墙,会跳过运营方封顶)、maxTurns/maxOutputTokens 的 0 无意义。每键各自被 * 运营方上限旋钮 clamp(Math.min;旋钮缺省不设=不封顶)。全空 ⇒ undefined。 */ export declare function normalizeLimits(v: unknown, caps: TaskLimitCaps, warn?: (event: string, fields: Record) => void): { maxWalltimeMs?: number; maxOutputTokens?: number; maxTurns?: number; approachNotice?: false | { at?: [number, number]; }; } | undefined; /** 166-T3 — approachNotice 单键归一(normalizeLimits/HTTP 门共用判据源):合法 ⇒ 归一值,坏形 ⇒ undefined。 * 阈值规则对齐 core(prepare-task at 校验):二元组、(0,1] 有限数、a<=b(允许相等)。 */ export declare function normalizeApproachNotice(raw: unknown): false | { at?: [number, number]; } | undefined; /** * [ref]④ — body.limits 与 env 墙钟的合成规则(resolveSpec 的唯一 limits 装配点;run-local 无 body 车道不经此): * - `timeoutSec`:body 给了就用 body(caller 明确配速;已在 normalizeLimits 里被 TASK_TIMEOUT_MAX_SEC 封顶)。 * body 缺席保持既有姿势 = tenancy 墙钟 + env `TASK_TIMEOUT_SEC` 只抬不降(taskWallClockSec 内 Math.max, * 误配低值不能砍 council 预算)。注意两条纪律不叠加:「只抬不降」是 env 对内建缺省墙的规则,body 是 * caller 的显式意图,直接生效(可低于 env 墙 —— 交互短任务要更紧的配速正是本件诉求)。 * - `maxOutputTokens` / `maxTurns`:直透传(各自可选 env 上限已 clamp)。 * 全空 ⇒ undefined(spec 不挂 limits 键,byte-compat)。 */ export declare function resolveTaskLimits(bodyLimits: unknown, caps: TaskLimitCaps, envTimeoutSec: number, requirePrincipal: boolean | undefined, big: boolean, warn?: (event: string, fields: Record) => void): TaskSpec["limits"]; //# sourceMappingURL=spec-fields.d.ts.map