@deepseek-ai/dsh-sandbox-policy 0.1.1-rc.2 → 0.1.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/sandbox/sandbox-policy/README.md
5
- README.md: c85420019b490d311360f310c04212ac6cd737d1
6
- README.zh.md: 46e1deea680122622e58490064dcc4ea6699d1d3
5
+ README.md: 2a6ef9be4b38e65ace8d0ae1586542f0872dfc69
6
+ README.zh.md: 87c6149b7590cb39e4265ca4b8964df6cf2e91ad
package/README.md CHANGED
@@ -1,33 +1,110 @@
1
- # dsh-sandbox-policy — the sandbox policy home (`ctx.sandboxPolicy`)
1
+ ---
2
+ description: "The shared per-call sandbox policy resolver and current model context for users and maintainers composing, configuring, or debugging file-effect policy across enforcing capabilities."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-sandbox-policy
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- 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 receives one resolved mode-and-root policy per call; before each request, the model receives the current policy without a separate capability inventory.
10
+ ## Summary
11
+
12
+ `dsh-sandbox-policy` resolves the file-effect mode and workspace root for every confined capability call from one shared policy home, and tells the model the current policy before each request. A deployment sets a default mode and a fallback workspace root; a session can switch its own mode, and the switch survives restart because it lives in the session log. Every enforcing capability — bash, filesystem, terminal — reads the same resolved policy, so the mode a call runs under never depends on which family resolved it. The model sees one concise `sandbox:policy` contribution naming the mode and workspace, without a separate inventory of mounted capabilities.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount this package in any composition where sandbox-enforcing capabilities run: it owns the deployment default and the per-session overrides those capabilities consume, and it contributes the current policy to the model's runtime-context snapshot.
29
+
30
+ ### When to choose it
31
+
32
+ Choose it for every composition with confined capabilities (bash, filesystem, terminal) so one policy home keeps them from drifting into different modes or workspace roots. Skip it only when nothing enforces sandbox policy — with no consumers, the resolved policy has no effect.
33
+
34
+ ### Minimal configuration
35
+
36
+ Load the package with a default mode; the fail-safe default is `read-only`, and a deployment that wants a workspace-writable agent opts into `workspace-write` explicitly.
37
+
38
+ ```yaml
39
+ - name: '@deepseek-ai/dsh-sandbox-policy'
40
+ config:
41
+ mode: workspace-write
42
+ workspaceRoot: /absolute/path/to/workspace
43
+ ```
44
+
45
+ | Field | Default | Meaning |
46
+ |---|---|---|
47
+ | `mode` | `read-only` | The deployment default mode a session starts from, validated at load |
48
+ | `workspaceRoot` | `process.cwd()` | The fallback root `workspace-write` may write under for agentless calls or sessions without a cwd; normal agent calls use the session's immutable cwd instead |
49
+
50
+ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-sandbox-policy) is the exhaustive source for every accepted field and its JSDoc.
51
+
52
+ ### Switching a session's mode
53
+
54
+ A session's mode can be switched at runtime through a UI policy control or an explicit switch; the switch is recorded in the session log and takes effect on the session's next confined call. The switch survives restart through replay, and each session keeps its own mode — two sessions never see each other's state. A switched session keeps its immutable workspace cwd as the writable boundary.
55
+
56
+ ### Failures and recovery
6
57
 
7
- ## Why a shared home
58
+ An invalid configured mode is rejected when the plugin loads, so a typo fails loud instead of silently changing policy. A session without a cwd, and agentless calls, fall back to the configured workspace root; a call with an approved explicit mode uses that mode for exactly that call.
8
59
 
9
- Filesystem tools, one-shot bash commands, and terminal sessions may enforce the same mode vocabulary in different combinations. If each resolved its own `mode` + `workspaceRoot`, they could drift into a split world, exactly what [the sandbox Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) warns against. Each enforcing backend consumes the complete owner-resolved policy, while the current context describes only what that policy means for any available operation the DSH file sandbox enforces. The [cross-family fs sandbox Agent Note](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) records the shared-policy decision.
60
+ -----
10
61
 
11
- ## Config
62
+ <a id="understand-the-implementation"></a>
63
+ ## Understand the implementation
12
64
 
13
- - `mode` — the deployment default `SandboxMode` (`read-only` / `workspace-write` / `danger-full-access`), validated at load. Default `read-only` (fail-safe).
14
- - `workspaceRoot` — the fallback directory `workspace-write` may write under for agentless calls or sessions without a cwd. Default `process.cwd()`, resolved to its absolute filesystem identity either way. A normal agent call uses its session header's immutable `cwd` instead.
65
+ <details>
66
+ <summary>Implementation internals — click to expand</summary>
15
67
 
