From 7407c26bc1690cc084d72ccff68a967370bda943 Mon Sep 17 00:00:00 2001 From: NI0317 Date: Thu, 30 Jul 2026 15:48:05 +0800 Subject: [PATCH] feat(sandbox-policy): expose current file policy --- .../feature/2026-07-06-sandbox.i18n.yaml | 4 +- .../implemented/feature/2026-07-06-sandbox.md | 17 ++- .../feature/2026-07-06-sandbox.zh.md | 17 ++- ...0-current-sandbox-policy-context.i18n.yaml | 6 + ...26-07-30-current-sandbox-policy-context.md | 37 +++++ ...07-30-current-sandbox-policy-context.zh.md | 37 +++++ .../tests/permission-policy-context.e2e.ts | 126 ++++++++++++++++++ .../permission-policy-context/session.jsonl | 65 +++++++++ apps/web/tsconfig.json | 3 +- docs/config-catalog.md | 2 +- docs/cordis-catalog/services.md | 4 +- .../session-sandbox-root.cordis.snapshot.yml | 2 +- .../system-prompt.expected.md | 2 + .../code-mode-turn/system-prompt.expected.md | 2 + .../system-prompt.expected.md | 2 + .../lsp-definition/system-prompt.expected.md | 2 + .../pty-tools/system-prompt.expected.md | 2 + .../system-prompt.expected.md | 2 + .../text-turn/system-prompt.expected.md | 2 + .../web-fetch/system-prompt.expected.md | 2 + .../system-prompt.expected.md | 2 + .../sandbox/sandbox-policy/README.i18n.yaml | 4 +- packages/sandbox/sandbox-policy/README.md | 37 ++++- packages/sandbox/sandbox-policy/README.zh.md | 37 ++++- packages/sandbox/sandbox-policy/package.json | 6 +- packages/sandbox/sandbox-policy/src/index.ts | 41 +++++- .../sandbox-policy/tests/policy.spec.ts | 72 ++++++++++ packages/sandbox/sandbox-policy/tsconfig.json | 6 + pnpm-lock.yaml | 6 + .../verify-package-readme-model-experience.ts | 1 - tsconfig.host.json | 1 + 31 files changed, 506 insertions(+), 43 deletions(-) create mode 100644 .agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml create mode 100644 .agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md create mode 100644 .agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md create mode 100644 apps/web/tests/permission-policy-context.e2e.ts create mode 100644 apps/web/tests/snapshots/permission-policy-context/session.jsonl diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml index 9311c6cac7..8887be8f7a 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml @@ -2,5 +2,5 @@ # 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 .agents/notes/implemented/feature/2026-07-06-sandbox.md -2026-07-06-sandbox.md: 42b78ad8341dd52c4dd146a2207a5ae909d28f1e -2026-07-06-sandbox.zh.md: dfa3349e4d74d6f2c4944414c25fe3726d4a9b5a +2026-07-06-sandbox.md: ff79d2e1e4dc1501502cfeb2518ddf1065750f1a +2026-07-06-sandbox.zh.md: 1d2ab5ad556f4e1c72a124183b91b951b0ded6dd diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index 42b78ad834..ff79d2e1e4 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -40,7 +40,7 @@ The swap is invisible to every consumer of `ctx.bash`: the bash tools, hook comm Misconfiguration fails loud: `mode` outside the closed vocabulary is rejected at plugin load, and a host with no usable backend throws the structured `SANDBOX_UNAVAILABLE` — at `confine()` before the command ever spawns — rather than degrading to unconfined execution. `runnerCommand` on `dsh-sandbox-local` is the operator's explicit assertion of a bwrap-compatible runner (chain and probes skipped); it doubles as the deterministic fake-runner seam for keyless tests. -Denied file effects return a `[sandbox: file access denied under mode]` marker and instructions not to work around the denial. A confining executor adds paired `sandbox_permissions` and `justification` fields for one approved retry that must be strictly wider than the session's effective mode. A grant widens only that retry; rejection executes nothing, returns `the user rejected escalating this command to ""`, and permits no re-ask. The prompt does not announce sandbox mode, avoiding preemptive refusal. When `dsh-permission` is composed with a UI adapter, one preset selects both knob values; unmatched values fold to `custom`. The [ACP automation composition](../../../../examples/acp-agent/README.md) does not mount that UI service and selects its deployment mode explicitly. +Denied file effects return a `[sandbox: file access denied under mode]` marker and instructions not to work around the denial. A confining executor adds paired `sandbox_permissions` and `justification` fields for one approved retry that must be strictly wider than the session's effective mode. A grant widens only that retry; rejection executes nothing, returns `the user rejected escalating this command to ""`, and permits no re-ask. The owner-derived prompt section states the current file policy without replacing those enforcement boundaries. When `dsh-permission` is composed with a UI adapter, one preset selects both knob values; unmatched values fold to `custom`. The [ACP automation composition](../../../../examples/acp-agent/README.md) does not mount that UI service and selects its deployment mode explicitly. ### Design detail @@ -72,7 +72,7 @@ Backend profiles share the mode contract but differ in necessary host grants. La `dsh-bash-sandbox` extends `LocalBashExecutor` and hands `ctx.sandbox` the exact `['bash', '-c', command]` argv it is about to spawn. A denial is an orthogonal result fact, conservatively classified from the active runner's stderr dialect. A runner failure outranks denial: foreground execution throws `SANDBOX_UNAVAILABLE`; a settled `BashProcess` stamps `sandbox.runnerFailed`, and the bash producer renders it through generic `task_output`. -The model's view is result facts only: the static tool description explains the denial marker (`[sandbox: file access denied under mode]`), encourages attempting commands that may be denied, and forbids retrying around a denial; when the escalation fields are advertised, a denied result additionally carries the escalation hint itself, so the sanctioned same-turn retry is prompted at the decision point rather than depending on the model recalling the description (§ Escalation). No prompt section states the sandbox mode (§ Per-session modes). +The model sees the current effective file policy in the owner-derived `sandbox:policy` request section, while the static tool description explains the denial marker (`[sandbox: file access denied under mode]`), encourages attempting commands that may be denied, and forbids retrying around a denial; when the escalation fields are advertised, a denied result additionally carries the escalation hint itself, so the sanctioned same-turn retry is prompted at the decision point rather than depending on the model recalling the description (§ Escalation). [The current-policy decision](2026-07-30-current-sandbox-policy-context.md) owns the section's rationale and boundaries. #### Escalation: one approved wider retry after a denial @@ -105,7 +105,7 @@ interface SessionEventMap { Each owner exports the same three-piece kit: the event declaration, a pure fold (`effectiveSandboxMode(events)` / `effectiveApprovalPolicy(events)` — a `findLast`, typed to the domain's closed union), and THE write path (`setSandboxMode(session, mode)` / `setApprovalPolicy(session, policy)` — a switch IS its event; nothing mutates state out of band). No shared owner service, no generic facts map, no registry: a third knob copies the ~40-line pattern into its own package. Execution follows the fold on both sides — the bash tool's per-call stamp reads it as the middle rung of the § Escalation precedence chain, and the approval seam's `'never'` gate is [the approval Agent Note](2026-07-06-approval-seam.md)'s side of the same pattern. -Sandbox mode is not narrated in the prompt; denial results report the mode when it matters, avoiding preemptive refusal based on a standing label. Approval policy is different: only `'never'` is stated because automatic rejection otherwise looks like a user decision. Policy-change notices are coalesced and delivered by the next pre-step, with log-derived fallback after restart. The notice source is inferred from event position: a knob event after the last request header is user-driven; unlogged drift is operator or config driven. +Sandbox policy is rendered from the current mode and workspace root before every request; the existing `request/header` records the exact section, so no switch narrator or last-told state is needed. Approval policy stays separate: only `'never'` is stated because automatic rejection otherwise looks like a user decision, and its owner retains its own switch narration. **The optional UI surface** is `PermissionService`: a deployment-defined preset table whose entries bundle one sandbox mode with one approval policy. The shipped `workspace-write` and `danger-full-access` presets write through to both domain setters; a knob combination outside the table is reported as `custom`. UI adapters may expose that table as a selector. The automation-only ACP transport advertises no configuration selector and mounts no permission-preset service. @@ -120,7 +120,7 @@ fs/web/todo execute in-process, so their sandbox semantics are policy at their s - **Unit:** pin platform selection and profiles, fail-closed runner classification, per-call mode/root resolution, per-process facts, escalation validation and outcomes, permission preset folding and write-through, and narrator coalescing. - **Keyless real-runner:** exercise bwrap, Landlock, and Seatbelt against real filesystem effects at provider and bash-consumer layers; one real Cordis context concurrently drives two project sessions through shipped bash and fs tools, proving own-root success and sibling-root denial. Packed-install coverage proves the registry launcher remains executable. CI rejects a silent all-skip. - **With-key:** start the real ACP composition in read-only mode, let a model-driven bash write hit the runner's denial marker, then drive the bridge answerer and disk effect through granted and rejected workspace-write retries; unavailable credentials or runners self-skip. -- **Snapshot:** pin prompt deltas and notices plus both scripted approval branches. A real ACP example scenario places its session under the user home while the deployment fallback points at `/tmp`, then pins a successful deployment-selected workspace-write mutation; this distinguishes session-root resolution from the process fallback without depending on runner-specific denial text. Other snapshots start unconfined so unrelated fixtures remain platform-independent. +- **Snapshot:** pin the current policy section and both scripted approval branches. A real ACP example scenario places its session under the user home while the deployment fallback points at `/tmp`, then pins both the workspace-write request header and a successful deployment-selected mutation; this distinguishes session-root resolution from the process fallback without depending on runner-specific denial text. Other snapshots start unconfined so unrelated fixtures remain platform-independent. ## Deferred phases @@ -150,7 +150,7 @@ Each phase gets its full design when picked up, validated against the code at th - **Hard-match the retry to a prior denial** — rejected: command-string identity is fragile (quoting, `workdir`, env prefixes, a pipeline retried as its failing stage) — false-rejects honest retries or is trivially satisfied; the real boundary is the human seeing command + justification. Revisit only if `allow_always` grant storage ever needs machine-checkable scopes. - **A generic `env/state` facts map with an owner service** — rejected: approval and sandbox compose independently, so neither's state may drag in a third package; single-key folds are one `findLast` each, dissolving the owner service; no invariant spans the knobs, so atomic multi-key patches bought nothing. - **Narrate via `agent/user-message` + a bus event** — rejected: it presupposes a turn-entry seam that does not exist (the real seam is `agent/prompt-submit`), and pre-step's position serves both the coalesced turn-entry notice and the mid-turn immediacy bound with one listener. -- **A standing prompt statement of the sandbox mode (+ a switch narrator)** — shipped first, then removed on live evidence: with `Bash commands run under the "read-only" file sandbox.` in every request, the model refused to ATTEMPT denied-then-escalatable work (five of twelve turns in the first manual session ended with zero tool calls), turning the sandbox into a soft lockout. The denial marker names the mode at the moment it matters and the escalation fields carry the recovery; the approval knob keeps its statement because an auto-rejection is behaviorally indistinguishable from a human "no". +- **A bash-only mode label plus a switch narrator** — rejected: `Bash commands run under the "read-only" file sandbox.` caused preemptive refusal while leaving the filesystem-tool consequence and workspace scope ambiguous. The current owner-derived section is a different contract: later Web evidence showed that total absence caused false capability claims before a first tool call, and cross-family enforcement now supplies one complete file-effect policy. [The current-policy decision](2026-07-30-current-sandbox-policy-context.md) records why concise current state supersedes the absence choice without duplicating tool guidance. - **Track "last told" with its own bookkeeping events** — rejected: the `request/header` fold already records the exact prompt the model saw; parsing the closed candidate sentences back replaces a second bookkeeping stream — events are needed only where they ARE the store. - **Independent sandbox and approval selectors** — rejected: one deployment-defined permission preset keeps the two policy knobs coherent for UI clients that expose runtime switching. @@ -160,7 +160,7 @@ What shipped pins — the tiers in Testing hold each: - A denied command retried with `sandbox_permissions` + `justification` prompts the user through the composed answerer chain; a grant runs THAT call under the wider mode (result facts say so) while every other call keeps its own effective mode; every non-grant outcome produces its distinct error text and executes nothing. - The escalation fields exist exactly when the mounted executor confines; a request that is not strictly wider than the call's effective mode fails closed with its own text and prompts no one; a deployment with no ApprovalService fails escalating calls closed and leaves plain calls untouched. -- The system prompt never states the sandbox mode (an approval `'never'` policy is the one stated knob), and the whole exchange — headers, knob events, notices, approvals, results — reconstructs from the session log alone, with no event types beyond the two knob events. +- The system prompt states sandbox policy and approval policy in separate owner-derived sections; the whole exchange — headers, knob events, approval notices, approvals, and results — reconstructs from the session log alone, with no event types beyond the two knob events. - One preset selection records only changed knob values, while a no-op selection records nothing; an approval-policy switch is narrated in at most one coalesced notice, and a committed sandbox switch is honored by the next call's stamp. - A resumed session's overrides apply with no catch-up state; a default changed while the process was down is narrated before the session's first new request, attributed to the operator. - Two concurrent sessions never see each other's state or notices. @@ -179,8 +179,7 @@ Costs and accepted limits: - **The advertised target set is static while the effective mode is per-session** (schemas are registry-global) — a session already at the widest mode is still offered the fields. Harmless by construction: the strict-wider check at execution, not the enum, is the safety boundary — a non-widening request fails with its own text and never prompts anyone. - **A granted escalation is not a working sandbox.** An unavailable backend still fails closed even for a granted escalation to a confining mode — at `confine()` when the platform has no chain or every probe fails, at execution when an unprobed sole runner refuses (classified as a sandbox failure, not a command failure) — while a granted `danger-full-access` run never touches the provider at all: there the grant, not the probe, is the authority. - **The approval narrator's restart baseline parses prompt prose.** The closed candidate sentence is owned by the writing module itself, so a wording change is a coordinated writer+parser edit in one file; a session whose headers predate the section silently adopts the current policy without a notice. -- **The approval section is still a dynamic prompt surface** (a `'never'` switch breaks provider prompt-prefix caching for that session). Accepted: policy switches are rare, and a model acting on a stale `'never'` is worse. The sandbox knob no longer touches the prompt at all. -- **The model may hold a stale belief about the sandbox mode** (nothing announces a switch). Accepted deliberately: the next attempt's marker or success corrects it, and the observed failure mode of announcing — preemptive refusal — is worse than one wasted retry. +- **The approval and sandbox sections are dynamic prompt surfaces.** A policy switch breaks provider prompt-prefix caching for that session; unchanged state remains byte-stable, and a model acting on stale authority is worse than the bounded invalidation. ## FAQ @@ -191,7 +190,7 @@ Costs and accepted limits: - **Does the sandbox restrict network or process visibility?** No — `SandboxMode` claims FILE effects only; the bwrap profile deliberately does not unshare pid, and no backend claims network. Whether network restriction becomes its own knob is left open in § The seam. - **Which tools actually run confined?** OS subprocesses through `ctx.bash` — the bash tools, and hook commands transitively — plus the filesystem tools (`read`/`write`/`edit`) through the sandboxed `ctx.fs` provider (the [cross-family fs sandbox RFC](2026-07-14-cross-family-fs-sandbox.md)): bash confines via the OS runner, fs via an in-process path fence, both keying off the same `ctx.sandboxPolicy` mode. web/todo stay in-process and unfenced (web's only effect is network, outside the file-effect mode vocabulary). - **Does a granted escalation persist?** No. The grant is consumed by the exact foreground or background call that asked; every neighboring call keeps its own effective mode. A later background denial surfaces through `task_output` and may ground a new exact-command retry. -- **When does a runtime mode switch take effect?** Once its session event commits, the very next capability resolution folds and stamps the new mode. The model is not told a standing mode; its next command simply behaves under the new policy, and any denial names that policy at the point of use. +- **When does a runtime mode switch take effect?** Once its session event commits, the next request section and the next capability resolution fold the new mode. The request header records what the model was told, and any later denial names the same policy at the point of use. - **What survives a restart — and what if the operator changed the config default while the process was down?** Overrides replay from the session log (`effective = fold ?? config`), so a resumed session keeps its modes with zero catch-up machinery; a default that drifted offline changes behavior the same way a switch does (the approval policy, being stated, is additionally narrated with operator/config attribution). - **What does `enforcement: 'partial'` on a result mean?** The selected backend enforces the subset its kernel ABI governs — e.g. Landlock before ABI v3 does not govern path truncate — and says so structurally instead of refusing the host; the probe's report line distinguishes the cases. The bwrap and Seatbelt profiles govern every promised file effect by construction, so they always report `full`. diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md index dfa3349e4d..1d2ab5ad55 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md @@ -40,7 +40,7 @@ harness 是一个 SDK,因此约束必须是开发者可组合的能力:是 配置错误大声失败:`mode` 不在封闭词汇中时在插件加载时被拒绝;主机上没有可用后端时在 `confine()` 阶段(命令 spawn 之前)抛出结构化的 `SANDBOX_UNAVAILABLE`,而非降级为无约束执行。`dsh-sandbox-local` 上的 `runnerCommand` 是运维人员对一个 bwrap 兼容 runner 的显式断言(跳过链和探测);它同时充当 keyless 测试的确定性 fake-runner seam。 -被拒绝的文件操作返回 `[sandbox: file access denied under mode]` 标记,并附带不要绕过拒绝的指令。约束执行器添加配对的 `sandbox_permissions` 和 `justification` 字段,用于一次经批准的重试,该重试必须严格宽于会话的有效模式。授权仅放宽该次重试;拒绝则不执行任何内容,返回 `the user rejected escalating this command to ""`,且不允许再次请求。提示词不声明沙箱模式,以避免基于常驻标签的预防性拒绝。当 `dsh-permission` 与某个 UI 适配器一起组合时,一个 preset 同时选定两个旋钮值;不匹配的组合折叠为 `custom`。[ACP 自动化组合](../../../../examples/acp-agent/README.md)不挂载该 UI 服务,而是显式选定其部署模式。 +被拒绝的文件操作返回 `[sandbox: file access denied under mode]` 标记,并附带不要绕过拒绝的指令。约束执行器添加配对的 `sandbox_permissions` 和 `justification` 字段,用于一次经批准的重试,该重试必须严格宽于会话的有效模式。授权仅放宽该次重试;拒绝则不执行任何内容,返回 `the user rejected escalating this command to ""`,且不允许再次请求。由归属方派生的提示词段落会说明当前文件策略,但不会取代这些强制执行边界。当 `dsh-permission` 与某个 UI 适配器一起组合时,一个 preset 同时选定两个旋钮值;不匹配的组合折叠为 `custom`。[ACP 自动化组合](../../../../examples/acp-agent/README.md)不挂载该 UI 服务,而是显式选定其部署模式。 ### 设计细节 @@ -72,7 +72,7 @@ Landlock launcher 源码和包工作区位于 `native/landlock-run`,与 harnes `dsh-bash-sandbox` 扩展 `LocalBashExecutor`,并把即将 spawn 的确切 `['bash', '-c', command]` argv 交给 `ctx.sandbox`。拒绝是与其他结果正交的事实,依据当前 runner 的 stderr 方言保守分类。Runner 失败优先于拒绝:前台执行抛出 `SANDBOX_UNAVAILABLE`;结算后的 `BashProcess` 会盖章 `sandbox.runnerFailed`,bash 生产者再通过通用 `task_output` 渲染它。 -模型看到的仅是结果事实:静态工具描述解释拒绝标记(`[sandbox: file access denied under mode]`),鼓励尝试可能被拒绝的命令,并禁止绕过拒绝重试;当升级字段被公布时,被拒绝的结果还额外携带升级提示本身,使被认可的同轮次重试在决策点被提示,而非依赖模型回忆描述(§ 升级机制)。没有提示词段落声明沙箱模式(§ 按会话模式)。 +模型会在归属方派生的 `sandbox:policy` 请求段落中看到当前有效的文件策略;静态工具描述则解释拒绝标记(`[sandbox: file access denied under mode]`),鼓励尝试可能被拒绝的命令,并禁止绕过拒绝重试。当升级字段被公布时,被拒绝的结果还会携带升级提示本身,使被认可的同轮次重试在决策点获得提示,而非依赖模型回忆描述(§ 升级机制)。[当前策略决策](2026-07-30-current-sandbox-policy-context.md)负责该段落的理由与边界。 #### 升级机制:拒绝后一次经批准的更宽重试 @@ -105,7 +105,7 @@ interface SessionEventMap { 每个拥有者导出相同的三件套:事件声明、纯 fold(`effectiveSandboxMode(events)` / `effectiveApprovalPolicy(events)`——一个 `findLast`,类型化到领域的封闭联合),以及唯一的写入路径(`setSandboxMode(session, mode)` / `setApprovalPolicy(session, policy)`——切换即其事件;没有任何东西在带外修改状态)。无共享拥有者服务、无通用 facts map、无注册表:第三个旋钮只需将约 40 行模式复制到自己的包中。执行在两侧都遵循 fold——bash 工具的按调用盖章将其作为 § 升级机制优先级链的中间层读取,approval seam 的 `'never'` 门控是[批准 Agent Note](2026-07-06-approval-seam.md) 同一模式的另一侧。 -沙箱模式不在提示词中叙述;拒绝结果在需要时报告模式,避免基于常驻标签的预防性拒绝。批准策略不同:只有 `'never'` 被声明,因为自动拒绝在行为上与用户的「不」无法区分。策略变更通知被合并,由下一个步骤前检查点递送,重启后有基于日志的回退。通知来源从事件位置推断:最后一个 request header 之后的旋钮事件是用户驱动的;未记录的漂移是运维人员或配置驱动的。 +每次请求前都会根据当前模式与工作区根目录渲染沙箱策略;现有 `request/header` 会记录确切段落,因此无需切换叙述器或「上次告知」状态。批准策略保持独立:只有 `'never'` 被声明,因为自动拒绝在行为上与用户的「不」无法区分,其归属方继续保留自己的切换叙述。 **可选的 UI 界面**是 `PermissionService`:一张部署定义的 preset 表,每个条目捆绑一个沙箱模式与一个批准策略。随附的 `workspace-write` 和 `danger-full-access` preset 写入两个领域 setter;preset 表之外的旋钮组合报告为 `custom`。UI 适配器可以把该表暴露为选择器。仅面向自动化的 ACP 传输层不公布任何配置选择器,也不挂载权限 preset 服务。 @@ -120,7 +120,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **单元测试:** 固定平台选择和 profile、失败关闭的 runner 分类、按调用的模式/根目录解析、按进程事实、升级验证和结果、权限 preset fold 和写入透传、以及叙述器合并。 - **Keyless 真实 runner:** 在提供方和 bash 消费方层面对 bwrap、Landlock 和 Seatbelt 执行真实文件系统效果测试;一个真实 Cordis 上下文通过已交付的 bash 和 fs 工具并发驱动两个项目会话,证明在自身根目录写入成功、在兄弟根目录写入被拒绝。Packed-install 覆盖率证明注册表 launcher 保持可执行。CI 拒绝静默全跳过。 - **With-key:** 以只读模式启动真实 ACP 组合,让模型驱动的 bash 写入命中 runner 的拒绝标记,再通过已授权与被拒绝的 workspace-write 重试驱动 bridge 应答器和磁盘效果;不可用的凭证或 runner 自动跳过。 -- **快照:** 固定提示词 delta 和通知,以及两个脚本化的 approval 分支。一个真实 ACP 示例场景把会话放在用户主目录下,同时让部署后备根目录指向 `/tmp`,然后固定一次成功的、由部署选定的 workspace-write 变更;这能区分会话根目录解析与进程后备值,而不依赖 runner 特定的拒绝文本。其他快照以无约束启动,使无关 fixture(测试前置数据)保持平台无关。 +- **快照:** 固定当前策略段落和两个脚本化的 approval 分支。一个真实 ACP 示例场景把会话放在用户主目录下,同时让部署后备根目录指向 `/tmp`,然后固定 workspace-write 请求 header 与一次成功的、由部署选定的变更;这能区分会话根目录解析与进程后备值,而不依赖 runner 特定的拒绝文本。其他快照以无约束启动,使无关 fixture(测试前置数据)保持平台无关。 ## 延迟阶段 @@ -150,7 +150,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **将重试硬匹配到先前的拒绝**:否决。命令字符串同一性脆弱(引号、`workdir`、env 前缀、作为失败阶段重试的管道)——要么误拒诚实的重试,要么被轻易满足;真正的边界是人看到命令 + 理由。仅在 `allow_always` 授权存储需要机器可检查的范围时才重新考虑。 - **通用 `env/state` facts map 加拥有者服务**:否决。approval 和沙箱独立组合,因此任何一方的状态都不应拖入第三个包;单键 fold 各自是一个 `findLast`,拥有者服务自然消解;没有跨旋钮的不变式,因此原子多键补丁无收益。 - **通过 `agent/user-message` + 总线事件叙述**:否决。它预设了一个不存在的轮次入口 seam(真正的 seam 是 `agent/prompt-submit`),而步骤前检查点的位置使一个监听器能够同时服务合并的轮次入口通知和轮中即时性约束。 -- **提示词中常驻声明沙箱模式(+ 切换叙述器)**:先交付后移除,基于实际证据:当每个请求中都有 `Bash commands run under the "read-only" file sandbox.` 时,模型拒绝尝试被拒绝后可升级的工作(首次手动会话中十二个轮次有五个以零工具调用结束),将沙箱变成了软锁定。拒绝标记在需要时命名模式,升级字段承载恢复路径;批准旋钮保留其声明,因为自动拒绝在行为上与人的「不」无法区分。 +- **仅限 bash 的模式标签加切换叙述器**:否决。`Bash commands run under the "read-only" file sandbox.` 会引发预防性拒绝,同时没有明确文件系统工具的后果与工作区范围。当前由归属方派生的段落采用不同契约:后续 Web 证据表明,完全缺失策略会导致模型在首次工具调用前错误声称自身能力,而跨工具族强制现在能够提供一项完整的文件操作策略。[当前策略决策](2026-07-30-current-sandbox-policy-context.md)记录了为何用简洁的当前状态取代缺失策略的选择,同时不重复工具引导。 - **用专门的簿记事件追踪「上次告知」**:否决。`request/header` fold 已记录模型看到的确切提示词;将封闭的候选句子解析回来替代了第二条簿记流——事件仅在它们本身即为存储时才需要。 - **相互独立的沙箱与批准选择器**:否决。一个部署定义的权限 preset 让两个策略旋钮对暴露运行时切换的 UI 客户端保持一致。 @@ -160,7 +160,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - 被拒绝的命令以 `sandbox_permissions` + `justification` 重试时,通过组合的应答器链提示用户;授权使该次调用在更宽模式下运行(结果事实如此报告),而其他所有调用保持各自的有效模式;每种非授权结果产生各自不同的错误文本且不执行任何内容。 - 升级字段恰好在已挂载的执行器约束时存在;不严格宽于调用有效模式的请求以自身文本失败关闭且不提示任何人;没有 ApprovalService 的部署对升级调用失败关闭,对普通调用不影响。 -- 系统提示词从不声明沙箱模式(批准 `'never'` 策略是唯一被声明的旋钮),且整个交互——header、旋钮事件、通知、批准、结果——仅从会话日志即可重建,除两个旋钮事件外无额外事件类型。 +- 系统提示词在由各自归属方派生的独立段落中分别声明沙箱策略与批准策略;整个交互——header、旋钮事件、批准通知、批准与结果——仅从会话日志即可重建,除两个旋钮事件外无额外事件类型。 - 一次 preset 选择只记录发生变化的旋钮值,而无操作的选择不记录任何内容;批准策略切换最多以一条合并通知叙述,已提交的沙箱切换由下一次调用的盖章兑现。 - 恢复的会话的覆盖直接生效,无需追赶状态;进程停止期间变更的默认值在会话的首个新请求前被叙述,归因于运维人员。 - 两个并发会话永远看不到彼此的状态或通知。 @@ -179,8 +179,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **公布的目标集是静态的,而有效模式是按会话的**(schema 是注册表全局的)——已处于最宽模式的会话仍被提供这些字段。构造上无害:执行时的严格放宽检查(而非枚举)是安全边界——非放宽请求以自身文本失败且不提示任何人。 - **授权的升级不等于可工作的沙箱。** 不可用的后端即使对授权升级到约束模式也仍然失败关闭——在平台没有链或所有探测失败时于 `confine()` 阶段,在未探测的唯一 runner 拒绝时于执行阶段(归类为沙箱失败而非命令失败)——而授权的 `danger-full-access` 运行根本不触及提供方:此时授权(而非探测)是权威。 - **批准叙述器的重启基线解析提示词文本。** 封闭的候选句子由写入模块本身拥有,因此措辞变更是同一文件中写入器+解析器的协调编辑;header 早于该段落的会话静默采用当前策略而不发通知。 -- **批准段落仍是动态提示词表面**(`'never'` 切换会破坏该会话的提供方提示词前缀缓存)。已接受:策略切换罕见,且模型基于过时的 `'never'` 行动更糟。沙箱旋钮不再触及提示词。 -- **模型可能持有关于沙箱模式的过时信念**(没有任何东西宣布切换)。有意接受:下一次尝试的标记或成功会纠正它,而宣布的观察到的失败模式——预防性拒绝——比一次浪费的重试更糟。 +- **批准段落与沙箱段落都是动态提示词表面。** 策略切换会破坏该会话的提供方提示词前缀缓存;状态不变时仍保持字节稳定,且模型基于过时权限行动的风险高于这种有限的缓存失效。 ## FAQ @@ -191,7 +190,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **沙箱限制网络或进程可见性吗?** 不——`SandboxMode` 仅声称文件操作;bwrap profile 刻意不 unshare pid,没有后端声称网络。网络限制是否成为自己的旋钮留在 § seam 中开放。 - **哪些工具实际在约束下运行?** 通过 `ctx.bash` 的 OS 子进程——bash 工具及传递性的钩子命令——再加上通过沙箱化 `ctx.fs` 提供方运行的文件系统工具(`read`/`write`/`edit`,见[跨工具族 fs 沙箱 Agent Note](2026-07-14-cross-family-fs-sandbox.md)):bash 通过 OS runner 约束,fs 通过进程内路径围栏约束,二者都以同一个 `ctx.sandboxPolicy` 模式为键。web/todo 仍在进程内且不受限制(web 的唯一效果是网络,不在文件效果模式词汇内)。 - **授权的升级会持久化吗?** 不会。授权由发起请求的确切前台或后台调用消费;每个相邻调用保留自己的有效模式。后续的后台拒绝通过 `task_output` 呈现,并且可以作为一次新的精确命令重试的依据。 -- **运行时模式切换何时生效?** 一旦其会话事件提交,紧接着的下一次能力解析就会折叠并盖章新模式。模型不被告知常驻模式;其下一个命令直接在新策略下运行,任何拒绝都会在使用点命名该策略。 +- **运行时模式切换何时生效?** 一旦其会话事件提交,下一个请求段落与下一次能力解析都会折叠新模式。请求 header 会记录模型收到的内容,之后的任何拒绝都会在使用点命名同一策略。 - **重启后什么存活——如果运维人员在进程停止期间改了配置默认值呢?** 覆盖从会话日志回放(`effective = fold ?? config`),因此恢复的会话以零追赶机制保持其模式;离线漂移的默认值以与切换相同的方式改变行为(批准策略因被声明,还额外以运维人员/配置归因叙述)。 - **结果上的 `enforcement: 'partial'` 是什么意思?** 所选后端强制其内核 ABI 管控的子集——例如 ABI v3 之前的 Landlock 不管控路径 truncate——并以结构化方式如此声明而非拒绝主机;探测的报告行区分各种情况。bwrap 和 Seatbelt profile 构造上管控所有承诺的文件操作,因此始终报告 `full`。 diff --git a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml new file mode 100644 index 0000000000..a7bbe905ff --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml @@ -0,0 +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 .agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md +2026-07-30-current-sandbox-policy-context.md: 2854f527c62dedfcb2fa86ab684d162e892db35c +2026-07-30-current-sandbox-policy-context.zh.md: 0560251afc450136fd1c4a2e28aba3f1f16f2937 diff --git a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md new file mode 100644 index 0000000000..2854f527c6 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md @@ -0,0 +1,37 @@ +# Agent Note: Current sandbox policy context + +Status: implemented + +English | [中文](2026-07-30-current-sandbox-policy-context.zh.md) + +## Problem + +The sandbox policy already enforced and logged each session's file-effect mode, but a fresh model request did not contain that state. In a Web session under `read-only`, write and edit schemas remained visible, so the model claimed it could write and learned otherwise only after a denied call. After `/permission danger-full-access`, the next request carried the approval-policy change but still omitted the sandbox mode. Denial results were therefore the first model-visible policy source even when the user asked about capability before any operation. + +## Decision + +`dsh-sandbox-policy`, the owner of mode and workspace-root resolution, registers one `sandbox:policy` system-prompt section. Every agent request resolves the active session directly through `ctx.sandboxPolicy.resolve({ session })`; there is no denial-history scan, delta narrator, or in-memory “last told” state. + +The section states the current file-effect mode and only its owned consequences. `read-only` says ordinary writes, edits, and file-mutating shell effects are denied while required sinks may remain writable. `workspace-write` lists the canonical writable roots returned by the shared `writableRoots()` policy: the immutable session workspace root, `/tmp`, and the platform temporary directory, deduplicated after canonicalization. `danger-full-access` says the DSH file sandbox adds no file restriction. Every form says host permissions or backend availability may restrict more and that network and process access are outside this policy. + +The provider runs during normal request assembly, after a `/permission` switch has committed its existing `sandbox/mode` event and before `request/header` is logged. The rendered system text is therefore the durable reconstruction of the exact model-visible fact. Repeated assemblies over unchanged session state produce identical bytes; resume and replay fold the same durable mode event and immutable `SessionHeader.cwd` without catch-up state. + +Ownership stays narrow. Approval policy remains the separate `approval:policy` section, plan mode remains `plan:policy`, and tool plugins continue to own schemas and operation guidance. The prompt states policy; bash and filesystem backends remain the enforcement boundaries. + +## Alternatives considered + +**Narrate only mode changes.** Rejected because it leaves a fresh session uninformed and makes the first denied operation the policy-discovery mechanism. It also requires a baseline definition that is unnecessary when current state can be rendered directly. + +**Scan denial history or remember the last narrated mode.** Rejected because denial events describe attempted operations, not authoritative current state, while process-local bookkeeping does not survive resume. The owner can fold the durable policy directly on every request. + +**A generic runtime-facts registry.** Rejected because the existing system-prompt registry already evaluates owner-provided sections with the live agent at request time. One policy owner has no cross-domain invariant that justifies another package or registry. + +**Repeat tool schemas or approval and plan guidance in the section.** Rejected because those surfaces already have owners and independent lifecycles. Duplicating them would create contradictory request prefixes and broaden invalidation. + +**Keep sandbox mode absent because a standing mode label once caused preemptive refusal.** Rejected by the later Web evidence and the completed cross-family policy. The earlier sentence named only a bash sandbox and did not explain the actual write/edit boundary, so it could conflict with visible tools and escalation guidance. The owner-derived section states the complete current file-effect consequence, canonical workspace scope, and explicit non-guarantees without duplicating tool instructions. This supersedes only the absence decision in the [sandbox Agent Note](2026-07-06-sandbox.md); its enforcement and escalation boundaries remain current. + +## Consequences + +A model can answer what file effects are currently possible before probing a tool, and the next request after `/permission` reflects the committed mode. This adds a small dynamic system section and intentionally invalidates the request prefix when policy changes; unchanged state remains cache-stable. The statement is guidance, not an enforcement guard: runtime safety still comes from `dsh-bash-sandbox` and `dsh-fs-sandbox` consuming the same resolved policy. + +Focused sandbox-policy tests pin all three texts, canonical roots, switch timing, byte stability, and replay. A keyless assembled ACP snapshot pins the request header through the real Loader composition, while the Web browser scenario drives `/permission` across all modes, inspects each exact `request/header`, and checks the model completes without a probing tool call; record mode exercises the real provider. diff --git a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md new file mode 100644 index 0000000000..0560251afc --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md @@ -0,0 +1,37 @@ +# Agent Note: 当前沙箱策略上下文 + +Status: implemented + +[English](2026-07-30-current-sandbox-policy-context.md) | 中文 + +## 问题 + +沙箱策略已经强制执行并记录每个会话的文件操作模式,但新的模型请求并不包含这一状态。在 `read-only` 下的 Web 会话中,write 与 edit schema 仍然可见,因此模型会声称自己能够写入,直到一次被拒绝的调用后才发现事实并非如此。执行 `/permission danger-full-access` 后,下一个请求带有批准策略变更,却仍省略沙箱模式。因此,即使用户在任何操作前询问能力,拒绝结果也是模型可见的首个策略来源。 + +## 决策 + +`dsh-sandbox-policy` 负责解析模式与工作区根目录,并注册一个 `sandbox:policy` 系统提示词段落。每次 agent(智能体)请求都通过 `ctx.sandboxPolicy.resolve({ session })` 直接解析当前会话;不存在拒绝历史扫描、差量叙述器或内存中的「上次告知」状态。 + +该段落说明当前文件操作模式,且只说明归其所有的后果。`read-only` 表明普通写入、编辑和会修改文件的 shell 操作会被拒绝,但必要的写入目标可能仍可写。`workspace-write` 会列出共享 `writableRoots()` 策略返回的规范化可写根目录:不可变的会话工作区根目录、`/tmp` 与平台临时目录,并在规范化后去重。`danger-full-access` 表明 DSH 文件沙箱不会额外施加文件限制。每种形式都说明主机权限或后端可用性可能施加更多限制,且网络和进程访问不属于该策略的管辖范围。 + +提供方在正常请求组装期间运行:此时 `/permission` 切换已经提交既有 `sandbox/mode` 事件,`request/header` 尚未记录。因此,渲染后的系统文本就是模型所见确切事实的持久化重建结果。会话状态不变时,重复组装会产生完全相同的字节;恢复与回放会折叠同一条持久模式事件和不可变的 `SessionHeader.cwd`,无需追赶状态。 + +归属范围保持收敛。批准策略仍由独立的 `approval:policy` 段落负责,计划模式仍由 `plan:policy` 负责,工具插件也继续负责各自的 schema 与操作引导。提示词负责说明策略;bash 与文件系统后端仍是强制执行边界。 + +## 曾考虑的替代方案 + +**仅叙述模式变更。** 不予采用,因为这会让新会话不了解策略,并把首次被拒绝的操作变成策略发现机制。如果可以直接渲染当前状态,也就无需额外定义基线。 + +**扫描拒绝历史或记住上次叙述的模式。** 不予采用,因为拒绝事件描述的是尝试过的操作,而不是权威的当前状态;进程本地的簿记也无法跨恢复保留。归属方可以在每次请求时直接折叠持久策略。 + +**通用运行时事实注册表。** 不予采用,因为现有系统提示词注册表已经会在请求时使用当前 agent 评估归属方提供的段落。单一策略归属方不存在需要另一个包或注册表来承载的跨领域不变式。 + +**在该段落中重复工具 schema,或批准与计划引导。** 不予采用,因为这些接口已有各自归属方和独立生命周期。重复内容会造成相互矛盾的请求前缀,并扩大缓存失效范围。 + +**继续省略沙箱模式,因为常驻模式标签曾引发预防性拒绝。** 后续 Web 证据与已经完成的跨工具族策略否决了这一方案。先前的句子只提到 bash 沙箱,没有说明实际的写入/编辑边界,因此可能与可见工具和升级引导冲突。由归属方派生的段落会说明完整的当前文件操作后果、规范化的工作区范围,并明确说明不作哪些保证,同时不重复工具指令。这只取代[沙箱 Agent Note](2026-07-06-sandbox.md) 中关于省略策略的决策;其中的强制执行与升级边界仍然有效。 + +## 后果 + +模型可以在试探工具前回答当前可能执行哪些文件操作,且 `/permission` 后的下一个请求会反映已提交的模式。这会增加一个小型动态系统段落,并在策略变化时有意使请求前缀缓存失效;状态不变时仍保持缓存稳定。该声明是引导,而不是强制执行护栏:运行时安全仍来自 `dsh-bash-sandbox` 与 `dsh-fs-sandbox` 消费同一项解析完成的策略。 + +聚焦的 sandbox-policy 测试固定了三种文本、规范化根目录、切换时机、字节稳定性与回放。无密钥的组装 ACP 快照通过真实 Loader 组合固定请求 header;Web 浏览器场景则驱动 `/permission` 在所有模式之间切换,检查每个确切的 `request/header`,并验证模型无需试探性工具调用即可完成;录制模式会使用真实提供方。 diff --git a/apps/web/tests/permission-policy-context.e2e.ts b/apps/web/tests/permission-policy-context.e2e.ts new file mode 100644 index 0000000000..e5b63d2d66 --- /dev/null +++ b/apps/web/tests/permission-policy-context.e2e.ts @@ -0,0 +1,126 @@ +// Web acceptance for current sandbox-policy context. A real Chromium drives +// the shipped /permission command through all three presets; record mode uses +// the real provider, while replay keeps the same provider-authored behavior +// keyless. Assertions read the exact durable request headers and tool calls, +// so assistant prose alone cannot satisfy the scenario. +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import type { Browser, Page } from 'playwright' +import { chromium } from 'playwright' +import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest' +import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox' +import type { SessionEvent } from '@deepseek-ai/dsh-session' +import { + assertFixtureInventory, fixtureUserPrompts, launchWebScaffold, recordFixture, + watchConsole, webSnapshotMode, type WebScaffold, +} from './scaffold.ts' +import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts' + +const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/permission-policy-context', import.meta.url)) +const FIXTURE = fileURLToPath(new URL('./snapshots/permission-policy-context/session.jsonl', import.meta.url)) +const MODE = webSnapshotMode() + +const PROMPTS = [ + 'Can you create or edit a normal file right now under the current policy? Answer directly in one sentence. Do not call a tool just to discover the policy.', + 'Does the DSH file sandbox currently restrict file operations? Answer directly in one sentence. Do not call tools.', + 'Under the current DSH file policy, where are writes allowed? Answer directly in one sentence. Do not call tools.', +] as const + +const PRESET_LABELS = ['Read Only', 'Danger Full Access', 'Workspace Write'] as const + +function requestSystems(events: readonly SessionEvent[]): string[] { + return events.flatMap((event) => { + if (event.type !== 'request/header') return [] + return typeof event.data.header.system === 'string' ? [event.data.header.system] : [] + }) +} + +function assistantTexts(events: readonly SessionEvent[]): string[] { + return events.flatMap((event) => { + if (event.type !== 'assistant/message') return [] + return [event.data.message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('').replaceAll('**', '')] + }) +} + +describe('web e2e: current sandbox policy reaches the model before tools', () => { + let scaffold: WebScaffold + let browser: Browser + let page: Page + let tripwire: ReturnType + let sessionWorkspace: string | undefined + const sessionEvents: SessionEvent[] = [] + + beforeAll(async () => { + scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE }) + scaffold.ctx.on('session/event', (session, event: SessionEvent) => { + sessionWorkspace = session.header.cwd + sessionEvents.push(event) + }) + browser = await chromium.launch() + page = await newEnglishPage(browser) + tripwire = watchConsole(page) + await page.goto(scaffold.baseUrl, { waitUntil: 'load' }) + await page.waitForSelector('[class*="frame"]', { timeout: 30_000 }) + await connectFreshWorkspace(page) + }, 120_000) + + afterAll(async () => { + await browser?.close() + await scaffold?.close() + }) + + it('switches read-only, danger-full-access, and workspace-write through the real GUI command path', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-permission-policy-context')) + if (MODE !== 'record') { + expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual(PROMPTS) + } + + const input = page.locator('textarea').first() + let sessionId: Awaited> | undefined + for (const [index, preset] of ['read-only', 'danger-full-access', 'workspace-write'].entries()) { + await input.fill(`/permission ${preset}`) + await input.press('Enter') + await page.getByRole('button', { name: `Access mode, current: ${PRESET_LABELS[index]}` }) + .waitFor({ timeout: 10_000 }) + + const settled = scaffold.whenTurnSettled() + await input.fill(PROMPTS[index] as string) + await input.press('Enter') + sessionId = await settled + await expect.poll(() => input.isEnabled(), { timeout: 10_000 }).toBe(true) + } + + if (sessionId === undefined) throw new Error('permission-policy scenario completed no model turn') + if (MODE === 'record') await recordFixture(scaffold, sessionId, FIXTURE) + }, 240_000) + + it.skipIf(MODE === 'record')('records each effective policy before the corresponding model behavior', () => { + const systems = requestSystems(sessionEvents) + expect(systems).toHaveLength(3) + expect(systems[0]).toContain('Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied') + expect(systems[1]).toContain('Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations.') + expect(systems[1]).toContain('Approval prompts are disabled in this session') + + if (sessionWorkspace === undefined) throw new Error('permission-policy scenario observed no session workspace') + const policy = { + mode: 'workspace-write' as const, + workspaceRoot: canonicalPath(sessionWorkspace), + } + const roots = writableRoots(policy) + expect(systems[2]).toContain(`Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: ${roots.map(root => JSON.stringify(root)).join(', ')}.`) + expect(systems[2]).not.toContain('Approval prompts are disabled in this session') + + const answers = assistantTexts(sessionEvents) + expect(answers).toHaveLength(3) + expect(answers[0]).toMatch(/cannot create or edit normal files|writes?.*denied/i) + expect(answers[1]).toMatch(/does not.*restrict file operations|not restrict.*file operations/i) + expect(answers[2]).toMatch(/workspace.*(temporary|temp)|writable roots/i) + expect(sessionEvents.filter(event => event.type === 'tool/call')).toHaveLength(0) + }) + + it.skipIf(MODE === 'record')('stays clean and keeps the fixture inventory closed', async () => { + expect(tripwire.pageErrors).toEqual([]) + expect(tripwire.warnings).toEqual([]) + await assertFixtureInventory(SNAPSHOT_DIR, ['session.jsonl']) + }) +}) diff --git a/apps/web/tests/snapshots/permission-policy-context/session.jsonl b/apps/web/tests/snapshots/permission-policy-context/session.jsonl new file mode 100644 index 0000000000..38a9103343 --- /dev/null +++ b/apps/web/tests/snapshots/permission-policy-context/session.jsonl @@ -0,0 +1,65 @@ +{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785396584675,"cwd":"{{cwd}}/workspace"} +{"type":"command/run","seq":0,"time":1785396584734,"data":{"commandId":"cmd-d3a5f5bf-1","name":"permission","args":" read-only","source":{"kind":"user"}}} +{"type":"permission/preset","seq":1,"time":1785396584734,"data":{"preset":"read-only"}} +{"type":"sandbox/mode","seq":2,"time":1785396584735,"data":{"mode":"read-only"}} +{"type":"approval/policy","seq":3,"time":1785396584735,"data":{"policy":"ask"}} +{"type":"command/done","seq":4,"time":1785396584735,"data":{"commandId":"cmd-d3a5f5bf-1","kind":"success","text":"Permission preset: read-only."}} +{"type":"turn/start","seq":5,"time":1785396584762,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}} +{"type":"user/message","seq":6,"time":1785396584762,"data":{"content":[{"type":"text","text":"Can you create or edit a normal file right now under the current policy? Answer directly in one sentence. Do not call a tool just to discover the policy."}],"source":{"kind":"user","rpcId":"{{rpcId}}"},"role":"user","id":"55d4adb1-31a5-41a4-9dc8-e69c39656ea7"},"surfaceOp":"append"} +{"type":"session/title","seq":7,"time":1785396584763,"data":{"title":"Can you create or edit","messageSeqs":[6],"source":{"kind":"fallback"}}} +{"type":"user/message","seq":8,"time":1785396584844,"data":{"content":[{"type":"text","text":"\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n\n- `15-05-premortem`: Use before starting risky work — rebases, large refactors, multi-file migrations, or deploys — to identify failure modes and mitigations in advance.\n- `acceptance-criteria`: 检查Acceptance Criteria格式和完整性,验证是否符合Given-When-Then结构、覆盖正常流程/边界条件/异常场景。适合在为User Story编写AC后、准备测试用例前使用,当需要验收AC质量时。帮助不熟悉BDD的PM/BA确保AC明确、可测试、覆盖完整,避免遗漏关键场景。\n- `agents-sdk`: Build AI agents on Cloudflare Workers using the Agents SDK. Load when creating stateful agents, durable workflows, real-time WebSocket apps, scheduled tasks, MCP servers, chat applications, voice agents, or browser automation. Covers Agent class, state management, callable RPC, Workflows, durable execution, queues, retries, observability, and React hooks. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.\n- `aico-pm-user-story-writing`: Transform requirements into well-structured User Stories using \"As a [user], I want [goal], So that [benefit]\" format with Given/When/Then acceptance criteria. Use this skill when: - User asks to \"write user story\", \"create story\", \"add story\" - User mentions \"user story\", \"backlog item\", \"story\" - Running /pm.plan and need to break PRD into implementable stories - Creating backlog items for development team - Need to formalize a requirement into standard story format - Converting feature req...\n- `algorithmic-art`: Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.\n- `animation-vocabulary`: Reverse-lookup glossary that turns a vague description of a web animation or motion effect into its exact term (\"the bouncy thing when a popover opens\" → Pop in; \"the iOS rubber-band scroll\" → Rubber-banding). Use when the user asks \"what's it called when…\", or describes a motion effect without knowing its name and wants the right word to prompt an AI or designer with. For naming an effect, not designing or building one.\n- `app-comprehensive-test-generator`: Generate exhaustive user-flow and edge-case test scenarios from an app's codebase, produce scenario .md files, execute tests using connected or newly created MCPs, and produce an app.qa.report.md summarizing failures and suggested fixes.\n- `apple-design`: Apple's approach to interface design and fluid, physical motion, translated for the web. Use when building or reviewing gesture-driven UI, spring animations, drag/swipe/sheet interactions, momentum and interruptible transitions, translucent materials and depth, typography (optical sizing, tracking, leading), reduced-motion, or the design foundations (feedback, spatial consistency, restraint) behind Apple-style interfaces.\n- `brainstorming`: You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.\n- `brand-guidelines`: Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.\n- `canvas-design`: Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.\n- `claude-api`: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration. TRIGGER — read BEFORE opening the target file; don't skip because it \"looks like a one-liner\" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answ...\n- `cloudflare`: Comprehensive Cloudflare platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), feature flags (Flagship), networking (Tunnel, Spectrum), security (WAF, DDoS), and infrastructure-as-code (Terraform, Pulumi). Use for any Cloudflare development task. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.\n- `cloudflare-email-service`: Send and receive transactional emails with Cloudflare Email Service (Email Sending + Email Routing). Use when building email sending (Workers binding or REST API), email routing, Agents SDK email handling, or integrating email into any app — Workers, Node.js, Python, Go, etc. Also use for email deliverability, SPF/DKIM/DMARC, wrangler email setup, MCP email tools, or when a coding agent needs to send emails. Even for simple requests like \"add email to my Worker\" — this skill has critical conf...\n- `cloudflare-one`: Guides Cloudflare One Zero Trust and SASE work across Access, Gateway, WARP, Tunnel, Cloudflare WAN, DLP, CASB, device posture, and identity. Use when designing, configuring, troubleshooting, or reviewing Cloudflare One deployments. Retrieval-first: use current Cloudflare docs/API schemas instead of embedded product docs.\n- `cloudflare-one-migrations`: Plans migrations from Zscaler ZIA/ZPA, Palo Alto, legacy VPN, SWG, or SASE stacks to Cloudflare One. Use for migration assessments, policy mapping, rollout plans, and parity/gap analysis.\n- `code-review`: Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes — Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/PRD asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to \"review since X\".\n- `codebase-design`: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.\n- `content-refiner`: Refine verbose educational content by eliminating redundancy, tightening prose, and strengthening lesson connections. Use when content is wordy, repetitive, or lacks narrative flow between sections.\n- `context-compression`: This skill should be used when long-running agent sessions need context compression, structured summarization, compaction, token-per-task optimization, or durable handoff summaries that preserve decisions, files, risks, and next actions.\n- `create-feishu-doc`: Create a Feishu document and grant edit permissions to the user. Use when asked to write content to Feishu or create a document in a wiki space.\n- `design-compass`: Use when doing any product or UI design work — brainstorming a feature, starting a visual/interaction direction, reviewing half-built UI, or final-checking before ship; also when unsure which design skill applies. 产品设计 / 界面设计 / 交互设计 / UI review / 脑暴 / 视觉方向 / 设计验收时使用。\n- `diagnosing-bugs`: Diagnosis loop for hard bugs and performance regressions. Use when the user says \"diagnose\"/\"debug this\", or reports something broken/throwing/failing/slow.\n- `doc-coauthoring`: Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.\n- `docx`: Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks\n- `domain-modeling`: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model.\n- `durable-objects`: Create and review Cloudflare Durable Objects. Use when building stateful coordination (chat rooms, multiplayer games, booking systems), implementing RPC methods, SQLite storage, alarms, WebSockets, or reviewing DO code for best practices. Covers Workers integration, wrangler config, and testing with Vitest. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.\n- `ego-browser`: ego-browser (ego-lite) is a Chromium-based browser designed from the ground up to be friendly to both human users and AI Agents. AI Agents work in their own isolated space, reusing the user's login state without competing for the browser. Use this skill whenever the user needs to interact with a website opening pages, filling forms, clicking buttons, taking screenshots, extracting page data, testing web apps, logging into sites, automating browser operations, or any other browser automation t...\n- `emil-design-eng`: This skill encodes Emil Kowalski's philosophy on UI polish, component design, animation decisions, and the invisible details that make software feel great.\n- `feishu-workflow`: 飞书文档全流程管理 — 搜索、创建、编辑 wiki 文档,支持内容排版、表格、代码块、白板\n- `find-animation-opportunities`: Search a codebase or UI for places that don't animate but should, and reject everything that shouldn't. Read-only; it proposes motion with exact values, it does not implement it. Use when the user asks \"what could be animated here?\" or wants to \"make this feel more alive\". For fixing existing animations, use improve-animations or review-animations instead.\n- `frontend-design`: Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.\n- `gap-to-topic`: Turn a research area into a go/no-go decision dossier for ONE candidate thesis/proposal topic — a 3-gate verdict (is the gap open? is it a contribution? is it feasible?) with the evidence laid out so the researcher can verify it. Use when the user asks \"is this gap worth pursuing\", \"help me pick a thesis topic\", \"is this idea already taken\", \"find me a defensible research gap\", \"vet this research idea before I commit\", or \"should I do this\". NOT a literature review (use `literature-triage-mat...\n- `gc-minimal-zine-poster-v0-1`: Generate Minimal Zine Poster v0.1 poetic paper-poster prompts and the matching generated image. Use when the user gives a theme, sentence, object, mood, article idea, photo, or content brief and wants a quiet Japanese/Korean zine-like editorial poster with large negative space, aged paper texture, experimental typography, restrained color accents, and a generated bitmap image.\n- `grilling`: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.\n- `grillme-workflow`: Use when implementing complex multi-step tasks that benefit from structured plan review, multi-model validation, and post-execution verification\n- `humanizer`: Remove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on Wikipedia's comprehensive \"Signs of AI writing\" guide. Detects and fixes patterns including: inflated symbolism, promotional language, superficial -ing analyses, vague attributions, em dash overuse, rule of three, AI vocabulary words, passive voice, negative parallelisms, and filler phrases.\n- `improve-animations`: Survey a codebase's animation and motion code as a senior motion advisor, then produce a prioritized audit and self-contained implementation plans for other agents (or cheaper models) to execute. Read-only on source code — it plans improvements, it does not apply them. Use when the user asks to \"improve the animations\", \"audit the motion\", \"make this app feel better\", or wants a roadmap of animation fixes rather than a review of a single diff.\n- `internal-comms`: A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).\n- `lark-approval`: 飞书审批:查询和处理审批待办/已办/实例,搜索可发起审批定义、查看定义详情并发起原生审批实例。当用户要处理审批任务、查看审批实例、搜索或发起审批时使用。审批待办不是飞书任务;非审批类待办走 lark-task。不负责创建审批定义;三方审批定义不走原生提单。\n- `lark-apps`: 妙搭(Spark/Miaoda)应用开发与托管:应用创建、本地全栈开发、云端生成迭代、创意设计(UI mockup / 可交互原型 / 线框图 / 落地页 / 仪表盘 / 幻灯片 deck / 视觉探索)、AI相关能力和飞书平台能力或者其他外部能力集成、日志/Trace/监控指标/PV/UV 查询、环境变量管理、应用角色与成员管理、自动化触发器(定时/记录变更/Webhook/飞书审批)。当用户要开发/新建一个系统·工具·平台·应用,或要本地开发 / 云端开发 / 修改 / 部署 / 发布 / 上线 / 拿可分享链接,或用 HTML 做页面·网站·部署到妙搭,或要设计 / design / mockup / prototype / wireframe / 做 PPT / deck / 视觉探索,或提到妙搭/Spark/Miaoda(应用运行时域名形如 *.aiforce.cloud)、应用数据库、应用文件存储、开放 API Key、可见范围、应用角色/角色成员、线上日志、接口请求量、错误量、延迟、访问量、环境变量、给妙搭应用配自动化任务/定时触发/审批通过后自动触发时使用。不负责...\n- `lark-attendance`: 飞书考勤打卡:查询自己的考勤打卡记录\n- `lark-base`: 飞书多维表格(Base)操作:建表、字段、记录、视图、统计、公式/lookup、表单、仪表盘、workflow、角色权限;遇到 Base/多维表格/bitable 或 /base/ 链接时使用。文件导入转 lark-drive,认证/授权转 lark-shared。\n- `lark-calendar`: 飞书日历:管理日历日程和会议室。查看/搜索日程、创建/更新日程、管理参会人、查询忙闲和推荐时段、预定会议室。当用户需要查看日程安排、创建/修改会议、查询/预定会议室时使用。不负责:查询过去的视频会议记录(走 lark-vc)、待办任务(走 lark-task)。\n- `lark-contact`: 飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名。当用户提到某人姓名要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。\n- `lark-doc`: 飞书云文档(Docx / Wiki 文档):读取和编辑飞书文档内容。当用户给出文档 URL 或 token,或需要查看、创建、编辑文档、插入或下载文档图片附件时使用。文档中嵌入的电子表格、多维表格、画板,先用本 skill 提取 token 再切到对应 skill。当用户给出 doubao.com 的 /docx/ 或 /wiki/ URL/token 时,也应直接使用本 skill;路由依据是 URL 路径模式和 token,而不是域名。不负责文档评论管理,也不负责表格或 Base 的数据操作。当用户明确要操作飞书思维笔记时,也使用本 skill。\n- `lark-drive`: 飞书云空间(云盘/云存储):管理 Drive 文件和文件夹,包含上传/下载、创建文件夹、复制/移动/删除、查看元数据、评论/权限/订阅、标题、版本、飞书文档密级标签(secure labels)和本地文件导入。用户需要整理云盘目录、处理云空间资源 URL/token、判断链接类型/真实 token/标题,或导入 Word/Markdown/Excel/CSV/PPTX/.base 为 docx/sheet/bitable/slides 时使用;doubao.com 云空间 URL/token 也按资源路径和 token 路由,不回退 WebFetch。不负责:文档内容编辑(走 lark-doc)、表格/Base 表内数据操作(走 lark-sheets/lark-base)、知识空间节点/成员管理(走 lark-wiki)、原生 Markdown 文件读写/patch/diff(走 lark-markdown)。\n- `lark-event`: Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed f...\n- `lark-im`: 飞书即时通讯:收发消息和管理群聊。发送和回复消息、搜索聊天记录、管理群聊成员、上传下载图片和文件(支持大文件分片下载)、管理表情回复、发送应用内/短信/电话加急、发送和处理交互卡片(Interactive Card)、监听卡片按钮回调(card.action.trigger)。当用户需要发消息、查看或搜索聊天记录、下载聊天中的文件、查看群成员、搜索群、创建群聊或话题群、管理标记数据、管理 Feed 置顶(添加/移除/查询置顶会话)、管理标签数据、处理卡片回调时使用。\n- `lark-mail`: 飞书邮箱:Use when user mentions 起草邮件、写邮件、草稿、发送/回复/转发邮件、查阅邮件、看邮件、搜索邮件、邮件文件夹、邮件标签、邮件联系人、监听新邮件、邮件收信规则等;use for mail/email intent only. Do not use for docs/sheets/calendar/auth setup/pure contact lookup/IM chat tasks.\n- `lark-markdown`: 飞书 Markdown:查看、创建、上传、编辑和比较 Markdown 文件。当用户需要创建或编辑 Markdown 文件、读取、修改、局部 patch 或比较差异时使用。不负责将 Markdown 导入为飞书在线文档,也不负责文件搜索、权限、评论、移动、删除等云空间管理操作。\n- `lark-minutes`: 飞书妙记:搜索妙记、查看妙记基础信息、下载/上传音视频、读取或编辑妙记的产物内容、改标题、替换说话人/关键词、申请妙记查看/编辑权限。当给出minute_token、本地音视频文件,要查/改/转妙记产物,或用户明确要主动申请妙记权限时使用;本地音视频转纪要/逐字稿优先走本 skill,不要用 ffmpeg/whisper 本地转写。不负责:获取会议关联妙记,或仅按自然语言标题定位纪要\n- `lark-note`: 飞书会议纪要(Note)直查:已知 note_id 时查询纪要详情、展示类型、关联文档 token,并读取 unified 原始逐字记录。当用户已持有 note_id,或从文档显式 vc-node-id 获得 note_id 时使用。不负责会议/日程/妙记定位、文档标题搜索或 Docx 正文读取。\n- `lark-okr`: 飞书 OKR:管理目标与关键结果。查看和编辑 OKR 周期、目标、关键结果、对齐关系、量化指标和进展记录。当用户需要查看或创建 OKR、管理目标和关键结果、查看对齐关系时使用。不负责:待办任务管理(lark-task)、日程/会议安排(lark-calendar)、绩效评估\n- `lark-openapi-explorer`: 飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。\n- `lark-shared`: Use for lark-cli setup/auth tasks: auth login/status/logout, user vs bot identity, business-domain permissions (--domain, including all/docs/drive), missing scopes, revoking authorization, or handling _notice JSON.\n- `lark-sheets`: 飞书电子表格:创建和操作电子表格。支持创建表格、管理工作表与行列结构(增删/合并/调整尺寸/隐藏/冻结)、读写单元格(值/公式/样式/批注/单元格图片)、查找替换、多操作原子批量更新,以及图表、透视表、条件格式、筛选器、迷你图、浮动图片等对象的创建与维护。当用户需要创建电子表格、管理工作表、批量读写或编辑数据、统计汇总与可视化、表格美化、公式计算(含 Excel 公式迁移)、金融/财务建模(DCF、三张表、预算、Sensitivity 等)等任务时使用。若用户是想按名称或关键词搜索云空间(云盘/云存储)里的表格文件,请改用 lark-drive 的 drive +search 先定位资源。当用户给出 doubao.com 的 /sheets/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。\n- `lark-skill-maker`: 创建 lark-cli 的自定义 Skill。当用户需要把飞书 API 操作封装成可复用的 Skill(包装原子 API 或编排多步流程)时使用。\n- `lark-slides`: 飞书幻灯片:创建和编辑幻灯片。创建演示文稿、读取幻灯片内容、管理幻灯片页面(创建、删除、读取、局部替换)。当用户需要创建或编辑幻灯片、读取或修改单个页面时使用。当用户给出 doubao.com 的 /slides/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。不负责:云文档内容编辑(走 lark-doc)、云文档里的独立画板对象(走 lark-whiteboard)、上传或下载普通文件(走 lark-drive)。\n- `lark-task`: 飞书任务:管理任务、清单和任务智能体。创建待办任务、查看和更新任务状态、拆分子任务、组织任务清单、分配协作成员、上传任务附件、注册或注销任务智能体、更新任务智能体的主页数据、写入智能体任务记录。当用户需要创建待办事项、查看任务列表、跟踪任务进度、管理项目清单或给他人分配任务、为任务上传附件文件、注册注销任务智能体、更新智能体主页数据、写入任务记录时使用。\n- `lark-vc`: 飞书视频会议:搜索历史会议记录、查询会议纪要(总结/待办/章节/逐字稿)、查询参会人快照。当用户查询已结束的会议、获取会议产物(纪要/妙记)、查看参会人时使用;查询未来日程走 lark-calendar。不负责:Agent 真实入会/离会、会中实时事件(走 lark-vc-agent)。\n- `lark-vc-agent`: 飞书视频会议会中能力:用于让应用机器人真实加入或离开正在进行的会议,并读取当前身份可见的会中事件、发送会中文本消息或会中表情。适用于用户询问正在开的会议发生了什么、谁在发言、是否共享内容,或需要发现当前可读的进行中会议 ID。不负责已结束会议搜索、参会人快照、纪要、逐字稿或录制查询,这些使用 lark-vc 技能。\n- `lark-whiteboard`: 飞书画板:查询和编辑飞书云文档中的画板。支持导出画板为预览图片、导出原始节点结构、使用多种格式更新画板内容。 当用户需要查看画板内容、导出画板图片、编辑画板时使用此 skill。不负责:飞书云文档内容编辑(lark-doc)、文档内嵌电子表格/Base(lark-sheets / lark-base)。\n- `lark-whiteboard-bindao`: 画板画图 skill。覆盖端到端流程:审美判断 → SVG 创作 → 渲染审查 → 写入飞书画板。 核心是审美标准(高于一切技术约束),技术流程基于 lark-whiteboard skill 的 SVG 路径。 触发:任何需要画图/画框架/画流程/可视化的场景。\n- `lark-wiki`: 飞书知识库:管理知识空间、空间成员和文档节点。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式。当用户需要在知识库中查找或创建文档、浏览知识空间结构、查看或管理空间成员、移动或复制节点时使用。当用户给出 doubao.com 的 /wiki/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。不负责:上传文件到知识库节点下(走 lark-drive)、编辑文档/表格/Base 内容(走 lark-doc / lark-sheets / lark-base)。\n- `lark-workflow-meeting-summary`: 会议纪要整理工作流:汇总指定时间范围内的会议纪要并生成结构化报告。当用户需要整理会议纪要、生成会议周报、回顾一段时间内的会议内容时使用。\n- `lark-workflow-standup-report`: 日程待办摘要:编排 calendar +agenda 和 task +get-my-tasks,生成指定日期的日程与未完成任务摘要。适用于了解今天/明天/本周的安排。\n- `latent-briefing`: This skill should be used when the user asks to \"share memory between agents\", \"KV cache compaction for multi-agent\", \"orchestrator worker context\", \"latent briefing\", \"reduce worker tokens\", \"cross-agent memory without summarization\", or discusses Attention Matching compaction, recursive language models with workers, or token explosion in hierarchical agents.\n- `literature-triage-matrix`: Turn a list of papers (Zotero collection, Obsidian cluster, manual list) into a compact comparison matrix written to .research/literature_matrix.md, instead of generic per-paper summaries. Use when the user asks to \"make a literature matrix\", \"compare these papers by method/data/limitations\", or \"decide which papers are central to my review\". If the user says \"extract the claims from these papers\": cross-paper comparison matrix → this skill; claims from their own manuscript draft → `paper-mem...\n- `mcp-builder`: Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).\n- `memory-systems`: This skill should be used for persistent semantic memory in agent systems: cross-session knowledge retention, entity tracking, temporal validity, graph or vector retrieval, memory consolidation, and memory benchmark selection. Route file-backed scratchpads to filesystem-context, handoff summaries to context-compression, and token-efficiency tactics to context-optimization.\n- `multi-agent-patterns`: This skill should be used when designing multi-agent systems that need context isolation, supervisor or swarm coordination, explicit handoffs, parallel execution, or a decision on whether multiple agents are justified.\n- `notebooklm-brief-verifier`: Compare a downloaded NotebookLM brief against the source bundle research-hub uploaded, and report missed sources, unsupported claims, contradictions, and recommended follow-up prompts. Use when the user asks to \"verify this NotebookLM brief\", \"check if the brief missed anything\", or \"compare downloaded notes to the cluster papers\".\n- `paper-memory-builder`: Convert a paper draft + figures + Zotero metadata into reusable .paper/claims.yml and .paper/figures.yml files so the academic-writing-skills skill can do writing, revision, and audit passes without re-reading the manuscript every time. Use when the user asks to \"build paper memory\", \"extract claims from this manuscript\", \"extract claims, supporting evidence, and figure key numbers\", or \"prepare this paper for AI-assisted writing\". NOT for summarizing cited papers in a literature cluster — th...\n- `paper-summarize`: After research-hub ingests a cluster of cited papers, fill the per-paper Key Findings + Methodology + Relevance sections in BOTH Obsidian markdown and the Zotero child note. Use when the user says \"fill the TODO Key Findings/Methodology blocks left by research-hub auto\", \"I just ran auto and don't know what these papers are about\", or \"summarize the papers in cluster X\". Invokes a supported LLM CLI on each paper's abstract. NOT for summarizing the user's own manuscript draft — that's `paper-m...\n- `pdf`: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.\n- `pptx`: Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions \"deck,\" \"s...\n- `prototype`: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.\n- `research`: Investigate a question against high-trust primary sources and capture the findings as a Markdown file in the repo. Use when the user wants a topic researched, docs or API facts gathered, or reading legwork delegated to a background agent.\n- `research-add-fields`: 向现有调研outline补充字段定义。\n- `research-add-items`: 向现有调研outline补充items(调研对象)。\n- `research-chapter-ops`: Use when beginning work on a thesis chapter — creates and maintains the Chapter Operations Document (OPS file) with structure, decisions, error triggers, and cross-section conventions. Triggers on 'new chapter', 'chapter operations', 'OPS file', 'chapter setup', 'chapter-level coordination'.\n- `research-citation-management`: Use when managing citations in thesis writing — three-pipeline system for gap analysis, verification, and programmatic insertion with dual-AI cross-verification. Triggers on 'add citations', 'citation gaps', 'verify references', 'insert citations', 'reference management', 'bibliography'.\n- `research-context-compressor`: Inspect a research repository and write a compact `.research/` workspace manifest (project_manifest.yml, experiment_matrix.yml, data_dictionary.yml) so future AI sessions can orient themselves without rescanning the whole repo. Use when the user asks to \"compress this project context\", \"create a research manifest\", or \"save the project context for future agents\".\n- `research-deep`: 读取调研outline,为每个item启动独立agent进行深度调研。禁用task output。\n- `research-design-helper`: Guide a researcher through 5 Socratic segments — research question sharpening, expected mechanism, identifiability check, validation plan, risk register — and produce `.research/design_brief.md`. Use when the user asks to \"frame this research question\", \"design my study\", \"help me think through what model to build\", \"sharpen my hypothesis\", \"is my research question sharp enough to be falsifiable?\", or \"before I start coding, walk me through the design\". Runs AFTER a topic is chosen — it desig...\n- `research-error-log`: Use when creating, structuring, or extending the project Error Log (`CLAUDE_ERROR_LOG.md` / `CLAUDE_ERROR_LOG_V2.md`) — defines the dual-track archive/active format, three-layer V2 architecture, pattern entry schema, add-new-pattern protocol, and how postmortem / brief / review skills interface with it. Triggers on 'error log', 'new error pattern', 'add pattern to log', 'set up error log', 'V2 checklist', 'CLAUDE_ERROR_LOG', 'how does the error log work'.\n- `research-figure-generation`: Use when creating publication-quality figures for thesis — pipeline from raw data through verification, generation, researcher review, to Word document integration. Triggers on 'create figure', 'plot data', 'generate figure', 'thesis figures', 'insert figures into Word'.\n- `research-gemini-review`: Use after Claude writes any thesis prose draft — invokes Gemini API as an independent cross-model critic to eliminate self-preference bias. REQUIRED after research-writing-brief produces prose and before research-three-stage-review can be considered final. Triggers on 'review this draft', 'cross-model review', 'Gemini check', 'independent review of thesis prose'.\n- `research-hub`: Operate research-hub workflows for literature discovery, source ingest into Zotero/Obsidian/NotebookLM, dashboard inspection, and vault maintenance. Use when the user asks to find papers and organize them, build a knowledge base, ingest a folder of PDFs, upload to NotebookLM, generate research briefs, inspect clusters, or maintain a research vault. NOT for auditing or cleaning up an existing Zotero library — that's `zotero-library-curator` (read-only audit) plus `zotero-skills` (for CRUD).\n- `research-hub-multi-ai`: Research-domain router that writes `.coord/multi_ai_plan.md` when a single round of work will need two or more delegates AND the work touches research-hub artifacts (`.research/`, `.paper/`, Zotero/Obsidian/NotebookLM pipelines). For a single delegate, use `codex-delegate` or `gemini-delegate` directly — do not invoke this skill. For generic non-research multi-agent decomposition (pure code refactor, generic translation, no research-hub artifact), use `agent-collab-workspace:agent-task-splitt...\n- `research-paper-adaptation`: Use when converting a published paper (where researcher is author) into a thesis chapter — adaptation protocol with side-by-side verification and change classification. Triggers on 'adapt paper', 'paper to thesis', 'convert publication', 'published paper chapter', 'adapt manuscript'.\n- `research-postmortem`: Use when a thesis draft is rejected and must be rewritten from scratch — structured 5-part investigation into process failure with root cause analysis and systemic action items. Triggers on 'draft rejected', 'rewrite from scratch', 'writing failure', 'postmortem', 'what went wrong with the draft'.\n- `research-pre-writing-discussion`: Use before creating a Writing Brief for any thesis section — structured interview to extract researcher's knowledge, judgments, and decisions through three phases. Triggers on 'discuss section', 'plan what to write', 'pre-writing discussion', 'before writing brief', 'extract knowledge for section'.\n- `research-project-orienter`: Read the .research/ manifest files at a project root and produce a single orientation memo (research question, datasets, current stage, key entrypoints, evidence artifacts, open questions). Use when the user asks to \"orient me in this project\", \"what is this repo about\", or \"build a context map for this paper\" — and the project already has .research/ manifests (or trigger research-context-compressor first).\n- `research-report`: 将deep调研结果汇总为markdown报告,覆盖所有字段,跳过不确定值。\n- `research-session-management`: Use when starting or ending any thesis writing session — manages INDEX files, handoff documents, and startup/shutdown protocols for cross-session continuity. Triggers on 'new thesis session', 'session handoff', 'continue thesis work', 'pick up where left off', 'end session'.\n- `research-style-audit`: Use after completing any thesis section draft — runs programmatic style audit to catch Pattern\n- `research-task-file`: Use when creating self-contained task files for autonomous AI agent execution — goal-oriented instructions with context, decision frameworks, and validation criteria. Triggers on 'create task file', 'autonomous task', 'agent task', 'data extraction task', 'TASK file', 'batch processing task'.\n- `research-three-stage-review`: Use after completing a thesis prose draft — runs three independent review stages with different perspectives and information access. Triggers on 'review draft', 'check section', 'draft review', 'quality check', 'before sending to advisor'.\n- `research-writing`: Use when starting any academic thesis or dissertation writing task — routes to the correct thesis sub-skill based on the current phase of work. Triggers on 'thesis', 'dissertation', 'chapter writing', 'section writing', 'defense prep', 'academic writing with AI'.\n- `research-writing-brief`: Use when planning any thesis section before writing prose — creates a Writing Brief with boundary rules, internalization check, verified data table, and paragraph-level outline with argumentative purposes. Triggers on 'plan section', 'write section X.Y', 'prepare to write', 'writing brief', 'section outline'.\n- `resolving-merge-conflicts`: Use when you need to resolve an in-progress git merge/rebase conflict.\n- `sandbox-sdk`: Build sandboxed applications for secure code execution. Load when building AI code execution, code interpreters, CI/CD systems, interactive dev environments, or executing untrusted code. Covers Sandbox SDK lifecycle, commands, files, code interpreter, and preview URLs. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.\n- `scheduled-task-planner`: 分析定时任务需求,确定最优部署方案(Cloudflare Worker 或本地 launchd)\n- `sequential-thinking`: Structured reflective problem-solving methodology. Process: decompose, analyze, hypothesize, verify, revise. Capabilities: complex problem decomposition, adaptive planning, course correction, hypothesis verification, multi-step analysis. Actions: decompose, analyze, plan, revise, verify solutions step-by-step. Keywords: sequential thinking, problem decomposition, multi-step analysis, hypothesis verification, adaptive planning, course correction, reflective thinking, step-by-step, thought sequ...\n- `skill-creator`: Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.\n- `slack-gif-creator`: Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like \"make me a GIF of X doing Y for Slack.\"\n- `tdd`: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions \"red-green-refactor\", or wants integration tests.\n- `test`: Test features before users find bugs. Use when feature is built, before deploying, or when bugs reported. Covers manual testing, edge cases, cross-browser testing, and testing checklists for non-technical founders.\n- `theme-factory`: Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.\n- `turnstile-spin`: Set up Cloudflare Turnstile end-to-end in a project — scan the codebase, create the widget via the Cloudflare API, deploy the managed siteverify Worker, write the frontend snippets, validate, and persist the skill. Load this when a user asks to add Turnstile, set up CAPTCHA, protect a form from bots, or fix a Turnstile integration. Mirrors developers.cloudflare.com/turnstile/spin.\n- `web-artifacts-builder`: Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.\n- `web-perf`: Analyzes web performance using Chrome DevTools MCP. Measures Core Web Vitals (LCP, INP, CLS) and supplementary metrics (FCP, TBT, Speed Index), identifies render-blocking resources, network dependency chains, layout shifts, caching issues, and accessibility gaps. Use when asked to audit, profile, debug, or optimize page load performance, Lighthouse scores, or site speed. Biases towards retrieval from current documentation over pre-trained knowledge.\n- `webapp-testing`: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.\n- `workers-best-practices`: Reviews and authors Cloudflare Workers code against production best practices. Load when writing new Workers, reviewing Worker code, configuring wrangler.jsonc, or checking for common Workers anti-patterns (streaming, floating promises, global state, secrets, bindings, observability). Biases towards retrieval from Cloudflare docs over pre-trained knowledge.\n- `wrangler`: Cloudflare Workers CLI for deploying, developing, and managing Workers, KV, R2, D1, Vectorize, Hyperdrive, Workers AI, Containers, Queues, Workflows, Pipelines, and Secrets Store. Load before running wrangler commands to ensure correct syntax and best practices. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.\n- `xlsx`: Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in...\n- `zotero-library-curator`: Audit and curate a Zotero library — find duplicate DOIs, orphan items missing required tags, propose collection rebinds, identify bloated or under-used collections, generate tag hygiene reports, emit preview-only cleanup plans. Use when the user asks to \"audit Zotero\", \"find duplicates\", \"tag hygiene report\", \"which collections are bloated or under-used\", or \"propose a Zotero cleanup plan\". Defers all CRUD operations to the standalone `zotero-skills` skill or `research-hub zotero` CLI. Includ...\n\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\n"}],"source":{"kind":"plugin","plugin":"dsh-tool-skill"},"role":"user","id":"9dcd8b4f-7633-4bb6-83df-1d1e5a9e259c"},"surfaceOp":"append"} +{"type":"step/start","seq":9,"time":1785396584846,"data":{"turn":1,"step":1}} +{"type":"request/header","seq":10,"time":1785396584846,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} +{"type":"assistant/chunk","seq":11,"time":1785396585703,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} +{"type":"reasoning-chunks","seq0":12,"time0":1785396585703,"data":{"turn":1,"step":1,"index":0,"dt":[102,57,1,2,2,1,1,0,12,1,0,0,17,1,0,0,0,0,22,1,0,0,0,1,21,26,1,0,21,2,0,0,0,21,25,3,0,21,0,0,0,0,2,22,2,0,0,0,21,2,0,0,36,3,0,0,11,0,0,0,0,0,45,2,0,31,1,0,34],"texts":["The"," user"," is"," asking"," a"," direct"," question"," about"," whether"," I"," can"," create"," or"," edit"," a"," normal"," file"," under"," the"," current"," policy","."," Let"," me"," check"," what"," the"," current"," policy"," says",".\n\n","From"," the"," system"," instructions",":"," \"","Current"," D","SH"," file"," sand","box"," policy",":"," read","-only","."," Ordinary"," file"," writes",","," edits",","," and"," file","-m","ut","ating"," shell"," effects"," are"," denied","...\"\n\n","So"," the"," answer"," is"," clear","."]}} +{"type":"assistant/chunk","seq":82,"time":1785396586277,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} +{"type":"text-chunks","seq0":83,"time0":1785396586277,"data":{"turn":1,"step":1,"index":1,"dt":[0,0,6,0,1,0,24,0,1,0,0,1,21,6,0,0,21,0,1,22,1,0,23,1,0,22,3,1,80,1,0,0,0,0,0,0],"texts":["No",","," under"," the"," current"," read","-only"," sand","box"," policy",","," I"," cannot"," create"," or"," edit"," normal"," files"," —"," any"," attempt"," to"," write",","," edit",","," or"," perform"," file","-m","ut","ating"," operations"," will"," be"," denied","."]}} +{"type":"assistant/chunk","seq":120,"time":1785396586514,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user is asking a direct question about whether I can create or edit a normal file under the current policy. Let me check what the current policy says.\n\nFrom the system instructions: \"Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied...\"\n\nSo the answer is clear."}}}} +{"type":"assistant/chunk","seq":121,"time":1785396586514,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"No, under the current read-only sandbox policy, I cannot create or edit normal files — any attempt to write, edit, or perform file-mutating operations will be denied."}}}} +{"type":"assistant/chunk","seq":122,"time":1785396586514,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":27,"outputTokens":108,"cacheReadTokens":14848,"reasoningTokens":70}}}} +{"type":"assistant/chunk","seq":123,"time":1785396586514,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} +{"type":"assistant/message","seq":124,"time":1785396586516,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user is asking a direct question about whether I can create or edit a normal file under the current policy. Let me check what the current policy says.\n\nFrom the system instructions: \"Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied...\"\n\nSo the answer is clear."},{"type":"text","text":"No, under the current read-only sandbox policy, I cannot create or edit normal files — any attempt to write, edit, or perform file-mutating operations will be denied."}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"96a4eae6-6783-4ce3-969e-1eae425b9e9a"},"usage":{"inputTokens":27,"outputTokens":108,"cacheReadTokens":14848,"reasoningTokens":70}},"sourceEventSeqs":[11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123],"surfaceOp":"append"} +{"type":"step/end","seq":125,"time":1785396586516,"data":{"turn":1,"step":1}} +{"type":"turn/end","seq":126,"time":1785396586516,"data":{"turn":1,"reason":{"kind":"completed"}}} +{"type":"command/run","seq":127,"time":1785396586539,"data":{"commandId":"cmd-d3a5f5bf-2","name":"permission","args":" danger-full-access","source":{"kind":"user"}}} +{"type":"permission/preset","seq":128,"time":1785396586539,"data":{"preset":"danger-full-access"}} +{"type":"sandbox/mode","seq":129,"time":1785396586539,"data":{"mode":"danger-full-access"}} +{"type":"approval/policy","seq":130,"time":1785396586539,"data":{"policy":"never"}} +{"type":"command/done","seq":131,"time":1785396586539,"data":{"commandId":"cmd-d3a5f5bf-2","kind":"success","text":"Permission preset: danger-full-access."}} +{"type":"turn/start","seq":132,"time":1785396586553,"data":{"turn":2,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}} +{"type":"user/message","seq":133,"time":1785396586553,"data":{"content":[{"type":"text","text":"Does the DSH file sandbox currently restrict file operations? Answer directly in one sentence. Do not call tools."}],"source":{"kind":"user","rpcId":"{{rpcId}}"},"role":"user","id":"c52b6803-4d50-4070-8630-f92672bdb1de"},"surfaceOp":"append"} +{"type":"user/message","seq":134,"time":1785396586557,"data":{"content":[{"type":"text","text":"The approval policy changed from \"ask\" to \"never\" (changed by the user)."}],"source":{"kind":"plugin","plugin":"user-approval"},"role":"user","id":"5e21f7ad-5780-4d9e-aaf1-a60ba18f5904"},"surfaceOp":"append"} +{"type":"step/start","seq":135,"time":1785396586557,"data":{"turn":2,"step":1}} +{"type":"request/header","seq":136,"time":1785396586558,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}"},"reason":"change"}} +{"type":"assistant/chunk","seq":137,"time":1785396587353,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} +{"type":"reasoning-chunks","seq0":138,"time0":1785396587354,"data":{"turn":2,"step":1,"index":0,"dt":[153,30,3,0,0,0,0,14,1,0,0,0,2,20,2,0,0,0,21,2,0,21,2,0,20,23,1,0,0,0,0,64,2,0,0,0,0,1,0,0,5,1,0,0,0,0,35,1,0,0,0,0,6,0,0,0,22,2,0,0,0,22,2,0,0,0,21,0,0,0,0,2,22,2,0,0,0,19,1,0,0,0,1,22,3,0,0,19,2,1,0,28,5,0,0,1,23,3,0,0,0,29,1,0,0,0,0,20,27,3,0,0,17,2,0,0,0,21,2,0,0,18,0,0,0,1,1,21,24,43,1,4,0,22,39,0,0,7,23,25,1,22,1,1,1,18,3,0,0,0,22,22,8,1,0,15,2,0,0,23,1,0,0,20,0,0,23,2,0,19,2,23,2,0,48,0,0,0,1,27,13,25,21,2,1,22,1,0,0,21,2,0,19],"texts":["The"," user"," is"," asking"," about"," the"," current"," D","SH"," file"," sand","box"," policy","."," Let"," me"," look"," at"," the"," system"," prompt"," information",".\n\n","The"," system"," says",":"," \"","Current"," D","SH"," file"," sand","box"," policy",":"," danger","-full","-access","."," The"," D","SH"," file"," sand","box"," does"," not"," restrict"," file"," operations","."," Host"," OS"," permissions"," and"," other"," policies"," still"," apply","."," This"," policy"," does"," not"," govern"," network"," or"," process"," access",".\"\n\n","And",":"," \"","Appro","val"," prompts"," are"," disabled"," in"," this"," session",":"," actions"," that"," require"," approval"," are"," rejected"," automatically"," —"," do"," not"," request"," sand","box"," escalation"," (","do"," not"," set"," sand","box","_per","missions",").","\"\n\n","So"," the"," D","SH"," file"," sand","box"," does"," NOT"," restrict"," file"," operations"," under"," \"","danger","-full","-access","\""," policy","."," The"," restriction"," about"," approval"," being"," disabled"," is"," separate"," -"," it"," means"," if"," an"," action"," needs"," approval",","," it","'s"," rejected"," automatically"," instead"," of"," asking"," the"," user","."," But"," since"," the"," sand","box"," policy"," is"," \"","danger","-full","-access","\","," file"," operations"," are"," not"," restricted"," by"," the"," sand","box",".\n\n","However",","," I"," previously"," answered"," incorrectly","."," Let"," me"," correct"," that"," based"," on"," what"," the"," system"," says","."]}} +{"type":"assistant/chunk","seq":332,"time":1785396588796,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} +{"type":"text-chunks","seq0":333,"time0":1785396588796,"data":{"turn":2,"step":1,"index":1,"dt":[19,1,21,3,32,1,0,0,12,0,0,0,0,3,49,0,0,0,0,2,13,1,0,0,0,23,0,0,1,0,0,20,1,0,0,23],"texts":["You"," have"," it"," right",":"," the"," current"," D","SH"," file"," sand","box"," policy"," is"," **","danger","-full","-access","**,"," meaning"," the"," sand","box"," does"," **","not","**"," restrict"," file"," operations"," —"," my"," previous"," answer"," was"," incorrect","."]}} +{"type":"assistant/chunk","seq":370,"time":1785396589055,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user is asking about the current DSH file sandbox policy. Let me look at the system prompt information.\n\nThe system says: \"Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access.\"\n\nAnd: \"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set sandbox_permissions).\"\n\nSo the DSH file sandbox does NOT restrict file operations under \"danger-full-access\" policy. The restriction about approval being disabled is separate - it means if an action needs approval, it's rejected automatically instead of asking the user. But since the sandbox policy is \"danger-full-access\", file operations are not restricted by the sandbox.\n\nHowever, I previously answered incorrectly. Let me correct that based on what the system says."}}}} +{"type":"assistant/chunk","seq":371,"time":1785396589055,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"You have it right: the current DSH file sandbox policy is **danger-full-access**, meaning the sandbox does **not** restrict file operations — my previous answer was incorrect."}}}} +{"type":"assistant/chunk","seq":372,"time":1785396589055,"data":{"turn":2,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":122,"outputTokens":232,"cacheReadTokens":14848,"reasoningTokens":194}}}} +{"type":"assistant/chunk","seq":373,"time":1785396589055,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} +{"type":"assistant/message","seq":374,"time":1785396589056,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user is asking about the current DSH file sandbox policy. Let me look at the system prompt information.\n\nThe system says: \"Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access.\"\n\nAnd: \"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set sandbox_permissions).\"\n\nSo the DSH file sandbox does NOT restrict file operations under \"danger-full-access\" policy. The restriction about approval being disabled is separate - it means if an action needs approval, it's rejected automatically instead of asking the user. But since the sandbox policy is \"danger-full-access\", file operations are not restricted by the sandbox.\n\nHowever, I previously answered incorrectly. Let me correct that based on what the system says."},{"type":"text","text":"You have it right: the current DSH file sandbox policy is **danger-full-access**, meaning the sandbox does **not** restrict file operations — my previous answer was incorrect."}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"922c051f-f03b-45c5-8702-cf8766bdb303"},"usage":{"inputTokens":122,"outputTokens":232,"cacheReadTokens":14848,"reasoningTokens":194}},"sourceEventSeqs":[137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373],"surfaceOp":"append"} +{"type":"step/end","seq":375,"time":1785396589056,"data":{"turn":2,"step":1}} +{"type":"turn/end","seq":376,"time":1785396589056,"data":{"turn":2,"reason":{"kind":"completed"}}} +{"type":"command/run","seq":377,"time":1785396589076,"data":{"commandId":"cmd-d3a5f5bf-3","name":"permission","args":" workspace-write","source":{"kind":"user"}}} +{"type":"permission/preset","seq":378,"time":1785396589076,"data":{"preset":"workspace-write"}} +{"type":"sandbox/mode","seq":379,"time":1785396589077,"data":{"mode":"workspace-write"}} +{"type":"approval/policy","seq":380,"time":1785396589077,"data":{"policy":"ask"}} +{"type":"command/done","seq":381,"time":1785396589077,"data":{"commandId":"cmd-d3a5f5bf-3","kind":"success","text":"Permission preset: workspace-write."}} +{"type":"turn/start","seq":382,"time":1785396589108,"data":{"turn":3,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}} +{"type":"user/message","seq":383,"time":1785396589108,"data":{"content":[{"type":"text","text":"Under the current DSH file policy, where are writes allowed? Answer directly in one sentence. Do not call tools."}],"source":{"kind":"user","rpcId":"{{rpcId}}"},"role":"user","id":"7ced4dc1-ac7a-4d9e-aa2c-418cd389aa46"},"surfaceOp":"append"} +{"type":"user/message","seq":384,"time":1785396589110,"data":{"content":[{"type":"text","text":"The approval policy changed from \"never\" to \"ask\" (changed by the user)."}],"source":{"kind":"plugin","plugin":"user-approval"},"role":"user","id":"707a35ba-0211-4b5e-b6b2-0214636ae501"},"surfaceOp":"append"} +{"type":"step/start","seq":385,"time":1785396589110,"data":{"turn":3,"step":1}} +{"type":"request/header","seq":386,"time":1785396589110,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}"},"reason":"change"}} +{"type":"assistant/chunk","seq":387,"time":1785396589989,"data":{"turn":3,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} +{"type":"reasoning-chunks","seq0":388,"time0":1785396589989,"data":{"turn":3,"step":1,"index":0,"dt":[408,22,2,0,0,29,0,0,0,0,17,0,0,0,0,1,23,0,0,21,2,1,0,19,24,0,23,1,26,0,0,20,2,21,1,0,23,1,0,0,0,1,22,2,0,1,0,21,0,0,0,0,38,2,0,0,4,1,0,26,1,0,0,0,0,20,0,0,0,47,0,0,0,0,1,0,0,0,23,2,1,0,21,0,0,0,0,20,2,0,0,0,21,4,0,21,1,0,22,1,0,22,1,0,0,0,32,3,0,0,12,1,0,0,0,0,25,1,0,0,0,0,18,1,0,0,0,2,27,0,0,1,0,0,26,0,0,0,0,0,12,0,0,1,0,0,33,0,0,0,0,12,0,0,1,0,36,18,0,0],"texts":["The"," user"," is"," asking"," about"," the"," current"," D","SH"," file"," sand","box"," policy","."," Let"," me"," look"," at"," what"," was"," stated"," earlier"," in"," the"," system"," prompt"," about"," the"," workspace","-w","rite"," policy",":\n\n","\"","Current"," D","SH"," file"," sand","box"," policy",":"," workspace","-w","rite","."," File"," writes",","," edits",","," and"," file","-m","ut","ating"," shell"," effects"," are"," limited"," to"," these"," canonical"," writ","able"," roots",":"," \"/","private","/","var","/f","old","ers","/","8","k","/k","j","35","k","1","fd","6","t","90","n","0","cz","g","7","k","3","hv","140","000","gn","/T","/d","sh","-web","-e","2","e","-w","s","-z","G","m","zd","T","/","works","pace","\","," \"/","private","/t","mp","\","," \"/","private","/","var","/f","old","ers","/","8","k","/k","j","35","k","1","fd","6","t","90","n","0","cz","g","7","k","3","hv","140","000","gn","/T","\"",".\"\n\n","So"," I"," should"," answer"," based"," on"," that","."]}} +{"type":"assistant/chunk","seq":549,"time":1785396591287,"data":{"turn":3,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} +{"type":"text-chunks","seq0":550,"time0":1785396591287,"data":{"turn":3,"step":1,"index":1,"dt":[21,1,0,0,0,0,17,1,0,0,0,0,22,3,35,1,15,1,0,0,0,0,18,0,1,21,0,0,0,0,0,42,2,0,25],"texts":["Under"," the"," current"," `","works","pace","-w","rite","`"," policy",","," writes"," are"," allowed"," only"," within"," three"," canonical"," writ","able"," roots",":"," the"," workspace"," directory",","," `/","private","/t","mp","`,"," and"," the"," temp"," directory","."]}} +{"type":"assistant/chunk","seq":586,"time":1785396591514,"data":{"turn":3,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user is asking about the current DSH file sandbox policy. Let me look at what was stated earlier in the system prompt about the workspace-write policy:\n\n\"Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: \"{{cwd}}/workspace\", \"/private/tmp\", \"/private/var/folders/8k/kj35k1fd6t90n0czg7k3hv140000gn/T\".\"\n\nSo I should answer based on that."}}}} +{"type":"assistant/chunk","seq":587,"time":1785396591514,"data":{"turn":3,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"Under the current `workspace-write` policy, writes are allowed only within three canonical writable roots: the workspace directory, `/private/tmp`, and the temp directory."}}}} +{"type":"assistant/chunk","seq":588,"time":1785396591514,"data":{"turn":3,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":14862,"outputTokens":198,"cacheReadTokens":256,"reasoningTokens":161}}}} +{"type":"assistant/chunk","seq":589,"time":1785396591514,"data":{"turn":3,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} +{"type":"assistant/message","seq":590,"time":1785396591514,"data":{"turn":3,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user is asking about the current DSH file sandbox policy. Let me look at what was stated earlier in the system prompt about the workspace-write policy:\n\n\"Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: \"{{cwd}}/workspace\", \"/private/tmp\", \"/private/var/folders/8k/kj35k1fd6t90n0czg7k3hv140000gn/T\".\"\n\nSo I should answer based on that."},{"type":"text","text":"Under the current `workspace-write` policy, writes are allowed only within three canonical writable roots: the workspace directory, `/private/tmp`, and the temp directory."}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"e1dfef5a-338c-4518-843a-33e27415eeca"},"usage":{"inputTokens":14862,"outputTokens":198,"cacheReadTokens":256,"reasoningTokens":161}},"sourceEventSeqs":[387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589],"surfaceOp":"append"} +{"type":"step/end","seq":591,"time":1785396591515,"data":{"turn":3,"step":1}} +{"type":"turn/end","seq":592,"time":1785396591515,"data":{"turn":3,"reason":{"kind":"completed"}}} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 7a7f228fb0..c1bebd6f49 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -38,7 +38,8 @@ "tests/cordis-tool-round.e2e.ts", "tests/message-actions.e2e.ts", "tests/queue-actions.e2e.ts", - "tests/skill-invocation-policy.e2e.ts" + "tests/skill-invocation-policy.e2e.ts", + "tests/permission-policy-context.e2e.ts" ], "references": [ { diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 191d96b255..9f769ea24c 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -1007,7 +1007,7 @@ export interface Config { Depends on: [`SandboxMode`](core-data-structures/sandbox.md) -Source: [`packages/sandbox/sandbox-policy/src/index.ts:44`](../packages/sandbox/sandbox-policy/src/index.ts) +Source: [`packages/sandbox/sandbox-policy/src/index.ts:66`](../packages/sandbox/sandbox-policy/src/index.ts) ## `@deepseek-ai/dsh-session-persistence-jsonl` diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index c655de3a70..7376eccec6 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -1017,7 +1017,7 @@ Source: [`packages/sandbox/sandbox/src/index.ts:131`](../../packages/sandbox/san ## `ctx.sandboxPolicy` — `SandboxPolicyService` -The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode and fallback workspace root. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability. +The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability. ```ts cordis-catalog /** @@ -1041,7 +1041,7 @@ overrideOf(session: Session): SandboxMode | undefined Types: [SandboxExecutionPolicy](../core-data-structures/sandbox.md) · [SandboxMode](../core-data-structures/sandbox.md) · [SandboxPolicyRequest](../core-data-structures/sandbox.md) · [Session](../core-data-structures/session.md) -Source: [`packages/sandbox/sandbox-policy/src/index.ts:68`](../../packages/sandbox/sandbox-policy/src/index.ts) +Source: [`packages/sandbox/sandbox-policy/src/index.ts:90`](../../packages/sandbox/sandbox-policy/src/index.ts) ## `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) diff --git a/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml b/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml index f1261dc294..2c3a099dd9 100644 --- a/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml +++ b/examples/acp-agent/session-sandbox-root.cordis.snapshot.yml @@ -36,7 +36,7 @@ - id: sandbox-policy name: '@deepseek-ai/dsh-sandbox-policy' config: - mode: danger-full-access + mode: workspace-write workspaceRoot: /tmp - insert: - id: llm-replay diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md index 58a54a1509..4bfecf73a0 100644 --- a/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`). diff --git a/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md index 0cee2a6517..6642a2a761 100644 --- a/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`). diff --git a/examples/acp-agent/tests/snapshots/escalation-approved/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/escalation-approved/system-prompt.expected.md index e3437ad61a..2b97a4ed5d 100644 --- a/examples/acp-agent/tests/snapshots/escalation-approved/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/escalation-approved/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: "{{cwd}}", "/private/tmp", "/private/var/folders/8k/kj35k1fd6t90n0czg7k3hv140000gn/T". Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access. + Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. diff --git a/examples/acp-agent/tests/snapshots/lsp-definition/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/lsp-definition/system-prompt.expected.md index 7bde8fe289..271cffa6a2 100644 --- a/examples/acp-agent/tests/snapshots/lsp-definition/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/lsp-definition/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use search/read for ordinary navigation. Use lsp when textual matches are ambiguous or before a change requires precise definitions, implementations, or references. Positions are one-based line and character (UTF-16) at the cursor; an off-symbol position may return no results. findReferences always includes the declaration. Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. diff --git a/examples/acp-agent/tests/snapshots/pty-tools/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/pty-tools/system-prompt.expected.md index df065a83cb..6fd672513e 100644 --- a/examples/acp-agent/tests/snapshots/pty-tools/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/pty-tools/system-prompt.expected.md @@ -17,6 +17,8 @@ Use a terminal session only when work needs persistent terminal state or interac Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`). diff --git a/examples/acp-agent/tests/snapshots/session-query-spill/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/session-query-spill/system-prompt.expected.md index 68bdd841c7..28ef38abec 100644 --- a/examples/acp-agent/tests/snapshots/session-query-spill/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/session-query-spill/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use session_search to find relevant work from prior sessions, or session_event_search to search earlier events in one session. Search results are cursor-free and workspace-scoped. Follow a useful hit with session_trace, session_event_trace, or session_event_read when you need lineage, relationships, or exact data. Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. diff --git a/examples/acp-agent/tests/snapshots/text-turn/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/text-turn/system-prompt.expected.md index 17e6773a03..50a901a134 100644 --- a/examples/acp-agent/tests/snapshots/text-turn/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/text-turn/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`). diff --git a/examples/acp-agent/tests/snapshots/web-fetch/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/web-fetch/system-prompt.expected.md index 45705db0a5..aab4a34d15 100644 --- a/examples/acp-agent/tests/snapshots/web-fetch/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/web-fetch/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL (for example a result from web_search). It returns the page content decoded to text. Cite the URL as a markdown link when you use its content. Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. diff --git a/examples/acp-agent/tests/snapshots/workspace-context/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/workspace-context/system-prompt.expected.md index 6cd8d5725f..fec15f48cb 100644 --- a/examples/acp-agent/tests/snapshots/workspace-context/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/workspace-context/system-prompt.expected.md @@ -15,6 +15,8 @@ Check the [exit code: N] marker on every bash result; investigate failures befor Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. + Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`). diff --git a/packages/sandbox/sandbox-policy/README.i18n.yaml b/packages/sandbox/sandbox-policy/README.i18n.yaml index b21c8d885a..b926dba213 100644 --- a/packages/sandbox/sandbox-policy/README.i18n.yaml +++ b/packages/sandbox/sandbox-policy/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/sandbox/sandbox-policy/README.md -README.md: dca54330bc888af9ecac21aa92019d8a2b0140bd -README.zh.md: a201d48c81f563fc3d85495e964bb67432517a3c +README.md: 3258492ba80102ec96d37baa5e4989b1c396cf15 +README.zh.md: 4bb7e3b1321620413b81414d60f7b2d02df588c6 diff --git a/packages/sandbox/sandbox-policy/README.md b/packages/sandbox/sandbox-policy/README.md index dca54330bc..3258492ba8 100644 --- a/packages/sandbox/sandbox-policy/README.md +++ b/packages/sandbox/sandbox-policy/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The single owner of sandbox-policy resolution: the deployment's default [`SandboxMode`](../sandbox/README.md) and fallback root, plus each session's durable mode override and immutable workspace root. Every enforcing capability family receives one resolved mode-and-root policy per call. +The single owner of sandbox-policy resolution: the deployment's default [`SandboxMode`](../sandbox/README.md) and fallback root, plus each session's durable mode override and immutable workspace root. Every enforcing capability family receives one resolved mode-and-root policy per call, and the model receives that same effective policy before each request. ## Why a shared home @@ -17,23 +17,50 @@ Two families enforce the same mode vocabulary: the sandboxed bash executor (`@de - `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.defaultMode` / `ctx.sandboxPolicy.workspaceRoot` — the deployment default and fallback root used by `resolve()`. +- `sandbox:policy` — a request-time system-prompt section derived from `resolve({ session })`. It states the current file-effect mode, its consequences, and every canonical writable root under `workspace-write`; it does not claim host permissions, sandbox-backend readiness, or network/process restrictions. - `effectiveSandboxMode(events)` — the pure fold of a session's `sandbox/mode` events (the last switch wins, or `undefined`), used inside `resolve()`. - `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. - `SANDBOX_MODES` — every mode, for option advertisement and runtime validation. -The optional `./invariant` companion rejects a forged durable `sandbox/mode` event whose value falls outside that closed vocabulary; Session and its companion own the surrounding storage and core execution-enclosure rules. +The optional `./invariant` companion rejects a forged durable `sandbox/mode` event whose value falls outside that closed vocabulary; Session and its companion own the surrounding storage and core execution-enclosure rules. The rendered section is logged inside `request/header`, so the exact effective policy remains reconstructable without another event or an in-memory “last told” mirror. ## 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 ?? 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 stays log-only; the next request assembles the current section from the fold before any tool call. ## Model Experience -Indirectly, through `dsh-tool-bash` and `dsh-tool-fs`, which render the effective mode this service holds in their `[sandbox: …]` denial markers and escalation prompts; the `sandbox/mode` event itself never reaches the model. +### Current file sandbox policy + +#### What the model sees + +One `sandbox:policy` system section on every agent request. The section states only DSH file-effect policy; tool schemas remain their owners' surfaces, approval policy remains `dsh-user-approval`'s section, and plan guidance remains `dsh-plan-mode`'s section. + +##### Read-only + +```markdown +Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied; required sinks such as `/dev/null` may remain writable. Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access. +``` + +##### Workspace-write + +```markdown +Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: "", "". Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access. +``` + +##### Danger-full-access + +```markdown +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. +``` + +#### Token effect + +One concise system section per request. `workspace-write` additionally lists the canonical session workspace root plus the canonical `/tmp` and platform temporary roots, deduplicated when they identify the same directory. #### KV Cache effect -No direct invalidation; the named consumers own any request-prefix changes, and the mode is deliberately absent from the prompt. +The request prefix is byte-stable while the session mode and immutable workspace root stay unchanged. A mode switch changes the section on the next request; the resulting `request/header` records the new prefix. ## Known Limitations and Deferred Work diff --git a/packages/sandbox/sandbox-policy/README.zh.md b/packages/sandbox/sandbox-policy/README.zh.md index a201d48c81..4bb7e3b132 100644 --- a/packages/sandbox/sandbox-policy/README.zh.md +++ b/packages/sandbox/sandbox-policy/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -沙箱策略解析的唯一归属位置:部署默认 [`SandboxMode`](../sandbox/README.md) 与回退根目录,加上每个会话的持久模式覆盖和不可变工作区根目录。每个强制执行策略的能力家族在每次调用时都会收到一项解析完成的模式与根目录策略。 +沙箱策略解析的唯一归属位置:部署默认 [`SandboxMode`](../sandbox/README.md) 与回退根目录,加上每个会话的持久模式覆盖和不可变工作区根目录。每个强制执行策略的能力家族在每次调用时都会收到一项解析完成的模式与根目录策略,模型也会在每次请求前收到同一项有效策略。 ## 为何需要共享归属位置 @@ -17,23 +17,50 @@ - `ctx.sandboxPolicy.resolve({ session?, mode? })`:解析一项完整的逐调用策略。显式批准的模式优先于会话最后一条 `sandbox/mode` 事件,后者又优先于 `defaultMode`;会话不可变的 `cwd` 会先按文件系统语义规范化,再成为 `workspaceRoot`,否则使用配置的回退值。规范化先于词法归一化,因此 `symlink/..` 与进程工作目录解析保持一致。 - `ctx.sandboxPolicy.defaultMode`/`ctx.sandboxPolicy.workspaceRoot`:`resolve()` 使用的部署默认值与回退根目录。 +- `sandbox:policy`:由 `resolve({ session })` 派生的请求时系统提示词段落。它说明当前文件操作模式及其后果,并列出 `workspace-write` 下所有规范化的可写根目录;不会声称主机权限、沙箱后端就绪状态或网络/进程限制。 - `effectiveSandboxMode(events)`:会话 `sandbox/mode` 事件的纯 fold(最后一次切换胜出,没有则为 `undefined`),在 `resolve()` 内使用。 - `setSandboxMode(session, mode)`:逐会话覆盖的唯一写入路径:恰好追加一条 `sandbox/mode` 事件。切换本身就是事件;不会在带外修改模式。 - `SANDBOX_MODES`:所有模式,用于选项展示与运行时验证。 -可选的 `./invariant` 配套组件会拒绝伪造的持久 `sandbox/mode` 事件,只要其值不在该封闭词汇中;Session 与其配套组件负责相关存储与核心执行封闭规则。 +可选的 `./invariant` 配套组件会拒绝伪造的持久 `sandbox/mode` 事件,只要其值不在该封闭词汇中;Session 与其配套组件负责相关存储与核心执行封闭规则。渲染后的段落记录在 `request/header` 中,因此无需另一条事件或内存中的「上次告知」镜像,也能重建确切的有效策略。 ## 逐会话存储 -运行时切换是在对应会话日志中追加的一条 `sandbox/mode` 事件。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖会通过回放跨重启保留,两个会话也绝不会看到彼此状态。工作区标识无需另一条事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。该事件只进入日志(沿用 `approval/*` 先例):模型通过强制执行工具的拒绝标记获知模式,绝不会从事件获知。 +运行时切换是在对应会话日志中追加的一条 `sandbox/mode` 事件。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖会通过回放跨重启保留,两个会话也绝不会看到彼此状态。工作区标识无需另一条事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。该事件仍只进入日志;下一次请求会在任何工具调用发生前,根据 fold 组装当前段落。 ## 模型体验 -通过 `dsh-tool-bash` 和 `dsh-tool-fs` 间接影响;它们会在 `[sandbox: …]` 拒绝标记和升权提示词中渲染该服务持有的有效模式,`sandbox/mode` 事件本身绝不会到达模型。 +### 当前文件沙箱策略 + +#### 模型看到的内容 + +每次 agent 请求都有一个 `sandbox:policy` 系统段落。该段落只说明 DSH 文件操作策略;工具 schema 仍由各自归属方管理,批准策略仍由 `dsh-user-approval` 的段落管理,计划引导仍由 `dsh-plan-mode` 的段落管理。 + +##### 只读 + +```markdown +Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied; required sinks such as `/dev/null` may remain writable. Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access. +``` + +##### 工作区写入 + +```markdown +Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: "", "". Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access. +``` + +##### 完全访问 + +```markdown +Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access. +``` + +#### Token 影响 + +每个请求增加一个简洁的系统段落。`workspace-write` 还会列出规范化的会话工作区根目录,以及规范化的 `/tmp` 与平台临时根目录;如果它们指向同一目录则去重。 #### KV Cache 影响 -不会直接使 KV Cache 失效;请求前缀变更由上述消费方负责,且提示词有意不包含模式。 +只要会话模式与不可变工作区根目录不变,请求前缀就在字节层面保持稳定。模式切换会在下一次请求中改变该段落;生成的 `request/header` 会记录新的前缀。 ## 已知限制与暂缓事项 diff --git a/packages/sandbox/sandbox-policy/package.json b/packages/sandbox/sandbox-policy/package.json index d5f9270ed1..6f516c222d 100644 --- a/packages/sandbox/sandbox-policy/package.json +++ b/packages/sandbox/sandbox-policy/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-sandbox-policy", - "description": "Per-call sandbox policy resolver (ctx.sandboxPolicy): deployment fallbacks plus each session's mode and workspace root, shared by every enforcing capability family", + "description": "Per-call sandbox policy resolver and current model context: deployment fallbacks plus each session's mode and workspace root, shared by every enforcing capability family", "version": "0.0.1", "private": true, "type": "module", @@ -27,18 +27,22 @@ ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", + "@deepseek-ai/dsh-system-prompt": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-sandbox": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/sandbox/sandbox-policy/src/index.ts b/packages/sandbox/sandbox-policy/src/index.ts index 1f5ba0bb00..8d932ace99 100644 --- a/packages/sandbox/sandbox-policy/src/index.ts +++ b/packages/sandbox/sandbox-policy/src/index.ts @@ -3,6 +3,9 @@ * deployment's sandbox fallbacks plus per-session resolution: the file-effect * {@link SandboxMode}, the `workspace-write` root, and the override kit (the * `sandbox/mode` event, its fold, and its write path, from `./session-mode.ts`). + * Before each agent request, the owner also renders the resolved policy as the + * `sandbox:policy` system section; request headers therefore reconstruct the + * same mode and roots the enforcing consumers resolve. * * Both enforcing capability families read the SAME policy here: the sandboxed * bash executor (`@deepseek-ai/dsh-bash-sandbox`) and the sandboxed filesystem @@ -17,8 +20,10 @@ import { resolve as resolvePath } from 'node:path' import { Context, Service } from 'cordis' import z from 'schemastery' -import { canonicalPath, type SandboxExecutionPolicy, type SandboxMode } from '@deepseek-ai/dsh-sandbox' +import type {} from '@deepseek-ai/dsh-agent' +import { canonicalPath, writableRoots, type SandboxExecutionPolicy, type SandboxMode } from '@deepseek-ai/dsh-sandbox' import type { Session } from '@deepseek-ai/dsh-session' +import type {} from '@deepseek-ai/dsh-system-prompt' import { effectiveSandboxMode } from './session-mode.ts' export { SANDBOX_MODES, effectiveSandboxMode, setSandboxMode } from './session-mode.ts' @@ -28,6 +33,23 @@ function resolveWorkspaceRoot(path: string): string { return resolvePath(canonicalPath(path)) } +/** Render the current file-effect policy without claiming host or backend capabilities. */ +function renderPolicyContext(policy: SandboxExecutionPolicy): string { + switch (policy.mode) { + case 'read-only': + return 'Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied; required sinks such as `/dev/null` may remain writable. Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access.' + case 'workspace-write': + return `Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: ${writableRoots(policy).map(root => JSON.stringify(root)).join(', ')}. Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access.` + case 'danger-full-access': + return 'Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access.' + /* v8 ignore next 4 -- SandboxMode is a typed same-process closed union; this branch is only the static exhaustiveness guard. */ + default: { + const mode: never = policy.mode + throw new Error(`unreachable sandbox mode: ${String(mode)}`) + } + } +} + declare module 'cordis' { interface Context { sandboxPolicy: SandboxPolicyService @@ -61,9 +83,9 @@ export interface SandboxPolicyRequest { /** * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment - * default mode and fallback workspace root. Tool layers call {@link resolve} - * for each execution so a session's mode log and immutable cwd travel together - * to every enforcing capability. + * default mode, fallback workspace root, and current request-time policy + * section. Tool layers call {@link resolve} for each execution so a session's + * mode log and immutable cwd travel together to every enforcing capability. */ export class SandboxPolicyService extends Service { // Inline schema call: the config catalog walks `static Config` statically. @@ -86,6 +108,17 @@ export class SandboxPolicyService extends Service { // the process cwd is real branching, resolved absolute either way. this.defaultMode = config.mode as SandboxMode this.workspaceRoot = resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd()) + + ctx.inject(['systemPrompt'], (scope: Context) => { + scope.systemPrompt.section({ + name: 'sandbox:policy', + order: 110, + text: (context) => { + const session = context.agent?.session + return session === undefined ? '' : renderPolicyContext(this.resolve({ session })) + }, + }) + }) } /** diff --git a/packages/sandbox/sandbox-policy/tests/policy.spec.ts b/packages/sandbox/sandbox-policy/tests/policy.spec.ts index 63ca0cd3d5..ea7a1c4b32 100644 --- a/packages/sandbox/sandbox-policy/tests/policy.spec.ts +++ b/packages/sandbox/sandbox-policy/tests/policy.spec.ts @@ -9,8 +9,11 @@ import { tmpdir } from 'node:os' import { join, resolve, sep } from 'node:path' import { describe, expect, it } from 'vitest' import { Context } from 'cordis' +import type { Agent } from '@deepseek-ai/dsh-agent' +import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox' import { Session, SessionId } from '@deepseek-ai/dsh-session' import SandboxPolicyService, { SANDBOX_MODES, effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy' +import SystemPrompt from '@deepseek-ai/dsh-system-prompt' async function mounted(config: { mode?: 'read-only' | 'workspace-write' | 'danger-full-access'; workspaceRoot?: string } = {}) { const ctx = new Context() @@ -28,6 +31,15 @@ function session(id: string, cwd?: string): Session { }) } +function agentFor(activeSession: Session): Agent { + return { session: activeSession } as unknown as Agent +} + +async function policySection(ctx: Context, activeSession: Session): Promise { + return (await ctx.systemPrompt.assemble({ agent: agentFor(activeSession) })) + .sections.find(section => section.name === 'sandbox:policy')?.text +} + describe('SandboxPolicyService', () => { it('defaults to read-only under the process cwd', async () => { const ctx = await mounted() @@ -116,10 +128,70 @@ describe('SandboxPolicyService', () => { it('unregisters cleanly from a child fiber (HMR safety)', async () => { const ctx = new Context() + await ctx.plugin(SystemPrompt) const fiber = await ctx.plugin(SandboxPolicyService, {}) expect(ctx.sandboxPolicy).toBeDefined() + expect(await policySection(ctx, session('sess-hmr'))).toContain('read-only') await fiber.dispose() expect(ctx.get('sandboxPolicy')).toBeUndefined() + expect((await ctx.systemPrompt.assemble()).sections.find(section => section.name === 'sandbox:policy')).toBeUndefined() + }) +}) + +describe('sandbox:policy request context', () => { + async function promptMounted(config: { mode?: 'read-only' | 'workspace-write' | 'danger-full-access'; workspaceRoot?: string } = {}): Promise { + const ctx = new Context() + await ctx.plugin(SystemPrompt) + await ctx.plugin(SandboxPolicyService, config) + return ctx + } + + it('states the fresh read-only consequences before a tool attempt', async () => { + const ctx = await promptMounted() + const text = await policySection(ctx, session('sess-read-only', '/projects/read-only')) + expect(text).toBe('Current DSH file sandbox policy: read-only. Ordinary file writes, edits, and file-mutating shell effects are denied; required sinks such as `/dev/null` may remain writable. Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access.') + }) + + it('states canonical workspace and temporary roots under workspace-write', async () => { + const ctx = await promptMounted({ mode: 'workspace-write', workspaceRoot: '/fallback' }) + const active = session('sess-workspace-write', '/projects/../projects/current') + const policy = ctx.sandboxPolicy.resolve({ session: active }) + const roots = writableRoots(policy) + const text = await policySection(ctx, active) + expect(text).toBe(`Current DSH file sandbox policy: workspace-write. File writes, edits, and file-mutating shell effects are limited to these canonical writable roots: ${roots.map(root => JSON.stringify(root)).join(', ')}. Host OS permissions and sandbox-backend availability may restrict operations further. This policy does not govern network or process access.`) + expect(roots[0]).toBe(resolve('/projects/current')) + expect(roots).toContain(canonicalPath('/tmp')) + }) + + it('states that danger-full-access adds no DSH file restriction without claiming wider authority', async () => { + const ctx = await promptMounted({ mode: 'danger-full-access' }) + const text = await policySection(ctx, session('sess-danger', '/projects/current')) + expect(text).toBe('Current DSH file sandbox policy: danger-full-access. The DSH file sandbox does not restrict file operations. Host OS permissions and other policies still apply. This policy does not govern network or process access.') + }) + + it('reflects the latest durable switch on the next assembly and stays byte-stable otherwise', async () => { + const ctx = await promptMounted() + const active = session('sess-switch', '/projects/current') + const first = await policySection(ctx, active) + expect(await policySection(ctx, active)).toBe(first) + + setSandboxMode(active, 'danger-full-access') + const danger = await policySection(ctx, active) + expect(danger).toContain('does not restrict file operations') + expect(await policySection(ctx, active)).toBe(danger) + + setSandboxMode(active, 'workspace-write') + expect(await policySection(ctx, active)).toContain(JSON.stringify(resolve('/projects/current'))) + }) + + it('reconstructs resumed policy from the session log and omits diagnostics without an agent', async () => { + const active = session('sess-resume', '/projects/current') + setSandboxMode(active, 'workspace-write') + const resumed = new Session(active.id, active.events, active.header) + const ctx = await promptMounted({ mode: 'read-only' }) + + expect(await policySection(ctx, resumed)).toContain('workspace-write') + expect((await ctx.systemPrompt.assemble()).sections.find(section => section.name === 'sandbox:policy')?.text).toBe('') }) }) diff --git a/packages/sandbox/sandbox-policy/tsconfig.json b/packages/sandbox/sandbox-policy/tsconfig.json index cb6fc623d0..c7081ad9be 100644 --- a/packages/sandbox/sandbox-policy/tsconfig.json +++ b/packages/sandbox/sandbox-policy/tsconfig.json @@ -17,12 +17,18 @@ { "path": "../../../vendor/schemastery" }, + { + "path": "../../core/agent" + }, { "path": "../sandbox" }, { "path": "../../core/session" }, + { + "path": "../../core/system-prompt" + }, { "path": "../../support/invariants" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61930c815d..1bdbe907c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3557,6 +3557,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants @@ -3566,6 +3569,9 @@ importers: '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 28ceeca2ad..0e34ee083c 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -90,7 +90,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/subprocess/subprocess': { kind: 'indirect', reason: 'The seam delegates all model rendering to consumer seams such as the bash executor family.' }, 'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' }, 'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' }, - 'packages/sandbox/sandbox-policy': { kind: 'indirect', reason: 'The policy service holds the mode dsh-tool-bash and dsh-tool-fs render in their denial markers.' }, 'packages/sdk/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' }, 'packages/sdk/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' }, 'packages/sdk/scripts': { kind: 'indirect', reason: 'The launcher delegates model context to the loaded project plugin tree.' }, diff --git a/tsconfig.host.json b/tsconfig.host.json index 7cd27c954c..e62bcdbf43 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -26,6 +26,7 @@ "apps/web/tests/message-actions.e2e.ts", "apps/web/tests/queue-actions.e2e.ts", "apps/web/tests/skill-invocation-policy.e2e.ts", + "apps/web/tests/permission-policy-context.e2e.ts", "apps/cli/tests/**/*.ts", "examples/*/src/**/*.ts", "examples/*/start.ts",