diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index cb94678776..8c1a7283c0 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -256,7 +256,7 @@ overrideOf(session: Session): ApprovalPolicy | undefined Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalPolicy](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [Session](../core-data-structures/session.md) -Source: [`packages/ui/user-approval/src/index.ts:248`](../../packages/ui/user-approval/src/index.ts) +Source: [`packages/ui/user-approval/src/index.ts:251`](../../packages/ui/user-approval/src/index.ts) ## `ctx.bash` — `BashExecutor` (abstract seam) @@ -957,10 +957,11 @@ The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mo ```ts cordis-catalog /** * Resolve the complete policy for one capability call. An approved explicit - * mode outranks the session's last `sandbox/mode` event, which outranks the - * deployment default. A session cwd is its workspace-write boundary; the - * configured root is the fallback for agentless calls and sessions without a - * cwd. + * mode outranks the session's override chain ({@link overrideOf}: own + * post-seed switches, else the inherited header baseline), which outranks + * the deployment default. A session cwd is its workspace-write boundary; + * the configured root is the fallback for agentless calls and sessions + * without a cwd. * @param request - optional session and approved mode override. * @returns the fully resolved per-call mode and absolute workspace root. */ diff --git a/packages/cordis/tool-cordis/src/api-catalog.ts b/packages/cordis/tool-cordis/src/api-catalog.ts index 69b30d2421..dca9c8e4a6 100644 --- a/packages/cordis/tool-cordis/src/api-catalog.ts +++ b/packages/cordis/tool-cordis/src/api-catalog.ts @@ -488,7 +488,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ methods: [ { signature: 'resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy', - jsDoc: '/**\n * Resolve the complete policy for one capability call. An approved explicit\n * mode outranks the session\'s last `sandbox/mode` event, which outranks the\n * deployment default. A session cwd is its workspace-write boundary; the\n * configured root is the fallback for agentless calls and sessions without a\n * cwd.\n * @param request - optional session and approved mode override.\n * @returns the fully resolved per-call mode and absolute workspace root.\n */', + jsDoc: '/**\n * Resolve the complete policy for one capability call. An approved explicit\n * mode outranks the session\'s override chain ({@link overrideOf}: own\n * post-seed switches, else the inherited header baseline), which outranks\n * the deployment default. A session cwd is its workspace-write boundary;\n * the configured root is the fallback for agentless calls and sessions\n * without a cwd.\n * @param request - optional session and approved mode override.\n * @returns the fully resolved per-call mode and absolute workspace root.\n */', }, { signature: 'overrideOf(session: Session): SandboxMode | undefined', diff --git a/packages/sandbox/sandbox-policy/README.i18n.yaml b/packages/sandbox/sandbox-policy/README.i18n.yaml index 77822b4b58..a06a489cfb 100644 --- a/packages/sandbox/sandbox-policy/README.i18n.yaml +++ b/packages/sandbox/sandbox-policy/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -README.md: ca6ee67ab0cdb645bba135b3ca4f542844306857 -README.zh.md: a0c43235de97872ef734f0bef3cc6ed16930e232 +# pnpm run verify-translation-pairing --write packages/sandbox/sandbox-policy/README.md +README.md: be1125b1ec30e1db10223fe1023cc5dd7a57a3a7 +README.zh.md: 3ef9e5310570b1f2597fde99ff5cfcebb14096c1 diff --git a/packages/sandbox/sandbox-policy/README.md b/packages/sandbox/sandbox-policy/README.md index ca6ee67ab0..be1125b1ec 100644 --- a/packages/sandbox/sandbox-policy/README.md +++ b/packages/sandbox/sandbox-policy/README.md @@ -15,9 +15,9 @@ Two families enforce the same mode vocabulary: the sandboxed bash executor (`@de ## Surface -- `ctx.sandboxPolicy.resolve({ session?, mode? })` — resolves one complete per-call policy. An explicit approved mode outranks the session's last `sandbox/mode` event, which outranks `defaultMode`; the session's immutable `cwd` is canonicalized with filesystem semantics before becoming `workspaceRoot`, otherwise the configured fallback applies. Canonicalization precedes lexical normalization so `symlink/..` agrees with process working-directory resolution. +- `ctx.sandboxPolicy.resolve({ session?, mode? })` — resolves one complete per-call policy. An explicit approved mode outranks the session's override chain (`overrideOf`, below), which outranks `defaultMode`; the session's immutable `cwd` is canonicalized with filesystem semantics before becoming `workspaceRoot`, otherwise the configured fallback applies. Canonicalization precedes lexical normalization so `symlink/..` agrees with process working-directory resolution. - `ctx.sandboxPolicy.defaultMode` / `ctx.sandboxPolicy.workspaceRoot` — the deployment default and fallback root used by `resolve()`. -- `effectiveSandboxMode(events)` — the pure fold of a session's `sandbox/mode` events (the last switch wins, or `undefined`), used inside `resolve()`. +- `effectiveSandboxMode(events)` — the pure fold of a slice of `sandbox/mode` events (the last switch wins, or `undefined`), the building block `sandboxOverrideOf` composes with the seed boundary and the header baseline. - `setSandboxMode(session, mode)` — THE write path for a per-session override: appends exactly one `sandbox/mode` event. The switch IS its event; nothing mutates the mode out of band. - `ctx.sandboxPolicy.overrideOf(session)` (the pure `sandboxOverrideOf` export, also consumed by the permission presets) — the session's override chain, never the deployment default: with an inherited `sandboxMode` header baseline (a delegation child), the fold of the session's OWN switches past `SessionHeader.seedLength`, else the baseline, validated against the closed vocabulary on read (throws on foreign values — a durable boundary); without one (a top-level session or a generic `SessionStore.fork` child), the whole-log fold, so seed-carried switches remain the replayed inherited truth. The in-process subagent driver captures this at delegation and writes it into each child's creation-time header, so a delegating parent's tightened mode binds its children with no first-turn timing window ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)). - `SANDBOX_MODES` — every mode, for option advertisement and runtime validation. @@ -26,7 +26,7 @@ The optional `./invariant` companion rejects a forged durable `sandbox/mode` eve ## The per-session store -A runtime switch is one log-only `sandbox/mode` event on the session it applies to. `effective = explicit grant ?? fold(events) ?? deployment default`, so an override survives restart by replay and two sessions never see each other's state. Workspace identity does not need another event: the immutable `SessionHeader.cwd` recorded at creation is the root for every call in that session. The event is log-only (the `approval/*` precedent): the model learns the mode from the enforcing tools' denial markers, never from the event. +A runtime switch is one log-only `sandbox/mode` event on the session it applies to. `effective = explicit grant ?? override chain ?? deployment default`, where the override chain is `sandboxOverrideOf`'s fold of the session's OWN post-seed switches, else the inherited header baseline — so an override survives restart by replay, a delegation child starts under its parent's captured policy, and two sessions never see each other's state. Workspace identity does not need another event: the immutable `SessionHeader.cwd` recorded at creation is the root for every call in that session. The event is log-only (the `approval/*` precedent): the model learns the mode from the enforcing tools' denial markers, never from the event. ## Model Experience diff --git a/packages/sandbox/sandbox-policy/README.zh.md b/packages/sandbox/sandbox-policy/README.zh.md index a0c43235de..3ef9e53105 100644 --- a/packages/sandbox/sandbox-policy/README.zh.md +++ b/packages/sandbox/sandbox-policy/README.zh.md @@ -15,9 +15,9 @@ ## 表层 -- `ctx.sandboxPolicy.resolve({ session?, mode? })`:解析一项完整的逐调用策略。显式批准的模式优先于会话最后一条 `sandbox/mode` 事件,后者又优先于 `defaultMode`;会话不可变的 `cwd` 会先按文件系统语义规范化,再成为 `workspaceRoot`,否则使用配置的回退值。规范化先于词法归一化,因此 `symlink/..` 与进程工作目录解析保持一致。 +- `ctx.sandboxPolicy.resolve({ session?, mode? })`:解析一项完整的逐调用策略。显式批准的模式优先于会话的覆盖链(见下文 `overrideOf`),后者又优先于 `defaultMode`;会话不可变的 `cwd` 会先按文件系统语义规范化,再成为 `workspaceRoot`,否则使用配置的回退值。规范化先于词法归一化,因此 `symlink/..` 与进程工作目录解析保持一致。 - `ctx.sandboxPolicy.defaultMode`/`ctx.sandboxPolicy.workspaceRoot`:`resolve()` 使用的部署默认值与回退根。 -- `effectiveSandboxMode(events)`:会话 `sandbox/mode` 事件的纯 fold(最后一次切换胜出,没有则为 `undefined`),在 `resolve()` 内使用。 +- `effectiveSandboxMode(events)`:对一段 `sandbox/mode` 事件切片的纯折叠(最后一次切换胜出,没有则为 `undefined`),是 `sandboxOverrideOf` 与种子边界和会话头基线进行组合时所用的基础构件。 - `setSandboxMode(session, mode)`:逐会话覆盖的唯一写入路径:恰好追加一条 `sandbox/mode` 事件。切换本身就是事件;不会在带外修改模式。 - `ctx.sandboxPolicy.overrideOf(session)`(即纯函数导出 `sandboxOverrideOf`,也供权限 preset 消费):会话的覆盖链,绝不包含部署默认值:当存在继承的 `sandboxMode` 会话头基线时(即委派子 agent),先折叠会话自己在 `SessionHeader.seedLength` 之后的切换,否则取该基线,读取时按封闭词汇校验(遇到词汇之外的值即抛出异常——这是一条持久边界);没有基线时(顶层会话或通用的 `SessionStore.fork` 子会话),折叠覆盖完整日志,因此种子携带的切换仍是回放所得的继承事实。进程内 subagent 驱动器在委派时捕获该值,并写入每个子 agent 创建时的会话头,使发起委派的父级收紧后的模式约束其子 agent,且不存在任何第一轮次的时序窗口(参见[设计原理](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md))。 - `SANDBOX_MODES`:所有模式,用于选项展示与运行时验证。 @@ -26,7 +26,7 @@ ## 逐会话 store -运行时切换是在对应会话日志中追加的一条 `sandbox/mode` 事件。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖会通过回放跨重启保留,两个会话也绝不会看到彼此状态。Workspace 标识无需另一条事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。该事件只进入日志(沿用 `approval/*` 先例):模型通过强制执行工具的拒绝标记获知模式,绝不会从事件获知。 +运行时切换是在对应会话日志中追加的一条 `sandbox/mode` 事件。`effective = explicit grant ?? override chain ?? deployment default`,其中覆盖链(override chain)是 `sandboxOverrideOf` 折叠会话自己在种子之后的切换所得,否则取继承的会话头基线——因此覆盖会通过回放跨重启保留,委派子 agent 会在其父级捕获的策略下启动,两个会话也绝不会看到彼此状态。Workspace 标识无需另一条事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。该事件只进入日志(沿用 `approval/*` 先例):模型通过强制执行工具的拒绝标记获知模式,绝不会从事件获知。 ## 模型体验 diff --git a/packages/sandbox/sandbox-policy/src/index.ts b/packages/sandbox/sandbox-policy/src/index.ts index dd4f2d6465..dd35c303af 100644 --- a/packages/sandbox/sandbox-policy/src/index.ts +++ b/packages/sandbox/sandbox-policy/src/index.ts @@ -90,10 +90,11 @@ export class SandboxPolicyService extends Service { /** * Resolve the complete policy for one capability call. An approved explicit - * mode outranks the session's last `sandbox/mode` event, which outranks the - * deployment default. A session cwd is its workspace-write boundary; the - * configured root is the fallback for agentless calls and sessions without a - * cwd. + * mode outranks the session's override chain ({@link overrideOf}: own + * post-seed switches, else the inherited header baseline), which outranks + * the deployment default. A session cwd is its workspace-write boundary; + * the configured root is the fallback for agentless calls and sessions + * without a cwd. * @param request - optional session and approved mode override. * @returns the fully resolved per-call mode and absolute workspace root. */ diff --git a/packages/sandbox/sandbox-policy/src/session-mode.ts b/packages/sandbox/sandbox-policy/src/session-mode.ts index c74def8e5e..d2f018818a 100644 --- a/packages/sandbox/sandbox-policy/src/session-mode.ts +++ b/packages/sandbox/sandbox-policy/src/session-mode.ts @@ -1,15 +1,19 @@ /** - * Per-session sandbox-mode override: the session log as the store. A runtime - * switch (a UI policy control or test scenario) is recorded as one - * `sandbox/mode` event on the session it applies to; - * `effective = fold(events) ?? the deployment default`, so an override - * survives restart by replay, two sessions can never see each other's state, - * and there is no external config store. The event is log-only (the - * `approval/*` precedent): the model learns the mode from the boundary - * markers in the enforcing tools, never from the event itself. EXECUTION - * honors the fold through `ctx.sandboxPolicy.resolve()` — it stamps the mode - * together with the calling session's workspace root onto each capability - * call, weakest-precedence beneath an escalation grant. + * Per-session sandbox-mode override: the session log as the store, layered + * over the header's delegation baseline. A runtime switch (a UI policy + * control or test scenario) is recorded as one `sandbox/mode` event on the + * session it applies to; `effective = override chain ?? the deployment + * default`, where the override chain ({@link sandboxOverrideOf}) is the fold + * of the session's OWN post-seed switches, else the inherited + * `SessionHeader.sandboxMode` baseline. An override survives restart by + * replay, a delegation child starts under its parent's captured policy, two + * sessions can never see each other's state, and there is no external config + * store. The event is log-only (the `approval/*` precedent): the model + * learns the mode from the boundary markers in the enforcing tools, never + * from the event itself. EXECUTION honors the chain through + * `ctx.sandboxPolicy.resolve()` — it stamps the mode together with the + * calling session's workspace root onto each capability call, + * weakest-precedence beneath an escalation grant. * * The override is policy state shared by every enforcing family (bash and * filesystem alike), so it lives here in the policy package rather than in any @@ -26,10 +30,11 @@ declare module '@deepseek-ai/dsh-session' { /** * The session's sandbox mode was switched — log-only (like `approval/*`; * NOT a surface event, carries no `surfaceOp`): durable and replayable, - * never in the model transcript. The LAST such event is the session's - * override ({@link effectiveSandboxMode}); who asked for it is derivable - * from position (an event after the log's last `request/header*` was a - * runtime switch by the user; see the tool layer's narrator). + * never in the model transcript. The last such OWN (post-seed) event is + * the session's override ({@link sandboxOverrideOf}); who asked for it is + * derivable from position (an event after the log's last + * `request/header*` was a runtime switch by the user; see the tool + * layer's narrator). */ 'sandbox/mode': { mode: SandboxMode } } @@ -39,10 +44,11 @@ declare module '@deepseek-ai/dsh-session' { export const SANDBOX_MODES: readonly SandboxMode[] = ['read-only', 'workspace-write', 'danger-full-access'] /** - * The session's sandbox-mode override: the last `sandbox/mode` event in the - * log, or undefined when the session never switched (callers apply the - * deployment default). The pure fold — resume needs no catch-up machinery - * because replaying the log IS the state. + * The pure fold of a slice of `sandbox/mode` events: the last switch wins, + * or undefined without one. The building block {@link sandboxOverrideOf} + * composes with the seed boundary and the header baseline — consumers + * resolving a SESSION's policy go through that chain, not this raw fold. + * Resume needs no catch-up machinery because replaying the log IS the state. * @param events - session events in log order (other event types are skipped). * @returns the mode of the last switch event, or undefined without one. */ diff --git a/packages/ui/user-approval/README.i18n.yaml b/packages/ui/user-approval/README.i18n.yaml index eff3ad6622..0b80c98b63 100644 --- a/packages/ui/user-approval/README.i18n.yaml +++ b/packages/ui/user-approval/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -README.md: 251782ac19de54413e6e318d141f39f964900a32 -README.zh.md: 9fab4f5cb73bb893d7c55a2e65c43c96eb9dfa4c +# pnpm run verify-translation-pairing --write packages/ui/user-approval/README.md +README.md: c29ada50950cb6b40065a09bac1becbf4afb0ce4 +README.zh.md: c119f2009298edfaa101b10c7562d0c36324ec5d diff --git a/packages/ui/user-approval/README.md b/packages/ui/user-approval/README.md index 251782ac19..c29ada5095 100644 --- a/packages/ui/user-approval/README.md +++ b/packages/ui/user-approval/README.md @@ -8,7 +8,7 @@ Each request must belong to an open agent turn. The service appends a paired `ap Answerers are `approval/request` waterfall listeners. Return an outcome to answer for an owned agent or call `next()` to delegate. Agent-scoped listeners receive only that agent's requests; compose one terminal answerer per deployment because sibling listener order is not a policy priority mechanism. The ACP automation bridge supplies one-shot machine decisions for sessions it owns. -`ApprovalPolicy` is `'ask'` or `'never'`. The effective value is the last `approval/policy` event, falling back to config; `setApprovalPolicy()` is the write path. `'never'` rejects before interactive dispatch and is the only policy stated in the prompt. Switches produce at most one coalesced notice, attributed positionally over the session's OWN events: to the user when an own override follows the last own `request/header`, to the delegating session when no own override exists and the delta matches the inherited header baseline, and to operator/config otherwise. `ctx.approval.overrideOf(session)` (the pure `approvalOverrideOf` export, also consumed by the permission presets) resolves the session's override chain, never the configured default: with an inherited `approvalPolicy` header baseline (a delegation child), the fold of the session's OWN switches past `SessionHeader.seedLength`, else the baseline, validated against the closed vocabulary on read; without one (a top-level session or a generic `SessionStore.fork` child), the whole-log fold, so a seed-carried `'never'` survives; the in-process subagent driver captures this at delegation and writes it into each child's creation-time header, so a `'never'` parent cannot mint prompting children, with no first-turn timing window ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)). +`ApprovalPolicy` is `'ask'` or `'never'`. The effective value is the session's override chain (`approvalOverrideOf`, below), falling back to config; `setApprovalPolicy()` is the write path. `'never'` rejects before interactive dispatch and is the only policy stated in the prompt. Switches produce at most one coalesced notice, attributed positionally over the session's OWN events: to the user when an own override follows the last own `request/header`, to the delegating session when no own override exists and the delta matches the inherited header baseline, and to operator/config otherwise. `ctx.approval.overrideOf(session)` (the pure `approvalOverrideOf` export, also consumed by the permission presets) resolves the session's override chain, never the configured default: with an inherited `approvalPolicy` header baseline (a delegation child), the fold of the session's OWN switches past `SessionHeader.seedLength`, else the baseline, validated against the closed vocabulary on read; without one (a top-level session or a generic `SessionStore.fork` child), the whole-log fold, so a seed-carried `'never'` survives; the in-process subagent driver captures this at delegation and writes it into each child's creation-time header, so a `'never'` parent cannot mint prompting children, with no first-turn timing window ([rationale](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md)). The tools pipeline routes `ask` decisions through this seam and fails closed when it is absent; the sandboxed bash tool also uses it for escalated retries. The ACP automation bridge answers calls for its own agents through the client's machine policy. Audit events remain log-only, so the model sees only the asking consumer's result. See the [approval-seam Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-approval-seam.md) and [sandbox Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-sandbox.md). diff --git a/packages/ui/user-approval/README.zh.md b/packages/ui/user-approval/README.zh.md index 9fab4f5cb7..c119f20092 100644 --- a/packages/ui/user-approval/README.zh.md +++ b/packages/ui/user-approval/README.zh.md @@ -8,7 +8,7 @@ 应答者是 `approval/request` waterfall(瀑布式事件)监听器。要回答所拥有 agent 的请求,请返回一个结果;否则调用 `next()` 委托。限定到 agent 的监听器只接收该 agent 的请求;每项部署应当组合一个终端应答者,因为同级监听器的顺序不是策略优先级机制。ACP(Agent Client Protocol)自动化桥接层为其拥有的会话提供一次性机器决定。 -`ApprovalPolicy` 为 `'ask'` 或 `'never'`。实际值取最后一条 `approval/policy` 事件,并回退到配置;`setApprovalPolicy()` 是写入路径。`'never'` 会在交互式分发之前拒绝请求,也是提示词中唯一声明的策略。切换最多产生一条合并通知,并按其在会话自己的事件中的位置归因:如果会话自己的覆盖出现在自己最后一个 `request/header` 之后,则归因于用户;如果不存在自己的覆盖且该变化与继承的会话头基线相符,则归因于发起委派的会话;否则归因于操作方/配置。`ctx.approval.overrideOf(session)`(即纯函数导出 `approvalOverrideOf`,也供权限 preset 消费)解析会话的覆盖链,绝不包含配置默认值:当存在继承的 `approvalPolicy` 会话头基线时(即委派子 agent),先折叠会话自己在 `SessionHeader.seedLength` 之后的切换,否则取该基线,读取时按封闭词汇校验;没有基线时(顶层会话或通用的 `SessionStore.fork` 子会话),折叠覆盖完整日志,因此种子携带的 `'never'` 得以存续;进程内 subagent 驱动器在委派时捕获该值,并写入每个子 agent 创建时的会话头,使 `'never'` 父级无法造出会弹出提示的子 agent,且不存在任何第一轮次的时序窗口(参见[设计原理](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md))。 +`ApprovalPolicy` 为 `'ask'` 或 `'never'`。实际值取会话的覆盖链(见下文 `approvalOverrideOf`),并回退到配置;`setApprovalPolicy()` 是写入路径。`'never'` 会在交互式分发之前拒绝请求,也是提示词中唯一声明的策略。切换最多产生一条合并通知,并按其在会话自己的事件中的位置归因:如果会话自己的覆盖出现在自己最后一个 `request/header` 之后,则归因于用户;如果不存在自己的覆盖且该变化与继承的会话头基线相符,则归因于发起委派的会话;否则归因于操作方/配置。`ctx.approval.overrideOf(session)`(即纯函数导出 `approvalOverrideOf`,也供权限 preset 消费)解析会话的覆盖链,绝不包含配置默认值:当存在继承的 `approvalPolicy` 会话头基线时(即委派子 agent),先折叠会话自己在 `SessionHeader.seedLength` 之后的切换,否则取该基线,读取时按封闭词汇校验;没有基线时(顶层会话或通用的 `SessionStore.fork` 子会话),折叠覆盖完整日志,因此种子携带的 `'never'` 得以存续;进程内 subagent 驱动器在委派时捕获该值,并写入每个子 agent 创建时的会话头,使 `'never'` 父级无法造出会弹出提示的子 agent,且不存在任何第一轮次的时序窗口(参见[设计原理](../../../.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md))。 工具流水线通过此 seam 路由 `ask` 决定,并在该 seam 缺失时以拒绝方式关闭;沙箱 bash 工具也会将它用于升权重试。ACP 自动化桥接层根据客户端的机器策略,回答其自有 agent 的调用。审计事件仍只写入日志,因此模型只会看到发起请求的消费方所返回的结果。详见[审批 seam Agent Note(agent 决策记录)](../../../.agents/notes/implemented/feature/2026-07-06-approval-seam.md)和[沙箱 Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)。 diff --git a/packages/ui/user-approval/src/index.ts b/packages/ui/user-approval/src/index.ts index 3d4ed57416..fb5503f3be 100644 --- a/packages/ui/user-approval/src/index.ts +++ b/packages/ui/user-approval/src/index.ts @@ -59,10 +59,11 @@ declare module '@deepseek-ai/dsh-session' { /** * The session's approval policy was switched — log-only, durable, * replayable, never in the model transcript (the model learns the policy - * from the prompt section and the narrator's notices). The LAST such - * event is the session's override ({@link effectiveApprovalPolicy}); - * who asked for it is derivable from position (an event after the log's - * last `request/header` was a runtime switch by the user). + * from the prompt section and the narrator's notices). The last such OWN + * (post-seed) event is the session's override + * ({@link approvalOverrideOf}); who asked for it is derivable from + * position (an own event after the log's last own `request/header` was a + * runtime switch by the user). */ 'approval/policy': { policy: ApprovalPolicy } } @@ -123,10 +124,12 @@ function toldApprovalPolicy(system: string | undefined): ApprovalPolicy | undefi } /** - * The session's approval-policy override: the last `approval/policy` event in - * the log, or undefined when the session never switched (callers apply the - * plugin's configured default). The pure fold — resume needs no catch-up - * machinery because replaying the log IS the state. + * The pure fold of a slice of `approval/policy` events: the last switch + * wins, or undefined without one. The building block + * {@link approvalOverrideOf} composes with the seed boundary and the header + * baseline — consumers resolving a SESSION's policy go through that chain, + * not this raw fold. Resume needs no catch-up machinery because replaying + * the log IS the state. * @param events - session events in log order (other event types are skipped). * @returns the policy of the last switch event, or undefined without one. */ @@ -359,9 +362,9 @@ export class ApprovalService extends Service { } /** - * The session's effective policy: its own `approval/policy` fold, else the - * configured default (the schema already defaulted an omitted policy to - * `'ask'`; the `??` only narrows the optional-input TYPE). + * The session's effective policy: its override chain ({@link overrideOf}), + * else the configured default (the schema already defaulted an omitted + * policy to `'ask'`; the `??` only narrows the optional-input TYPE). * @param session - the exact accepted session whose policy applies. * @returns the policy every ask for this session resolves under right now. */