/** * HTTP wire REQUEST types ([ref] A4) — pure type shapes of the task-submit surface, moved VERBATIM out of * http/server.ts so a caller that needs only the request SHAPE (security.ts's authorizer signature; embedders via * index.ts) depends on a type-only LEAF instead of the whole 10k-line route module. server.ts re-exports * {@link TaskRequestBody}, so every existing import site keeps working unchanged. Conservative scope for the first * cut: ONLY TaskRequestBody (its field types are inline or core imports — no other server.ts-local satellites); * sibling request/response shapes can migrate here later on the same pattern. */ import type { A2aServerSpec, AgentDefinition, ApproverPosture, McpServerSpec, SkillSpec } from "@sema-agent/core"; import type { SettingsPermissionMode } from "../task-settings.js"; import type { wiringManifestFaceEventData } from "../trace/project.js"; export interface TaskRequestBody { objective: string; sessionId?: string; /** Which deployment SCENARIO assembles this task's tools/prompt/skills (the keys of the worker's scenario * table — e.g. "default"/"code-review"/"scan"/"discuss"). An INTENT bounded by the deployment's own table AND, * on a governed worker, by the principal's center-resolved allowlist: an unknown name is a typed 400 * `scenario_unknown`, a known-but-not-allowed one a typed 400 `scenario_not_allowed`; absent ⇒ the ruling's * assigned scenario, else `DEFAULT_SCENARIO` (consumed at `boot/resolve-spec.ts` via `gateScenarioRequest`). * ⚠️ scenario-SPECIFIC keys (`repo`/`lenses`/`rounds`, read through `ScenarioRequest`'s index signature) are * deliberately NOT declared here — they belong to one scenario each, not to the shared submit surface. */ scenario?: string; /** dispatch-gateway failover 幂等第二级:caller-minted 任务 id(uuidv7)。同 owner 重放同 id ⇒ 幂等重放; * 异 owner ⇒ 409 `conflict.run_exists`;非 uuidv7 ⇒ 400 `request.id_invalid`(routes/runs.ts:192-208 真验收)。 * [ref] TR-16 连带:此键此前只活在 runs.ts 读点,导出类型漏declared——CAPS-OPS-13 同族第 9 键。 */ taskId?: string; images?: Array<{ data: string; mimeType: string; } | { url: string; }>; /** D-1 通用文件上传:预上传附件的句柄数组(POST /v1/attachments 的 `id`)。提交时绑定到会话并在 * 执行环境工作目录 `attachments/` 下物化;objective 尾部追加告知(名字/mime/尺寸,内容不进流)。 * 上限 16/task;未知或他人 id → 400(fresh 腿)。⚠️ 名为 `attachmentIds` 而非 `attachments`—— * 后者已被系统提醒配置键占用(本接口 :464,core spec.attachments 镜像)。 */ attachmentIds?: string[]; /** [ref] C1 (core 1.181): the CLIENT/gateway's view of the user — used by core to localize the env block's * date to the user's zone + surface who the agent acts for. The shell/gateway fills it from a verified user * identity (NOT the model). core validates timeZone (invalid → UTC, never mislabeled). Pass-through to * `spec.clientContext`; `locale` was removed in core 1.186 (dead field) so only timeZone+userEmail are accepted. */ clientContext?: { timeZone?: string; userEmail?: string; }; /** Business/UX system prompt the CLIENT injects (decoupling seam): appended after the scenario's * stable base (before ), so an integrator (e.g. OA) owns its own persona / UI markers / * workflow rules and evolves them WITHOUT a service change. Trusted: only a caller holding * SERVICE_AUTH_TOKEN reaches here (same boundary as `businessContext`). Keep it STABLE per * integrator-version (it's part of the cacheable prefix); put per-request context in `objective`. */ systemPrompt?: string; /** Append-only system prompt rider ([ref] R1 / [ref] R2 ruling): passes through to core's * `TaskSpec.appendSystemPrompt` (≈ CC `--append-system-prompt`) — a STABLE block composed after the scenario * base + `systemPrompt`, before the volatile tail (prefix-cache friendly). First-class lane for the shell's * product-knowledge block, so `settings.outputStyle` stays a real output style (a future center-issued style * block composes AFTER this field — byte-stable order, pinned by test). Same trust boundary + cap as * `systemPrompt` (token-holder only, MAX_SYSTEM_PROMPT_CHARS) — but NOT the same prompt position (codex F1, * honesty note): this lands in `core/role.append`, AFTER core's locked safety blocks, exactly where * `settings.outputStyle` has landed since 1.26 — the same principal class held this slot before this field * existed, so no new grant; whether caller riders should compose BEFORE the safety tail is core's slot-order * question (raised on the board). Rejected fail-loud when `systemPrompt` is an already-assembled prompt * (anchors detected) — that assembler arm cannot mount the rider (silent drop would belie the capability bit). */ appendSystemPrompt?: string; /** Explicit model selection (CLI `/model` picker + `run --model `). An INTENT by catalog * name (e.g. "deepseek-pro") — resolveSpec gates it against the SAME allow-list as an inline `@name` mention * (config.models keys), so it picks WHICH configured model but can never inject one. Wins over an inline * @mention; an unknown name falls through to mention/default (lenient). Never a baseUrl/apiKey. */ model?: string; /** [ref]① Cheap-model gear for within-task compaction/summarize (core [ref] `TaskSpec.compactionModel`): * a catalog INTENT like `model` (name/tier word/id), gated against the SAME expanded allow-list — picks WHICH * configured model compacts, never injects one. core clamps window-unsafe picks back to the main model with * `modelFallback` attribution on the `compacted` event. Absent ⇒ the `summarize` role / main model (status quo). * Unknown fresh-submit value → 400 (body.model posture); a RESUME replay whose ref left the catalog is dropped * + warned in resolveSpec (core's resolveModel would otherwise throw and brick the resume). */ compactionModel?: string; /** Per-task budget the caller requests (1.37). Capped server-side to the operator ceiling * (MAX_TASK_COST_USD / MAX_TASK_TOKENS) — a caller may ask for less, never more. */ maxCostUsd?: number; maxTokens?: number; /** Developer mode (1.44): run the task behind the independent verification gate (impl → independent * read-only verify → fix loop). Default off. `verifyRounds` is clamped to [1,5] (default 2). Not * supported on /v1/tasks/stream (the gate is multi-round, not a single stream). */ verify?: boolean; verifyRounds?: number; /** Quality-gate cascade (1.45): run cheapest→strongest (config `MODEL_CASCADE_LADDER`), escalating when * a rung fails the gate (default = it didn't complete). Default off; mutually exclusive with `verify`; * not on /v1/tasks/stream. ⚠️ each rung is a cold re-run — use for read-only / idempotent tasks. */ cascade?: boolean; /** Multi-lens review council (the `code-review` scenario's expensive tier) — `council:true` fans the review out * to N parallel lenses + an arbiter instead of the lead reviewing in-line; `debate:true` additionally runs the * L2 peer-debate rounds. Read on TWO seams and both matter: the scenario builder mounts `run_council` * (`capabilities/scenarios.ts` `codeReview`), and `boot/resolve-spec.ts` treats either flag as an EXPLICIT team * declaration — it widens the task's wall-clock tenancy budget (`resolveTaskLimits`) and suppresses the value * router's auto-escalation (`explicitTeam`). Only literal `true` counts on both seams. */ council?: boolean; debate?: boolean; /** Work-view correlation id (optional): groups the runs of one logical task across both client doors * (MCP façade / portal) so a fragmented set of runs reads as one task. Opaque to the engine — only * persisted on the run row + filterable via `GET /v1/tasks?jobId=`. ≤64 chars (matches the column). */ jobId?: string; /** Personalization seam: the caller's OWN skills for this task, threaded into core's * `TaskSpec.skills`. Merged AFTER the scenario's skills with SCENARIO-WINS on a name clash (the security * baseline can never be shadowed by user content — see mergeUserSkills). User skills are user-authored * content in the user's OWN principal-isolated task (self-harm surface, not privilege widening); * scenario/center skills remain the only trusted layer. * * 🔴 **类型 = core 的 `SkillSpec` 本体,不是它的手抄子集**(S-377):这里曾写死 * `{ name; description; content }` —— core 闭集六位里的三位,而 `manifest` / `files` / `baseDir` * 三位在运行期一直是**收的**(受理门 `validateUserSkills` 不判未知键,`mergeUserSkills` 原样透传到 * `TaskSpec.skills`)。一份会漂的手抄 = 声明面比真受理面**窄**,SDK 消费方按声明写代码就以为发不了, * 而 core 加键(7.20.0 [ref] 的 `baseDir`)时这里连红都不会红。改成 core 本体后,闭集由 tsc 看守。 * ⚠️ 受理**判据**(形/尺寸)仍在 `validateUserSkills` + `spec-fields.ts` 的 `SKILL_FIELD_SHAPES`—— * 类型说「哪些键存在」,门说「值合不合法」,两者不是一件事。 * (旧注里的「Caps: ≤10 skills, content ≤32KB each」两条帽早已随 [ref] / core 1.293 退役,一并纠正: * 今天只剩 `content ≤ 1 MiB` 这一条 core 加载门孪生。) */ skills?: SkillSpec[]; /** §7 P0.5 (用户按需选像): the sandbox image PROFILE this task wants (e.g. * "code-full" / "code-python"). An INTENT, never a digest — the service resolves it to a published digest with * the caller's principal (fail-closed visibility re-admit) and applies it per-pod. Only supported on the k8s * backend; absent ⇒ the worker-global default image. `capabilitiesNeeded` optionally requires the resolved * image to provide boolean capabilities (browser/db/nestedBuild) or the request is rejected. */ sandboxImageProfile?: string; capabilitiesNeeded?: string[]; /** Structured-output constraint (CC `--json-schema`): a JSON Schema the model's FINAL answer * must match — threaded into core's `TaskSpec.outputSchema`, which injects a built-in `submit_output` tool and * surfaces the validated object as `TaskResult.structuredOutput` (an invalid submit retries, then fails with * `output.invalid`). A plain JSON-schema OBJECT (shape + size validated here; deep validity is core's). */ outputSchema?: Record; /** Retry budget for an INVALID `submit_output` against `outputSchema` (core `TaskSpec.outputRetries`) — the * caller-facing other half of the structured-output pair. Narrow acceptance in `boot/resolve-spec.ts`: * finite, ≥1, floored, capped at 10; anything else ⇒ key omitted (core's own default). Meaningless without * `outputSchema` (core ignores it there). */ outputRetries?: number; /** Within-task compaction tuning (core [ref] `TaskSpec.compaction`). ⚠️ ONLY `clampTolerance` is a caller * knob — the rest of core's compaction object is an OPERATOR axis and is deliberately not on the wire, so this * type is narrower than core's field on purpose. `boot/resolve-spec.ts` accepts a finite number in [0,1] and * omits the whole `compaction` key otherwise. Pairs with `compactionModel` (which gear compacts). */ compaction?: { clampTolerance?: number; }; /** Reasoning-effort selection (CC `/effort` picker, shell-host contract E7): a neutral effort tier mapped to * core's `TaskSpec.thinking` (`ThinkingLevel`). Accepted set = core's tiers (off/minimal/low/medium/high/xhigh/max); * the picker's advertised default set is `/v1/models` `supportedEffortLevels`. A provided-but-unknown value is a * 400 (fail-loud, never silently dropped — the body.model silent-drop bug class). Absent ⇒ core uses the resolved * role's default thinking. NEVER `budgetTokens`/`fast_mode` (provider-neutral by omission). */ reasoningEffort?: string; /** Resume-at (shell-host contract E18): the E2 message `eventId` to rewind the conversation to — core branches the * session at that prior message before running the new objective ("rewind to this message, ask differently"). * The service resolves eventId→the persisted SessionTreeEntry.id (the anchor map) in resolveSpec; an unknown * eventId is a 4xx (never silently dropped). Requires a session. Absent ⇒ continue from the current leaf. */ resumeAt?: string; /** [ref] rewind exclusive mode (core 1.292 `TaskSpec.resumeAtMode`): qualifies the `resumeAt` target. `"at"` * (default when absent — zero regression) keeps the target message in the branched context; `"before"` branches at * the target's PARENT, EXCLUDING the target itself (the exclusive-rewind shape: "remove this prompt and everything * after it"). core enforces the "before" edges and the service passes its rejection codes through UNCHANGED for the * shell to render: `resume_at.before_target_not_user` (only plain user-message targets), * `resume_at.before_root_unsupported` (can't rewind past the session root — start a new session), * `rewind_snapshot.unresolvable` (rewindFiles: no snapshot at/above the "before" branch point). Only meaningful * WITH `resumeAt` — sent alone it's a 400 (fail-loud, not a silent no-op). */ resumeAtMode?: string; /** [ref] Phase3 (reuse-path warm-resume): require the `sessionId` to ALREADY exist. When `true` and the session * is genuinely missing (expired / purged / never created), the run FAILS LOUD (`resume.session_not_found`) instead * of silently starting a fresh empty session ("looks warm, actually fresh"). Absent/`false` ⇒ the historical * create-on-miss. Fails loud with NO sessionId too (nothing to resume). Rides the persisted body onto resume legs. */ requireExistingSession?: boolean; /** Prompt suggestions (shell-host contract E12): opt-in — after the task COMPLETES, core runs ONE extra LLM pass to * propose follow-up prompts the user might send. `true` = default (count 3, cheap role); an object tunes count/role. * OFF/absent ⇒ zero extra LLM. The strings are UNTRUSTED model text for the shell UI ONLY (never re-fed to a model); * the service persists them (redacted) as a `suggestions` event on the run's events tail. */ suggestNextPrompts?: boolean | { count?: number; role?: string; }; /** [ref] file restore ([ref]): with `resumeAt`, ALSO converge the working tree's **agent-edited files** * (the tracked set — nothing else is visible to the history, so a user's parallel work is structurally * untouchable) to that entry's history boundary. Capture needs no request anymore: first-touch tracking is * always on when the deployment wires a history store. Requires a FileHistoryStore. */ restoreFiles?: boolean; /** DV-15: opt-in tolerance for a PARTIAL file restore (default = loud terminal failure carrying the per-file * ledger; `true` = keep going and disclose the same ledger on the result's rewindNotes). */ acceptPartialRestore?: boolean; /** RETIRED spelling (whole-tree epoch). Still accepted on the wire: its capture sense (true, no resumeAt) is * a tolerated no-op with a deprecation disclosure; its restore sense (true + resumeAt) is a typed rejection * naming `restoreFiles` — the restore semantics genuinely changed (whole tree ⇒ tracked set), so that * migration is explicit by design (core adjudicates; the server never remaps it silently). */ rewindFiles?: boolean; /** R8 code-only rewind (CC "Restore code" mode): converge the TRACKED set to the history boundary at a prior * USER message WITHOUT forking the conversation (the session leaf is untouched — only the files move). The * value is the same handle as `resumeAt` (the prompt's taskId); the service resolves it → the entry id. Honored ONLY * when `resumeAt` is ABSENT (the two modes are mutually exclusive). Requires the anchor map + a FileHistoryStore. */ rewindFilesTo?: string; /** [ref] (core 1.246): per-task resilience INTENT flags. allowDegrade/allowFailover are caller-facing; * bypassBreaker is operator-only (resolveSpec drops it for non-operators — a permission downgrade, not a 400). */ resilience?: { allowDegrade?: boolean; allowFailover?: boolean; bypassBreaker?: boolean; }; /** [ref] (core 1.249): one-shot end-game verification nudge — opt-in by the AUTONOMY caller. */ finalVerification?: boolean; /** [ref]④ per-request 配速——core 5.8.0 时限重构后的键集([ref]):`maxWalltimeMs`(毫秒,opt-in 墙钟)/ * `maxOutputTokens`/`maxTurns`,均正整数;可选运营方上限旋钮各自封顶(TASK_TIMEOUT_MAX_SEC 保**秒**义, * 对毫秒键封顶 ×1000)。旧键 `timeoutSec` 与 deadline 族三 opt-out(机制退役)fresh-submit 400 响亮 * 点名替代;`maxWalltimeMs` 缺席时保持既有 tenancy 墙钟姿势(resolveTaskLimits,spec-fields.ts)。 */ /** 166-T3:approachNotice——core 默认 ON(80%/95% 各提示一次);false 关,{at:[r1,r2]} 调阈((0,1] 且 r1<=r2)。 */ limits?: { maxWalltimeMs?: number; maxOutputTokens?: number; maxTurns?: number; approachNotice?: false | { at?: [number, number]; }; }; /** [ref] (core 1.251) + G1 (core 1.253): turn-boundary attachment reminders (todo / changed-files / * plan-mode) + one-shot boundary notices (post-compaction background-task recap / deferred-tools delta). * [ref] CAPS-OPS-13:本类型经 src/index.ts 导出给 embedder——键集必须与真验收面(spec-fields.ts * normalizeAttachments 10 键 / normalizeLimits 6 键)同源;此前少 8 键 ⇒ agentListing:false(唯一能关 * core 默认开列表注入的通道)与三个 deadline opt-out 在类型面失踪。agentListing/skillsListing 是 * boolean 透传(core DEFAULT-ON,explicit false 才关);budgetUsd/mcpInstructions 只认 literal true; * limits 三个 opt-out 只认 literal false(normalizer 同判)。 */ attachments?: { todoReminder?: boolean; todoReminderMode?: "baseline" | "off"; changedFiles?: boolean | { maxFiles?: number; }; planModeReminder?: boolean; budgetUsd?: true; backgroundTasks?: boolean; toolsDelta?: boolean; mcpInstructions?: true; agentListing?: boolean; skillsListing?: boolean; }; /** C1 (core 1.219, subagent viewing pane): opt-in — forward a delegated child's live CONTENT events * (text_delta/text_end/reasoning_delta/tool_start/tool_end, each stamped `parentToolCallId`) onto this run's stream/durable * log, so a shell can render the child's transcript live ("enter 看详情"). Default OFF = progress-only (prior * behavior). Purely a render channel — core never merges the child stream into the parent's model context. */ forwardSubagentEvents?: boolean; /** G3([ref] 壳侧承诺③/[ref]③):壳供 per-session scratchpad 路径(壳 vendored getScratchpadDir 的 * 约定路径)。接受后优先于 server 自算,同源喂 envFacts.scratchpadDir 事实 + fs-write gate exemptDirs + * additionalDirectories 根围栏——壳提示词与 server 写门指同一个目录。验收 fail-closed(单用户 + * host-semantics lane + 绝对路径/深度/长度门,规则在 env-facts.acceptShellScratchpadDir),拒收形 * warn 后回落自算——「没到货引擎忽略 = 无害 additive」的对偶:异形键忽略,任务照跑。 */ scratchpadDir?: string; /** [ref] (core 1.225): opt-in — RETAIN settled sub-agent sessions so the shell can revive one * (`POST /v1/runs/:id/subagents/:target/resume`, CC dfe parity). `true` = core defaults (30min ttl / 16 max); * an object tunes them (service-clamped: ttlMs ≤24h, max ≤64 — retention pins sessions in memory within the * run). Default OFF = throwaway children (a resume then 409s `resume.retain_off`). Run-scoped: everything is * released when the parent run ends regardless. */ retainSubagentSessions?: boolean | { ttlMs?: number; max?: number; }; /** [ref] per-task custom subagents (core 1.295 `TaskSpec.agents` — CC `.claude/agents/` parity): the shell sends * its resolved agent definitions per request. Whitelist-validated FAIL-LOUD at submit (unknown keys 400 — an * agent definition silently losing a `denyTools`/`isolation` field is a silent widening, the opposite failure * mode of the resilience "tolerate unknown keys" posture; `permissionMode` deliberately NOT accepted — core call, * separate batch). `model` must be an ALREADY-RESOLVED name or Model object (the server does no alias translation). * 🔒 Honored on SINGLE-USER deployments only (multi-tenant warns + ignores until a per-tenant caps face exists — * fail-closed; advertised via capabilities.taskAgents). ≤32 per request. Rides the persisted body onto resume * legs (defensive per-item DROP there, never a brick). */ agents?: AgentDefinition[]; /** [ref]② (core 1.295 `TaskSpec.retainBackgroundProcesses`): opt-out of the task-end reaping of session * background processes ("keep my dev server running"). On the host lane this is a RESOURCE-RESIDENCY grant, so * it takes the same single-user gate as backgroundShell/hooks (multi-tenant warns + ignores; advertised via * capabilities.retainBackgroundProcesses). Boolean fail-loud at submit; only `true` rides the spec. */ retainBackgroundProcesses?: boolean; /** S-493(core 7.8.0 [ref] `TaskSpec.excludeAllTools`,cli L-207 / 板 [ref]): **整只工具面卸载** * —— 调用方工具、脚手架、手、MCP、skills 一个都不挂,模型这一 run 里没有任何工具可调、终局是纯文本。 * * 🔴 **与 {@link excludeTools} 是两种语义,刻意不折叠**:`"*"` 是一个合法的调用方工具名,所以「卸全部」 * 不能写成一条通配规则(那会把「卸全部」与「卸一只叫 `*` 的工具」塌成一件事)。两键同带 ⇒ 各自生效 * (全卸之后名单里那几只本来也不在,不矛盾、不拒)。 * 只 literal `true` 上 spec(`false` / 缺席 = 名册照旧,不挂键保持 byte-compat —— 挂一个 `false` 上去 * 等于替引擎重申了一次它自己的缺省);非 boolean 由 HTTP 门 400 `request.field_invalid`,resume 重放 * defensive DROP。per-run 工具面**收窄**无跨租户面 ⇒ 无租户门(`excludeTools` 同姿势)。 * 探测:本键在不在受理面由**键闭集**自己回答 —— 没接线的 server 版本对它 400 `request.body_shape` * 并逐字列名,所以**不加能力位**(一个键一条探测路,不长第二条)。 */ excludeAllTools?: boolean; /** [ref]② (core 1.314 工具面控制批): roster 真卸载清单(schema 不上 wire,manifest 收窄)。wire 工具名; * tighten-only(core 继承不变量 union 进子 spawn)。数组-of-string fail-loud at submit。 */ excludeTools?: string[]; /** [ref]② (core 1.314): [ref] 延迟披露清单(占位上 wire,ToolSearch 激活)——cli「Workflow 默认开 * 不暴露」的承载位。同 excludeTools 的校验/继承姿势。 */ deferTools?: string[]; /** [ref]/[ref] (core 1.328 R2): 提示词双形轴——simple(CC 212 短形,引擎缺省)| classic(长形,可按 * 任务/模型试分)。纯呈现轴无租户门;枚举 fail-loud at submit;缺省不挂键。 */ promptProfile?: "simple" | "classic"; /** [ref]②(core 5.14.0 座已在,`TaskSpec.toolMaterializeStrategy`):deferred 工具在**激活之后**怎么给 * 模型供 schema。`static` = 占位符形不变(激活只改可调用性,广告的 schema 仍是空对象);`swap` = 激活 * 后下一轮把占位符换成真 schema。 * 为什么要上 wire:cli 的 [ref] 取证表明,忠实跟随广告 schema 解码的 provider(openai-completions 车道) * 在 `static` 下会**无界循环** —— 每轮发 `{}` → invalidArgumentsRejection → 下一轮广告仍空,纠错回路 * 结构上无效。core 的缺省链是 `spec.toolMaterializeStrategy ?? env ?? `,而**进程级 env 说不了 * 「每个模型」的话**(模型目录车道 per-model api 可混),所以必须有一个**按任务**的位子——这条理由与 * core 缺省是哪个词无关,故不随其变。 * ⚠️ core 缺省本身已翻:5.15.0 BREAKING 起 `` = **`"swap"`**(5.14 世代是 `"static"`; * core 把 `"static"` 重新定性为「a strict explicit opt-in」)。本注上一版还写着 `?? "static"`,照它推 * 「不挂键 ⇒ 落 static」会反。缺省词的属主是 core,本仓不复述第二份——要确认取值去读 core 的 * `prepare-task` 与其 CHANGELOG。 * 缺省不挂键(交给 core 的 env/缺省链);未知词 400 fail-loud(与 promptProfile/permissionMode 同姿势 —— * 静默折缺省会让调用方以为切了策略却没切)。 */ toolMaterializeStrategy?: "static" | "swap"; /** R4 (CC parity): the LIGHT top-level per-turn permission-mode intent (CC `permissionMode`). The shell sends * the RAW mode (axis-agnostic, no client-side interpretation); the SERVICE interprets it tighten-only vs the * deployment baseline. Post-[ref] all FIVE modes are honored as gate-SHAPE choices (`plan` ⇒ read-only hands + * `present_plan`; `acceptEdits`/`bypassPermissions`/`auto` select how much the mode-derived fs-write ask gate * asks — they can never subtract from deployment/operator policy, tightenTaskSpec deny-wins; the old "LOOSEN → * coerced to default" note was pre-[ref] doc-rot). Folds onto the SAME tighten-only governance as * `settings.permissions.defaultMode` (the heavier bundle), so it's a lighter alias, not a second path. * [ref]-②: CLOSED enum on the wire — an unknown word 400s at submit (`request.field_invalid`, sibling of * promptProfile) instead of silently coercing to `default`; resume replay of a stored body stays lenient * (coercePermissionMode). */ /** 🔴 型**从值表派生**(S-496 U1 / [ref]):闭集的属主是 `task-settings.ts` 的 * `SETTINGS_PERMISSION_MODES`(受理腿 `isSettingsPermissionMode` 与两句 400 文案同读那一份)。 * 改前这里是第 6 份手抄的五词联合 —— 加/退一个模式时 wire 型面与受理腿会各走各的。 */ permissionMode?: SettingsPermissionMode; /** * **#1000**(core 7.26.0 `TaskSpec.approverPosture`;CC-116 装配口)—— 宿主对这个 run 的**审批姿态声明**: * 「这些卡由我自己自动答」。一个词(`"bypass"`),闭集属主在 core(`ApproverPosture` / `APPROVER_POSTURES`)。 * * 🔴 **它不授权任何东西,也不是一只门**(core 头注逐字:"a statement about who answers this run's approval * cards, never an instruction to the gate";引擎只把它**渲**给模型,没有任何裁决腿读它)。所以它与 * {@link permissionMode} 的 `"bypassPermissions"` **不是**一回事,也不能互相派生:后者选的是**门的形状** * (且只能收紧、deny-wins),前者只说「谁来答卡」。两者在语义上同族,声明它而不开 `bypassPermissions` * 只是对模型说了一句与实际不符的话 —— 见契约文档那一段的「谁受伤」。 * * 🔴 **本仓零判据、原样过境**(`boot/resolve-spec.ts` 写点):词的拒绝逻辑属引擎 * (`refuseInvalidApproverPosture` 响亮拒一个不认识的词),server **不加第二道门** —— 在下游再判一次就是 * 同一语义面的第二个判官(源头修复纪律 [ref]),而且本仓抄一份词表的那天 core 加第二个词就会被静默 400 掉。 * 型也从 core 的 `ApproverPosture` 派生(不抄字面联合)。 */ approverPosture?: ApproverPosture; /** MF-30 memory PAUSE (shell-host contract, option B per-request — clay 2026-06-27): `false` makes THIS run * read-only over long-term memory (`TaskSpec.memory.writeScope:null` — the [ref] memory ENGINE materializes/ * reads but its harvest commits nothing). Absent/`true` ⇒ normal read+write. The shell's `/memory` pause carries * this per request (no stored per-session flag). Re-applies on resume (rides in the persisted body). * ⚠️ **NOT the same axis as the deployment's `MEMORY_PERSISTENCE_CAPABLE:false`** (core 5.27.0 / [ref] F2): * that one declares the SESSION restricted — materialize/search/harvest serve the committed account and * unbacked disk divergence is refused (`restricted_divergence`). A `writeScope:null` PLANE keeps its ordinary * adopt-on-read semantics: a paused run still sees the user's hand-edits and git-pull drops, it just commits * nothing. Restriction is the session's DECLARATION, never the plane's structure. */ memoryWrite?: boolean; /** [ref] §2.1(core 7.0.0 片2/3;server [ref]):**session 记忆采集 opt-out 声明** —— `"off"` = 「本会话 * 不进长期记忆」:单成员闭集,**没有** `"on"`(缺席就是照常采集的唯一形)。透传为 `TaskSpec.memory.capture`, * 由 core 按部署姿态(`MEMORY_CAPTURE_POLICY`)+ 该 principal 的 `allowMemoryOptOut` verdict 裁:拒 = 403 * `memory.capture_optout_denied`。**一次性**:声明落一条单向控制面记录,后续 resume 恒不采集(record wins), * 同会话重复声明幂等;要回到采集只能开新会话。与 `memoryWrite:false`(本 run 只读平面,可逐 run 切换) * **不是一个轴**:那是「这次别写」,这是「这个会话永远别记」。 * ⚠️ 拼写门**两腿同判、响亮拒**:fresh 400 `request.field_invalid`;resume 重放同判(不做 resume 降级 —— * 把一条隐私请求按打字错误静默丢掉恰是 core 禁的方向;core 的 `config.memory_capture_spelling` 是第二道墙)。 * 记忆面暗的部署(引擎未接线 / 多租户)上本键随 `memory` 整体缺席而无处落 —— 那里本就零采集。 * Rides the persisted body onto resume legs(与 memoryWrite 同姿势)。 */ memoryCapture?: "off"; /** core [ref] r5 §5 W3(server S-405):**请求级「记忆整面关」声明** —— `"off"` = 这一次运行**不挂记忆面** * (没有 `# Memory` 段/索引/三只记忆工具/写准入)。单成员闭集,**没有** `"on"`(缺席就是记忆照常挂的唯一形; * 声明只能单向收紧)。透传为 `TaskSpec.memory.enabled:false` —— ⚠️ **保留事实,不折成整键缺席**:core 的 * 终局观测面把「有 spec 但被关」读作 `reason:"disabled"`、把「没有 spec」读作 `reason:"no-spec"`,审计上 * 不是一件事,所以本仓铸的是 `{ …正常 scopes, enabled:false }`。 * ⚠️ **语义如实**:这是「不挂记忆面」,**不是**文件系统级的禁读禁写 —— 记忆根若落在本次授权的工作区内, * 普通 Read/Write 照走普通围栏(没有记忆面 = 没有记忆写门,不是多了一道禁令)。 * 与 `memoryCapture:"off"` **不是一个轴**:那是「记忆照常挂,但本会话的内容永远不进长期记忆」 * (采集面,落一条单向会话记录);这是「本次运行整个记忆面都不挂」(读写整面,逐请求)。 * 🔴 **两键同发时不要以为买到了两件事**(亲核 core 7.21.1 `prepare-memory.js`):core 把 capture 腿与 * 引擎挂载合取,`enabled:false` 下 capture 的**单向会话记录不落**(core 发 `phase:"config"` 一行披露)⇒ * 下一 turn 不带本键时采集照常。要「这个会话永久别记」,那一 turn 就**别**同时发本键。详见契约 §12.7。 * 与 `memoryWrite:false`(本 run 只读平面:照常挂、照常读、harvest 不提交)同样正交、同样可并存。 * 🔴 **与 `GET /v1/capabilities` 的 legacy 能力位 `memory`(恒假)同名不同物**:那一位说的是退役的 * MemoryStore HTTP 动词族,与本键零关系 —— 别按那一位判断本键可不可用。 * ⚠️ 拼写门**两腿同判、响亮拒**:fresh 400 `request.field_invalid`;resume 重放同判(不做 resume 降级 —— * 把一条收紧声明按打字错误静默丢掉是不可逆方向的静默降级)。两腿共用同一只判据 * (`request-value-closure.ts` 的 `singleMemberOffFieldIssue`,与 `memoryCapture` 同表)。 * 记忆面暗的部署(引擎未接线 / 多租户)上 `memory` 整键本就缺席,本声明无处落也无事可关。 * Rides the persisted body onto resume legs(与 `memoryWrite` 同姿势)。 * ✅ **子代地板自 core 7.22.0 到货,本仓零接线**:core 的 `parentSpecSeatOf` 从**这条 run 自己的** * `spec.memory.enabled === false` 铸 `ParentSpecSeat.memoryOff`,`applyParentSpecSeat` 作**无条件 * REMOVAL** 施加(改写成 `{ …子代自己的 scopes, enabled:false }`,永不删键)⇒ 父关 ⇒ 子代 / 孙代 / * workflow 子 spec / 跨 run 唤醒与保留 resume 一律无记忆面。🚧 **tier-3 耐久复活不带**(core 已记残余)。 * 链路的机器锚:`test/memory-off-request.test.ts` ⑥ 段(亲读 core dist 的铸点与施加点)。 */ memory?: "off"; /** 142-S4 projectId 线程化([ref] §2):这个 run 归属的项目(center 登记簿键,generic lowercase * UUID — core S1 `resolveProjectId` marker 或 S3 无仓 mint 的产物;客户端只透传,零铸造权威)。是「哪个 * 项目」的选择器,不是身份/capability — scope 的 tenant 段永远来自 verified principal。SHAPE 门在 * authorizer(core `PROJECT_ID_REGEX`,不合形状=422 typed `invalid_project_id`)。**大小写不敏感收、 * 归一小写用**([ref]/[ref]:词法与 core 的格式单源对齐,带 /i;收下后折小写再派生 scope/查表 —— * 同一个 id 的两种大小写写法落同一只记忆盘)。多租户 DB 记忆面下它把派生 * scope 从 user 盘切到项目盘(`proj:/`)并以 config.projects[projectId].defaultScopes * 作 memory.scopes 种子;单用户部署忽略派生(memoryScopeFor 不变),仅 defaultScopes 种子生效。 * Rides the persisted body onto resume legs(与 memoryWrite 同姿势)。 */ projectId?: string; /** [#40 / TOC cwd seam, SDK 0.0.36] The TOC client's launch directory — the agent's HOST execution-env workspace * runs HERE (CC-parity: `sema` in a project dir operates on THAT project). An ABSOLUTE host path. 🔒 honored ONLY * on the single-user `host` lane (see task-cwd.ts `cwdHonored`); IGNORED on cloud/container lanes + multi-tenant * (a caller can't point a shared/cloud worker at an arbitrary host path). Validated absolute (prepareSpec 400s a * relative cwd — it would silently resolve against the SERVICE process cwd). */ cwd?: string; /** * S-470(device lane 首绑写协议,design `device-executor-lane-v2` §4.3.2)—— 这条**根会话**要执行在 * 哪台已注册的员工设备上(`POST /v1/devices/enroll` 返回的 `dev_`)。 * * 与 {@link cwd} **同级同信任姿势**:调用方供给、server 闸裁 —— 它不是身份,身份恒取验证过的 * 复合身份链;它只是「在我自己名下的设备里挑一台」。 * · 仅 `REMOTE_EXEC=device` 的部署接受;其它车道收到它 = **400 响亮拒**(不静默忽略 —— 调用方会 * 以为自己指定了执行设备,而那条声明从未存在); * · 仅**根任务**提交携带(子代无条件继承根绑定,core 为 subagent/cascade/verify 另铸 sessionId); * · 首绑 = INSERT-if-absent CAS(并发单赢家);后续根提交:缺席 = 沿用绑定 / 携且同 = 幂等 / * 携且异 = 403 `device.identity_mismatch`(**v1 无隐式重绑**,换设备走 * `POST /v1/devices/sessions/:rootSessionId/rebind`); * · resume 家族(`/decide` / `/answer` / `/plan_review` / `/wake`)**不收**这个键:车道随树固定。 */ deviceId?: string; /** [ref] (CC `--add-dir`, core `TaskSpec.additionalDirectories`): extra host dirs the FILE tools may reach * beyond the containment root. 🔒 Gated exactly like `cwd`/`shellEnv` — honored ONLY on the single-user host * lane (`task-cwd.ts` `cwdHonored`); off that lane `boot/resolve-spec.ts` drops them with a loud * `task_additional_directories_ignored` (never silently). SHAPE is fail-loud at submit (absolute host paths, * no `..` segments, ≤ MAX_ADDITIONAL_DIRS entries) — separate from whether the lane honors them. * `additionalReadDirectories` (core 5.11.0) is the same door with a READ-only semantic: it widens the read * containment (classify auto-allow + read_file/grep) and never the write face. Rides the persisted body onto * resume legs. */ additionalDirectories?: string[]; additionalReadDirectories?: string[]; /** [ref]⑧/[ref]/[ref] (core 5.23.0 `TaskSpec.oneShot`): this SUBMISSION is one-shot — no later turn exists * in which an async background notification could land (the archetypal case is a headless `sema -p` whose * process exits when the turn ends). PER-REQUEST on purpose: "does this submission expect to be continued" is * a property of the submission, not of the connection it arrived on. core consumes it as GUIDANCE ONLY (the * `RunWorkflow` / delegation receipts tell the model to block-wait via `TaskOutput({block:true})` instead of * "end your turn, you will be notified" — the latter is actively wrong here and loses background results); it * grants nothing, so there is no tenancy gate. Sibling of `interactiveTools` (the same `-p` posture) and passed * through the same way: a boolean rides, absent/garbage ⇒ key omitted (core's default = interactive). Rides the * persisted body onto resume legs. */ oneShot?: boolean; /** [ref]①/[ref]② (core 1.296 three-state knob): per-request mount toggle for the interactive ask tools — * `false` unmounts AskUserQuestion for this submission (the headless `-p` posture; `oneShot` above is its * sibling, same passthrough), `true` forces the mount, absent = core's own default (delivery-face probing). * A boolean rides the spec unchanged (boot/resolve-spec.ts); a present non-boolean is a fail-loud 400 on * submit (server.ts prepareSpec), and resume legs replay the persisted body through the same defensive * typeof read. Declared per [ref] 件1 ([ref] boundary-must-schema): the submit chain reads this key, and an * undeclared read is a bare cast the exported type cannot audit (embedders could not even write it). */ interactiveTools?: boolean; /** The caller's per-request settings stamp (the client-side `SemaSettings` shape). The KEY SET is deliberately * NOT mirrored here — a hand-written second shape on this interface would be the classic mirror-drift form. * Per-key ownership is split, not single: `parseTaskSettings` (src/task-settings.ts) defensively owns the * shared settings keys on every path, while `settings.webSearch` has its OWN validator on the single-user * scenario lane (capabilities/scenarios.ts `webSearchConfigFromSettings` — the multi-tenant lane ignores it * by design). The declared type is the INTENDED caller form (sibling convention: `oneShot` declares boolean, * runtime drops garbage): a non-array object bag, or omit the key. A `null` reads as absence on both the * fresh-submit wall (prepareSpec) and replay (parseTaskSettings), and malformed forms 400 on submit / * no-op on replay. ([ref] 件1 leftover key, declared 2026-08-31; ownership wording per codex adversarial round.) * * 🔴 [ref] 件①(PM 裁「拒」[ref])—— **KEY SET 上运行时不再比类型宽**:`Record` 的开放 * 下标是 TS 形状,不是受理面。FRESH 提交腿上顶层 / `settings.permissions` 的**受理键是闭集** * ({@link taskSettingsKeyIssue} 的两张表),集外键 400 `request.body_shape` + 逐字列名(`unknownKeys` / * `unsupportedKeys`)。RESUME 重放腿照旧宽容(`parseTaskSettings` 忽略未知键,4xx 会把存量 parked 任务 * 砖死)——「运行时比类型宽」这句现在**只对值形成立,对键集不成立**。 */ settings?: Record; /** [R3] Caller-supplied per-request MCP servers (the TOC client's local `.mcp.json`), aligned to core * `McpServerSpec`. 🔒 honored on any SINGLE-USER deployment (task-mcp.ts `mcpInjectionHonored` = `requirePrincipal!==true`) * — the requester is the super-admin of their OWN worker (CC-parity), on ANY execution lane (the stdio MCP runs on the * worker, not the exec env); IGNORED on multi-tenant deployments (can't make the shared worker run an arbitrary * command). Validated + gated downstream; the center/config baseline wins on a name clash. * * [ref](core 5.60.0):条目上可选的 `contentOrigin`(`"local"|"execution"|"external"`)= **内容 * 出处声明**,只影响记忆写治理(会话污染标记),对审批门/策略/角色表零作用。缺席 = pre-378 逐字节 * 现行为(判 external)。🔴 **新提交(fresh)腿上,词表外的值 ⇒ 同步 400 `request.field_invalid`** —— * 静默丢会把一次信任边界声明变成探针。⚠️ **耐久 resume 腿上不拒**:那条腿重放的是存量 body,4xx 会把 * 一条 parked 任务永久砖死(折成 `409 resume_blocked_by_policy`),所以坏值在那里**降级**为「键缺席」+ * 一条 `mcp_content_class_dropped` warn(本仓三处 `opts.leg` 先例的同款姿势)。两条腿上 server 本体都 * 保留 —— 丢的只是声明。core 把这个键定性为**部署面**的键,本仓对它的 reject-or-strip 义务由既有的单用户闸 * **结构性**兑现:多租户腿整只 `mcpServers` 被忽略 ⇒ 租户够不着;单用户腿 = core 明文豁免的超管面 * (完整论证见 `task-mcp.ts` 的 `assertRequestMcpContentOrigin` 顶注)。部署面的正门是 center 下发的 * `CenterMcpServer.contentOrigin`。 */ mcpServers?: McpServerSpec[]; /** [DESIGN-269 车1] Caller-supplied per-request A2A peers (remote agents this task may call), aligned to * core `A2aServerSpec`. 🔒 honored on a SINGLE-USER deployment that has neither locked the `a2a` key nor * had `a2a_peers` denied by its compliance posture (task-a2a.ts `a2aInjectionHonored`, the same three-veto * predicate `capabilities.a2aInjection` advertises) — the requester is the super-admin of their OWN worker, * on ANY execution lane (an A2A call is a plain outbound HTTPS request from the worker, so the lane where * tool calls run is irrelevant); IGNORED on multi-tenant (a tenant can't point the shared worker at an * agent of their choosing). A locked deployment refuses the whole request instead: 400 `config.locked_key`. * 🔴 Every skill a peer advertises mounts `egress:true` + `effect:"write"` (core's ruling) — declaring a * peer is declaring an outbound WRITE channel, not a data source. Validated + gated downstream; the * center/config baseline wins on a name clash (the peer name IS the tool namespace). */ a2aPeers?: A2aServerSpec[]; /** CC parity (Workflow super-set): per-task activation of the LLM-authored workflow engine (core `run_workflow`). * `true` → `spec.selfOrchestration` (the engine mounts run_workflow), gated on the deployment's `selfOrchestrationEnabled` * (else a harmless no-op) + core's `allowWorkflows` per-principal entitlement on multi-tenant (single-user honors * directly). Absent/false ⇒ no workflow tool this run. */ selfOrchestration?: boolean; /** §4 (CC parity, mirrors `selfOrchestration`): per-task opt-in for CC `/fork` — mount core's `Fork` tool so the * model can fork the session (a child inherits the parent's full context + shares the prompt-cache prefix). `true` → * `spec.enableFork`; core additionally requires a durable fork-capable session store (`hasSessionFork` — the TOC file * backend satisfies it), else it stays inert. Absent/false ⇒ no Fork tool this run (default). Forks the CALLER's OWN * session (owner-checked authorizer), so no cross-tenant surface. */ enableFork?: boolean; [k: string]: unknown; } /** [ref] D-1: the operator-echoed decision-action binding carried on POST /v1/approvals/:sessionId/decide([ref]C-4:裸 :id 臂已删). * All optional for back-compat — a legacy BFF caller omitting them resumes on the current-pending values * exactly as before (the pre-D-1 path), tracked by a deprecation counter. * [ref] A9: hoisted VERBATIM out of `createHttpServer`'s closure so the approvals/assistant route domain * and the resume leg can name the SAME shape without a routes/* → server.ts value edge. */ export type DecideBinding = { /** The capability token the operator actually SAW (from listPending). A mismatch with the current pending * ⇒ approval_stale (they decided a superseded/resolved view). */ checkpointToken?: string; /** The pendingAction.toolCallId the operator saw — echoed into ResumeOutcome.boundCallId. */ boundCallId?: string; /** The server-minted opaque boundInputHash the operator saw — echoed VERBATIM (never recomputed here). */ boundInputHash?: string; /** [ref] edit: operator-corrected tool args, applied by core AFTER the binding check, on approve only. */ updatedInput?: unknown; }; /** [ref]([ref]④→[ref] 坐标轮换判定):409 `approval_stale` 拒体上的 additive 机读指路 —— * **本会话当前 pending** 的 D-1 回显坐标,让持旧坐标的壳一跳重定位(免重拉列表)。 * 键名/键形与 GET /v1/approvals 的 pending 行逐字同名(壳复用同一解析)。 * 🔴 恒**不含** checkpointToken(runs.ts §12-C:resume 凭证从不外发);只在当前 pending **在场**、 * 属 tool_approval、且已过路由级 owner 门的域内铸 —— 无 pending / 非工具门 / 读失败 ⇒ 键缺席(不造键)。 */ export type ApprovalStaleCurrentPending = { /** 当前 pending 被门住的工具名(与 pending 行的 `toolName` 同源同值)。 */ toolName: string; /** 当前 pending 的 `pendingAction.toolCallId` —— 壳重定位后 decide 时回显的 D-1 锚。 */ boundCallId: string; /** 当前 pending 的 server 铸 `boundInputHash`(行上有才带;逐字回显,壳不重算)。 */ boundInputHash?: string; }; /** S-528 —— `wiring_manifest` **起手接线回执**在 wire 上的值形。 * * 🔴 **从投影器的返回型派生,不手抄键**:键表的唯一属主是 `trace/project.ts` 的 * {@link wiringManifestFaceEventData}(它自己又把 `tools` 段的判形属主让给 core 的 typebox schema)。 * 手抄一张键表在这里,就是本仓 [ref] 点名的那类「手抄投影」—— core 加一段时它既不红也不跟。 * * 🔴 **诚实边界**:派生出来的是 `Record` —— 投影器本来就只说得出这个宽度(它的输入是 * `unknown`,产物按段 present-iff 铸)。所以这一行**不**声称「wire 上的每一段都有静态型」;它声称的是 * 「这一键的值恒等于那只投影器的产物」,而那正是消费端唯一需要的那句话。段级判据的属主在 core。 */ export type WiringManifestReceipt = ReturnType; /** S-528 —— `POST /v1/tasks`(**非流式**)200 体在 core `TaskResult` / park 回执之外带的**附加键**。 * * 两键都是 **additive + present-iff**,同一只装饰点铸(`routes/tasks.ts` 的 `withSyncSubmitKeys`), * 所以三处 200 出口(终局 / suspended / needs_review)与幂等重放腿自动同形: * · `notice` —— [ref] §2 发现性指路,**present-iff** 本次提交带了 verify / cascade; * · `wiringManifest` —— **present-iff** 引擎在本条 run 上真产了 `wiring_manifest` 帧。缺席 = 「这台引擎 * 没给回执」(老 core / 未挂配置面),**不是** `null`、**不是** `{}` —— 把「没这回事」铸成一个值, * 消费端就再也分不出「零工具」与「没说」。 */ export interface SyncSubmitExtraKeys { notice?: string; wiringManifest?: WiringManifestReceipt; } //# sourceMappingURL=wire-types.d.ts.map