16
- ## API
68
+ This section explains policy resolution, the per-session store, and the model-visible contribution; the observable behavior is fully covered in [Use this package](#use-this-package).
17
69
 
18
- - `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.
19
- - `ctx.sandboxPolicy.defaultMode` / `ctx.sandboxPolicy.workspaceRoot` — the deployment default and fallback root used by `resolve()`.
20
- - `sandbox:policy` — a request-time cache-safe context contribution derived directly from `resolve({ session })`. It states the mode's capability-neutral file-effect contract and the canonical session workspace under `workspace-write`; tool owners retain operation-specific denial and escalation guidance.
21
- - `effectiveSandboxMode(events)` — the pure fold of a session's `sandbox/mode` events (the last switch wins, or `undefined`), used inside `resolve()`.
22
- - `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.
23
- - `SANDBOX_MODES` — every mode, for option advertisement and runtime validation.
70
+ ### Resolution precedence
24
71
 
25
- 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 agent loop logs the assembled full runtime-context snapshot as a sourced `user/message`, so exact policy input remains reconstructable without an in-memory “last told” mirror.
72
+ `resolve({ session, mode })` returns one complete per-call policy: an approved explicit mode outranks the session's last `sandbox/mode` event, which outranks the deployment default. The session's immutable `cwd` is canonicalized with filesystem semantics before becoming the workspace root, so `symlink/..` agrees with process working-directory resolution; otherwise the configured fallback applies.
26
73
 
27
- ## The per-session store
74
+ ### The per-session store
28
75
 
29
- 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; before the next request, the owner contributes the current fact to the full runtime-context snapshot.
76
+ A runtime switch is one log-only `sandbox/mode` event on the session it applies to — the switch IS its event, and nothing mutates mode state out of band. `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 needs no event: the immutable `SessionHeader.cwd` recorded at creation is the root for every call in that session. The event stays log-only; before each request, the owner contributes the current fact to the full runtime-context snapshot, and the agent loop logs that snapshot as a sourced `user/message`.
30
77
 
78
+ ### Model-visible text
79
+
80
+ The `sandbox:policy` contribution states the mode's capability-neutral file-effect contract and the canonical session workspace under `workspace-write`. It does not enumerate mounted capabilities; tool plugins retain operation-specific denial and escalation guidance, approval policy contributes separately to the same snapshot, and plan guidance remains `dsh-plan-mode`'s system section. The optional `./invariant` companion rejects a forged durable `sandbox/mode` event whose value falls outside the closed mode vocabulary.
81
+
82
+ ### Source map
83
+
84
+ | File | Role |
85
+ |---|---|
86
+ | [`src/index.ts`](src/index.ts) | Plugin entry: `SandboxPolicyService`, `Config` schema, policy resolution and context contribution |
87
+ | [`src/session-mode.ts`](src/session-mode.ts) | The `sandbox/mode` event, its fold, and the write path |
88
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion: rejects `sandbox/mode` values outside the closed vocabulary |
89
+
90
+ </details>
91
+
92
+ -----
93
+
94
+ <a id="further-exploration"></a>
95
+ ## Further Exploration
96
+
97
+ Start with the subsystem reference for the shared vocabulary, then the seam contract, the cross-family decision, and the model-context decision.
98
+
99
+ - [Process sandbox subsystem](../../../docs/subsystems/sandbox.md) — modes, per-call policy, and enforcement semantics.
100
+ - [Sandbox seam package](../sandbox/README.md) — the confinement contract every enforcing capability implements.
101
+ - [Cross-family file sandbox decision](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) — why one shared policy home exists.
102
+ - [Current sandbox policy context decision](../../../.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md) — how the policy reaches the model before each request.
103
+ - [Capability-neutral policy context decision](../../../.agents/notes/implemented/simplification/2026-07-31-capability-neutral-sandbox-policy-context.md) — why the contribution names no mounted capabilities.
104
+
105
+ -----
106
+
107
+ <a id="model-experience"></a>
31
108
  ## Model Experience
32
109
 
33
110
  ### Current file sandbox policy
@@ -64,6 +141,21 @@ The stable system prompt remains byte-identical across mode changes. A changed f
64
141
 
65
142
  ## Known Limitations and Deferred Work
66
143
 
144
+ <a id="known-limitations-and-deferred-work"></a>
145
+
146
+
147
+ These limits define the policy surface this package provides. They are current package constraints, not a general sandbox comparison or a task backlog.
148
+
67
149
  - **One primary workspace root per session** — policy resolves `SessionHeader.cwd`; extra writable roots are not part of `SandboxExecutionPolicy`.
68
150
  - **File-effect modes only** — `SandboxMode` governs file effects; network and process policy are outside its vocabulary, so no knob here restricts them.
69
151
  - **Temporary areas are deliberately summarized** — enforcing backends grant different platform temporary areas, which are selected after policy resolution and therefore cannot be enumerated truthfully in the current context.
152
+
153
+ <a id="dev-note"></a>
154
+ ### Dev Note
155
+
156
+ <details>
157
+ <summary>Working context for maintainers — click to expand</summary>
158
+
159
+ None.
160
+
161
+ </details>
package/README.zh.md CHANGED
@@ -1,40 +1,117 @@
1
- # dsh-sandbox-policy:沙箱策略归属位置(`ctx.sandboxPolicy`)
1
+ ---
2
+ description: "面向组合、配置或排查跨执行能力文件效果策略的用户与维护者的共享逐调用沙箱策略解析器与当前模型上下文。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-sandbox-policy
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- 沙箱策略解析的唯一归属位置:部署默认 [`SandboxMode`](../sandbox/README.zh.md) 与回退根目录,加上每个会话的持久模式覆盖和不可变工作区根目录。每项负责强制执行的能力在每次调用时都会收到一项解析完成的模式与根目录策略;模型在每次请求前会收到当前策略,而不会另收一份能力清单。
10
+ ## 概述
11
+
12
+ `dsh-sandbox-policy` 为每次受限能力调用从统一的策略归属位置解析文件效果模式与工作区根目录,并在每次请求前把当前策略告知模型。部署方设置默认模式与回退工作区根目录;会话可以切换自己的模式,切换因存在于会话日志中而跨重启保留。每个强制执行能力——bash、文件系统、终端——读取同一份解析出的策略,因此调用运行的模式绝不取决于由哪个家族解析。模型会看到一条简洁的 `sandbox:policy` 贡献,指明模式与工作区,而不会收到一份已挂载能力的清单。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 在任何运行沙箱强制执行能力的组合中挂载此包:它拥有这些能力消费的部署默认值与逐会话覆盖,并把当前策略贡献给模型的运行时上下文快照。
29
+
30
+ ### 何时选择
31
+
32
+ 为每个带受限能力(bash、文件系统、终端)的组合选择它,让单一策略归属位置防止它们漂移到不同的模式或工作区根目录。只有没有任何沙箱策略强制执行时才跳过它——没有消费方时,解析出的策略不起作用。
33
+
34
+ ### 最小配置
35
+
36
+ 用默认模式加载本包;故障安全默认值是 `read-only`,想要可写工作区 agent 的部署需要显式选择 `workspace-write`。
37
+
38
+ ```yaml
39
+ - name: '@deepseek-ai/dsh-sandbox-policy'
40
+ config:
41
+ mode: workspace-write
42
+ workspaceRoot: /absolute/path/to/workspace
43
+ ```
44
+
45
+ | 字段 | 默认值 | 含义 |
46
+ |---|---|---|
47
+ | `mode` | `read-only` | 会话起始的部署默认模式,加载时验证 |
48
+ | `workspaceRoot` | `process.cwd()` | 无 agent(智能体)调用或没有 cwd 的会话在 `workspace-write` 下可写入的回退根目录;普通 agent 调用改用会话的不可变 cwd |
49
+
50
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-sandbox-policy)是每个受支持字段及其 JSDoc 的穷尽式真源。
51
+
52
+ ### 切换会话模式
53
+
54
+ 会话的模式可以在运行时通过 UI 策略控件或显式切换来更改;切换记录在会话日志中,并在该会话的下一次受限调用时生效。切换通过回放跨重启保留,每个会话保持自己的模式——两个会话绝不会看到彼此状态。切换后的会话继续以不可变的工作区 cwd 作为写入边界。
55
+
56
+ ### 失败与恢复
6
57
 
7
- ## 为何需要共享归属位置
58
+ 无效的配置模式会在插件加载时被拒绝,因此拼写错误会大声失败,而不是静默改变策略。没有 cwd 的会话与无 agent 调用回退到配置的工作区根目录;带已批准显式模式的调用只在该次调用中使用该模式。
8
59
 
9
- 文件系统工具、一次性 bash 命令和终端会话可以用不同组合强制执行同一套模式词汇。如果各自解析 `mode` + `workspaceRoot`,就可能漂移成分裂世界,正是[沙箱 Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md)所警告的情况。每个强制执行后端都会消费归属方解析出的完整策略,而当前上下文只说明该策略对于任何受 DSH 文件沙箱强制执行的可用操作有何含义。[跨家族 fs 沙箱 Agent Note](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md)记录了共享策略决策。
60
+ -----
10
61
 
11
- ## 配置
62
+ <a id="understand-the-implementation"></a>
63
+ ## 理解实现
12
64
 
13
- - `mode`:部署默认 `SandboxMode`(`read-only`/`workspace-write`/`danger-full-access`),加载时验证。默认为 `read-only`(故障安全)。
14
- - `workspaceRoot`:无 agent(智能体)的调用或没有 cwd 的会话在 `workspace-write` 下可写入的回退目录。默认为 `process.cwd()`;无论显式配置还是采用默认值,都会解析为其绝对文件系统标识。普通 agent 调用改用其会话头中不可变的 `cwd`。
65
+ <details>
66
+ <summary>实现细节——点击展开</summary>
15
67
 
16
- ## 接口
68
+ 本节解释策略解析、逐会话存储与模型可见贡献;可观察行为已在[使用本包](#use-this-package)中完整说明。
17
69
 
18
- - `ctx.sandboxPolicy.resolve({ session?, mode? })`:解析一项完整的逐调用策略。显式批准的模式优先于会话最后一条 `sandbox/mode` 事件,后者又优先于 `defaultMode`;会话不可变的 `cwd` 会先按文件系统语义规范化,再成为 `workspaceRoot`,否则使用配置的回退值。规范化先于词法归一化,因此 `symlink/..` 与进程工作目录解析保持一致。
19
- - `ctx.sandboxPolicy.defaultMode`/`ctx.sandboxPolicy.workspaceRoot`:`resolve()` 使用的部署默认值与回退根目录。
20
- - `sandbox:policy`:直接派生自 `resolve({ session })` 的请求时缓存安全上下文贡献。它说明该模式中与具体能力无关的文件操作约定,以及 `workspace-write` 下规范化的会话工作区;工具归属方仍负责特定于操作的拒绝与升权引导。
21
- - `effectiveSandboxMode(events)`:会话 `sandbox/mode` 事件的纯 fold(最后一次切换胜出,没有则为 `undefined`),在 `resolve()` 内使用。
22
- - `setSandboxMode(session, mode)`:逐会话覆盖的唯一写入路径:恰好追加一条 `sandbox/mode` 事件。切换本身就是事件;不会在带外修改模式。
23
- - `SANDBOX_MODES`:所有模式,用于选项展示与运行时验证。
70
+ ### 解析优先级
24
71
 
25
- 可选的 `./invariant` 配套组件会拒绝伪造的持久 `sandbox/mode` 事件,只要其值不在该封闭词汇中;Session 与其配套组件负责相关存储与核心执行封闭规则。agent loop(智能体循环)会将组装后的完整运行时上下文快照记录为一条带来源的 `user/message`,因此无需内存中的「上次告知」镜像,也能重建确切的策略输入。
72
+ `resolve({ session, mode })` 返回一份完整的逐调用策略:已批准的显式模式优先于会话最后一条 `sandbox/mode` 事件,后者又优先于部署默认值。会话的不可变 `cwd` 先按文件系统语义规范化,再成为工作区根目录,因此 `symlink/..` 与进程工作目录解析一致;否则使用配置的回退值。
26
73
 
27
- ## 逐会话存储
74
+ ### 逐会话存储
28
75
 
29
- 运行时切换是在对应会话日志中追加的一条 `sandbox/mode` 事件。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖会通过回放跨重启保留,两个会话也绝不会看到彼此状态。工作区标识无需另一条事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。该事件仍只进入日志;在下一次请求前,归属方会将当前事实贡献给完整运行时上下文快照。
76
+ 运行时切换是在对应会话日志中追加的一条仅记录 `sandbox/mode` 事件——切换本身就是事件,任何机制都不会在带外修改模式状态。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖通过回放跨重启保留,两个会话也绝不会看到彼此状态。工作区标识无需事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。事件仍只进入日志;在每次请求前,归属方会把当前事实贡献给完整运行时上下文快照,agent loop(智能体循环)将该快照记录为一条带来源的 `user/message`。
30
77
 
78
+ ### 模型可见文本
79
+
80
+ `sandbox:policy` 贡献说明模式的与具体能力无关的文件操作约定,以及 `workspace-write` 下规范化的会话工作区。它不枚举已挂载能力;工具插件保留特定于操作的拒绝与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。可选的 `./invariant` 配套组件会拒绝值超出封闭模式词汇的伪造持久 `sandbox/mode` 事件。
81
+
82
+ ### 源码地图
83
+
84
+ | 文件 | 职责 |
85
+ |---|---|
86
+ | [`src/index.ts`](src/index.ts) | 插件入口:`SandboxPolicyService`、`Config` schema、策略解析与上下文贡献 |
87
+ | [`src/session-mode.ts`](src/session-mode.ts) | `sandbox/mode` 事件、其 fold 与写入路径 |
88
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:拒绝超出封闭词汇的 `sandbox/mode` 值 |
89
+
90
+ </details>
91
+
92
+ -----
93
+
94
+ <a id="further-exploration"></a>
95
+ ## 进一步探索
96
+
97
+ 先从子系统参考文档了解共享词汇,再看 seam 约定、跨家族决策与模型上下文决策。
98
+
99
+ - [进程沙箱子系统](../../../docs/subsystems/sandbox.zh.md)——模式、逐调用策略与强制执行语义。
100
+ - [沙箱 seam 包](../sandbox/README.zh.md)——每个强制执行能力实现的隔离约定。
101
+ - [跨家族文件沙箱决策](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md)——为何存在统一的共享策略归属位置。
102
+ - [当前沙箱策略上下文决策](../../../.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md)——策略如何在每次请求前到达模型。
103
+ - [与具体能力无关的策略上下文决策](../../../.agents/notes/implemented/simplification/2026-07-31-capability-neutral-sandbox-policy-context.zh.md)——为何贡献不点名已挂载能力。
104
+
105
+ -----
106
+
107
+ <a id="model-experience"></a>
31
108
  ## 模型体验
32
109
 
33
110
  ### 当前文件沙箱策略
34
111
 
35
- #### 模型看到的内容
112
+ #### 模型看到什么
36
113
 
37
- 每个 agent 会话的当前运行时上下文快照中都有一项 `sandbox:policy` 贡献。它不枚举已装载的能力。工具插件继续负责操作与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。
114
+ 每个 agent 会话的当前运行时上下文快照中都有一项 `sandbox:policy` 贡献。它不枚举已挂载的能力。工具插件继续负责操作与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。
38
115
 
39
116
  ##### 只读
40
117
 
@@ -62,8 +139,23 @@ Current DSH file policy: danger-full-access. The DSH file sandbox does not restr
62
139
 
63
140
  模式切换时,稳定的系统提示词仍逐字节相同。变化后的完整上下文快照会追加到保留的历史之后,从而保留此前已缓存的前缀;后续未变化的请求会复用该保留快照。
64
141
 
65
- ## 已知限制与暂缓事项
142
+ ## 已知限制与延期工作
143
+
144
+ <a id="known-limitations-and-deferred-work"></a>
145
+
146
+
147
+ 这些限制定义了本包提供的策略表面。它们是当前包约束,不是通用沙箱对比或任务积压。
148
+
149
+ - **每个会话只有一个主要工作区根目录**——策略解析 `SessionHeader.cwd`;额外可写根目录不属于 `SandboxExecutionPolicy`。
150
+ - **仅限文件操作模式**——`SandboxMode` 管控文件操作;网络和进程策略不在其词汇中,因此这里没有限制它们的旋钮。
151
+ - **有意概述临时区域**——强制执行后端会授予不同的平台临时区域,这些区域在策略解析后才会选定,因此无法在当前上下文中如实枚举。
152
+
153
+ <a id="dev-note"></a>
154
+ ### 开发备注
155
+
156
+ <details>
157
+ <summary>维护者的工作上下文——点击展开</summary>
158
+
159
+ 无。
66
160
 
67
- - **每个会话只有一个主要工作区根目录**:策略解析 `SessionHeader.cwd`;额外可写根目录不属于 `SandboxExecutionPolicy`。
68
- - **仅限文件操作模式**:`SandboxMode` 管控文件操作;网络和进程策略不在其词汇中,因此这里没有限制它们的旋钮。
69
- - **有意概述临时区域**:强制执行后端会授予不同的平台临时区域,这些区域在策略解析后才会选定,因此无法在当前上下文中如实枚举。
161
+ </details>
package/lib/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { resolve } from "node:path";
2
2
  import { Service } from "@deepseek-ai/cordis";
3
- import z from "@deepseek-ai/schemastery";
3
+ import { z } from "zod";
4
+ import z$1 from "@deepseek-ai/schemastery";
4
5
  import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
5
6
  //#region lib/types/session-mode.js
6
7
  /**
7
8
  * Per-session sandbox-mode override: the session log as the store. A runtime
8
9
  * switch (a UI policy control or test scenario) is recorded as one
9
10
  * `sandbox/mode` event on the session it applies to;
10
- * `effective = fold(events) ?? the deployment default`, so an override
11
+ * `effective = projection state ?? the deployment default`, so an override
11
12
  * survives restart by replay, two sessions can never see each other's state,
12
13
  * and there is no external config store. The event is log-only (the
13
14
  * `approval/*` precedent): the policy owner projects the fold into each model
@@ -29,24 +30,10 @@ const SANDBOX_MODES = [
29
30
  "danger-full-access"
30
31
  ];
31
32
  /**
32
- * The session's sandbox-mode override: the last `sandbox/mode` event in the
33
- * log, or undefined when the session never switched (callers apply the
34
- * deployment default). The pure fold — resume needs no catch-up machinery
35
- * because replaying the log IS the state.
36
- * @param events - session events in log order (other event types are skipped).
37
- * @returns the mode of the last switch event, or undefined without one.
38
- */
39
- function effectiveSandboxMode(events) {
40
- for (let index = events.length - 1; index >= 0; index -= 1) {
41
- const event = events[index];
42
- if (event.type === "sandbox/mode") return event.data.mode;
43
- }
44
- }
45
- /**
46
33
  * THE write path for a session's sandbox-mode override: appends exactly one
47
34
  * `sandbox/mode` event — the switch IS its event; nothing mutates mode state
48
35
  * out of band. Takes effect on the session's next confined call (bash or fs)
49
- * — the consumers fold on every read.
36
+ * — consumers read the shared projection state.
50
37
  * @param session - the session the override belongs to.
51
38
  * @param mode - the mode every subsequent confined call in this session runs
52
39
  * under (until the next switch).
@@ -60,7 +47,9 @@ function setSandboxMode(session, mode) {
60
47
  * The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
61
48
  * deployment's sandbox fallbacks plus per-session resolution: the file-effect
62
49
  * {@link SandboxMode}, the `workspace-write` root, and the override kit (the
63
- * `sandbox/mode` event, its fold, and its write path, from `./session-mode.ts`).
50
+ * `sandbox/mode` event, its fold, and its write path; the fold is the
51
+ * `sandboxMode` session-projection unit registered here, while the event and
52
+ * write path come from `./session-mode.ts`).
64
53
  * Before each agent request, the owner also contributes the resolved policy to
65
54
  * the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
66
55
  * model history, so replay reconstructs the same mode and root the enforcing
@@ -92,6 +81,12 @@ function renderPolicyContext(policy) {
92
81
  }
93
82
  }
94
83
  }
84
+ /** The sandbox-mode projection's state schema (state equals the public shape). */
85
+ const sandboxModeStateSchema = z.union([
86
+ z.literal("read-only"),
87
+ z.literal("workspace-write"),
88
+ z.literal("danger-full-access")
89
+ ]).nullable();
95
90
  /**
96
91
  * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
97
92
  * default mode, fallback workspace root, and current request-time policy
@@ -99,14 +94,15 @@ function renderPolicyContext(policy) {
99
94
  * mode log and immutable cwd travel together to every enforcing capability.
100
95
  */
101
96
  var SandboxPolicyService = class extends Service {
102
- static Config = z.object({
103
- mode: z.union([
97
+ static Config = z$1.object({
98
+ mode: z$1.union([
104
99
  "read-only",
105
100
  "workspace-write",
106
101
  "danger-full-access"
107
102
  ]).default("read-only"),
108
- workspaceRoot: z.string()
103
+ workspaceRoot: z$1.string()
109
104
  });
105
+ static inject = ["sessionProjections"];
110
106
  /** The deployment default mode — the fallback beneath a session override. */
111
107
  defaultMode;
112
108
  /** The absolute `workspace-write` fallback root for calls without a session cwd. */
@@ -115,10 +111,17 @@ var SandboxPolicyService = class extends Service {
115
111
  super(ctx, "sandboxPolicy");
116
112
  this.defaultMode = config.mode;
117
113
  this.workspaceRoot = resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd());
114
+ ctx.sessionProjections.register({
115
+ key: "sandboxMode",
116
+ stateVersion: 1,
117
+ stateSchema: sandboxModeStateSchema,
118
+ init: () => null,
119
+ apply: (state, event) => event.type === "sandbox/mode" ? event.data.mode : state
120
+ });
118
121
  ctx.inject(["systemPrompt"], (scope) => {
119
122
  scope.systemPrompt.context({
120
123
  name: "sandbox:policy",
121
- order: 110,
124
+ order: scope.systemPrompt.getContextOrder("SANDBOX_POLICY"),
122
125
  text: (context) => {
123
126
  const session = context.agent?.session;
124
127
  return session === void 0 ? "" : renderPolicyContext(this.resolve({ session }));
@@ -149,8 +152,8 @@ var SandboxPolicyService = class extends Service {
149
152
  * @returns the last logged mode, or `undefined` without one.
150
153
  */
151
154
  overrideOf(session) {
152
- return effectiveSandboxMode(session.events);
155
+ return this.ctx.sessionProjections.stateOf(session, "sandboxMode") ?? void 0;
153
156
  }
154
157
  };
155
158
  //#endregion
156
- export { SANDBOX_MODES, SandboxPolicyService, SandboxPolicyService as default, effectiveSandboxMode, setSandboxMode };
159
+ export { SANDBOX_MODES, SandboxPolicyService, SandboxPolicyService as default, setSandboxMode };
package/lib/invariant.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Per-session sandbox-mode override: the session log as the store. A runtime
4
4
  * switch (a UI policy control or test scenario) is recorded as one
5
5
  * `sandbox/mode` event on the session it applies to;
6
- * `effective = fold(events) ?? the deployment default`, so an override
6
+ * `effective = projection state ?? the deployment default`, so an override
7
7
  * survives restart by replay, two sessions can never see each other's state,
8
8
  * and there is no external config store. The event is log-only (the
9
9
  * `approval/*` precedent): the policy owner projects the fold into each model
@@ -2,7 +2,9 @@
2
2
  * The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
3
3
  * deployment's sandbox fallbacks plus per-session resolution: the file-effect
4
4
  * {@link SandboxMode}, the `workspace-write` root, and the override kit (the
5
- * `sandbox/mode` event, its fold, and its write path, from `./session-mode.ts`).
5
+ * `sandbox/mode` event, its fold, and its write path; the fold is the
6
+ * `sandboxMode` session-projection unit registered here, while the event and
7
+ * write path come from `./session-mode.ts`).
6
8
  * Before each agent request, the owner also contributes the resolved policy to
7
9
  * the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
8
10
  * model history, so replay reconstructs the same mode and root the enforcing
@@ -18,10 +20,11 @@
18
20
  * @module @deepseek-ai/dsh-sandbox-policy
19
21
  */
20
22
  import { Context, Service } from '@deepseek-ai/cordis';
23
+ import { z as zod } from 'zod';
21
24
  import z from '@deepseek-ai/schemastery';
22
25
  import { type SandboxExecutionPolicy, type SandboxMode } from '@deepseek-ai/dsh-sandbox';
23
26
  import type { Session } from '@deepseek-ai/dsh-session';
24
- export { SANDBOX_MODES, effectiveSandboxMode, setSandboxMode } from './session-mode.ts';
27
+ export { SANDBOX_MODES, setSandboxMode } from './session-mode.ts';
25
28
  declare module '@deepseek-ai/cordis' {
26
29
  interface Context {
27
30
  sandboxPolicy: SandboxPolicyService;
@@ -50,6 +53,15 @@ export interface SandboxPolicyRequest {
50
53
  /** Explicit approved mode override, which outranks session policy. */
51
54
  mode?: SandboxMode;
52
55
  }
56
+ /** The sandbox-mode projection's state schema (state equals the public shape). */
57
+ declare const sandboxModeStateSchema: zod.ZodNullable<zod.ZodUnion<readonly [zod.ZodLiteral<"read-only">, zod.ZodLiteral<"workspace-write">, zod.ZodLiteral<"danger-full-access">]>>;
58
+ type SandboxModeState = zod.infer<typeof sandboxModeStateSchema>;
59
+ declare module '@deepseek-ai/dsh-session-projection/types' {
60
+ interface SessionProjectionStateMap {
61
+ /** Last logged sandbox-mode override, or null before one (deployment default applies at resolve time). */
62
+ sandboxMode: SandboxModeState;
63
+ }
64
+ }
53
65
  /**
54
66
  * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
55
67
  * default mode, fallback workspace root, and current request-time policy
@@ -58,6 +70,7 @@ export interface SandboxPolicyRequest {
58
70
  */
59
71
  export declare class SandboxPolicyService extends Service {
60
72
  static Config: z<Config>;
73
+ static inject: string[];
61
74
  /** The deployment default mode — the fallback beneath a session override. */
62
75
  readonly defaultMode: SandboxMode;
63
76
  /** The absolute `workspace-write` fallback root for calls without a session cwd. */
@@ -2,7 +2,7 @@
2
2
  * Per-session sandbox-mode override: the session log as the store. A runtime
3
3
  * switch (a UI policy control or test scenario) is recorded as one
4
4
  * `sandbox/mode` event on the session it applies to;
5
- * `effective = fold(events) ?? the deployment default`, so an override
5
+ * `effective = projection state ?? the deployment default`, so an override
6
6
  * survives restart by replay, two sessions can never see each other's state,
7
7
  * and there is no external config store. The event is log-only (the
8
8
  * `approval/*` precedent): the policy owner projects the fold into each model
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * @module dsh-sandbox-policy/session-mode
19
19
  */
20
- import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
20
+ import type { Session } from '@deepseek-ai/dsh-session';
21
21
  import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
22
22
  declare module '@deepseek-ai/dsh-session/types' {
23
23
  interface SessionEventMap {
@@ -25,7 +25,7 @@ declare module '@deepseek-ai/dsh-session/types' {
25
25
  * The session's sandbox mode was switched — log-only (like `approval/*`;
26
26
  * NOT a surface event, carries no `surfaceOp`): durable and replayable,
27
27
  * never in the model transcript. The LAST such event is the session's
28
- * override ({@link effectiveSandboxMode}). `source: 'delegation'` marks
28
+ * override (folded by the sandboxMode projection unit). `source: 'delegation'` marks
29
29
  * an override seeded into a child; an absent source is a runtime switch.
30
30
  */
31
31
  'sandbox/mode': {
@@ -37,20 +37,11 @@ declare module '@deepseek-ai/dsh-session/types' {
37
37
  }
38
38
  /** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
39
39
  export declare const SANDBOX_MODES: readonly SandboxMode[];
40
- /**
41
- * The session's sandbox-mode override: the last `sandbox/mode` event in the
42
- * log, or undefined when the session never switched (callers apply the
43
- * deployment default). The pure fold — resume needs no catch-up machinery
44
- * because replaying the log IS the state.
45
- * @param events - session events in log order (other event types are skipped).
46
- * @returns the mode of the last switch event, or undefined without one.
47
- */
48
- export declare function effectiveSandboxMode(events: readonly SessionEvent[]): SandboxMode | undefined;
49
40
  /**
50
41
  * THE write path for a session's sandbox-mode override: appends exactly one
51
42
  * `sandbox/mode` event — the switch IS its event; nothing mutates mode state
52
43
  * out of band. Takes effect on the session's next confined call (bash or fs)
53
- * — the consumers fold on every read.
44
+ * — consumers read the shared projection state.
54
45
  * @param session - the session the override belongs to.
55
46
  * @param mode - the mode every subsequent confined call in this session runs
56
47
  * under (until the next switch).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-sandbox-policy",
3
3
  "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",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,22 +32,25 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
36
- "@deepseek-ai/dsh-sandbox": "^0.1.1-rc.2",
37
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
38
- "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2",
39
- "@deepseek-ai/cordis": "^4.0.1",
40
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
35
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
36
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
37
+ "@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.2",
38
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
39
+ "@deepseek-ai/dsh-sandbox": "^0.1.2-alpha.2",
40
+ "@deepseek-ai/cordis": "^4.0.2",
41
+ "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.2"
41
42
  },
42
43
  "dependencies": {
43
- "@deepseek-ai/schemastery": "^3.18.1"
44
+ "zod": "^4.4.3",
45
+ "@deepseek-ai/schemastery": "^3.18.2"
44
46
  },
45
47
  "devDependencies": {
46
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
47
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
48
- "@deepseek-ai/dsh-sandbox": "^0.1.1-rc.2",
49
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
50
- "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2",
51
- "@deepseek-ai/cordis": "^4.0.1"
48
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
49
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
50
+ "@deepseek-ai/dsh-sandbox": "^0.1.2-alpha.2",
51
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
52
+ "@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.2",
53
+ "@deepseek-ai/cordis": "^4.0.2",
54
+ "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.2"
52
55
  }
53
56
  }