@qilin-agent/sandbox-policy 0.0.0-stage → 3.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,79 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/sandbox/sandbox-policy/README.md
5
+ /:
6
+ en: abf71058974a6f37
7
+ zh: 45c76c1d645a0e67
8
+ /qilin-sandbox-policy:
9
+ en: 983c2e8c93e8513a
10
+ zh: 4c1e0b559820f60c
11
+ /qilin-sandbox-policy/summary:
12
+ en: 62e1a0a017d6bc07
13
+ zh: 1d9f86eb54f51b8e
14
+ /qilin-sandbox-policy/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /qilin-sandbox-policy/use-this-package:
18
+ en: a75f214ba848bbd1
19
+ zh: 8ad35e2513b53bd1
20
+ /qilin-sandbox-policy/use-this-package/when-to-choose-it:
21
+ en: 6704447e52ec295b
22
+ zh: 9ed812d9e29d5533
23
+ /qilin-sandbox-policy/use-this-package/minimal-configuration:
24
+ en: 855079a846f9316b
25
+ zh: 6ff918f505f09f73
26
+ /qilin-sandbox-policy/use-this-package/switching-a-session-s-mode:
27
+ en: 957d72691e470e5c
28
+ zh: c22a3a750af0e4a3
29
+ /qilin-sandbox-policy/use-this-package/failures-and-recovery:
30
+ en: 9cef5f64a050f468
31
+ zh: 5dc42c148b2c902d
32
+ /qilin-sandbox-policy/understand-the-implementation:
33
+ en: 2d560aa6681fcfb8
34
+ zh: e1c6361a6fa40c63
35
+ /qilin-sandbox-policy/understand-the-implementation/resolution-precedence:
36
+ en: f1f38b10c23947d2
37
+ zh: 7a591e743d6793f5
38
+ /qilin-sandbox-policy/understand-the-implementation/the-per-session-store:
39
+ en: d13dabff02f2acfd
40
+ zh: f593888b4ffddf36
41
+ /qilin-sandbox-policy/understand-the-implementation/model-visible-text:
42
+ en: 38f9f9bf72d461b2
43
+ zh: ac0aa6889e14aea0
44
+ /qilin-sandbox-policy/understand-the-implementation/source-map:
45
+ en: cc7dd675c9915f7d
46
+ zh: d719c4f9de39873a
47
+ /qilin-sandbox-policy/further-exploration:
48
+ en: e00314ec4e49f003
49
+ zh: 0285b8448cdf6bf1
50
+ /qilin-sandbox-policy/model-experience:
51
+ en: 215e7ba838619b7b
52
+ zh: a311da3843709f90
53
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy:
54
+ en: 593b59dbbb02bc8c
55
+ zh: 38fa746346e9bc06
56
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy/what-the-model-sees:
57
+ en: 37d30c86d074bb17
58
+ zh: 428399379be74def
59
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy/what-the-model-sees/read-only:
60
+ en: 36f15579c9444eb9
61
+ zh: 289f3c4ce040bfe9
62
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy/what-the-model-sees/workspace-write:
63
+ en: 269d494ad304638d
64
+ zh: a879c1d7cc03e6ac
65
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy/what-the-model-sees/danger-full-access:
66
+ en: 4e28324a011422a1
67
+ zh: bf31ffce30f06a09
68
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy/token-effect:
69
+ en: e3387fb6eeba1239
70
+ zh: 06a47393057d3e9b
71
+ /qilin-sandbox-policy/model-experience/current-file-sandbox-policy/kv-cache-effect:
72
+ en: a16a657e0e5145fb
73
+ zh: 4b19263a728990dc
74
+ /qilin-sandbox-policy/known-limitations-and-deferred-work:
75
+ en: af363d6626eaa9b8
76
+ zh: 93f8d5f9519db0ae
77
+ /qilin-sandbox-policy/known-limitations-and-deferred-work/dev-note:
78
+ en: e67f633dc6fe7773
79
+ zh: 3bb0c20c93f16f98
package/README.md CHANGED
@@ -1,3 +1,158 @@
1
- # Temporary Holding Version
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
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/sandbox-policy
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Use this package to apply one file-effect policy to every confined bash, filesystem, and terminal call. Deployments choose a default mode and fallback workspace root, while each session can switch modes independently. Session choices survive restart, and all enforcing capabilities use the same mode and workspace for a call. Before each model request, the model receives the effective policy and workspace without an 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: '@qilin-agent/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()` | Absolute fallback root for agentless calls or sessions without a cwd; relative values fail at load. Normal agent calls use the session's immutable cwd |
49
+
50
+ The generated [configuration catalog](../../../docs/config-catalog.md#qilin-agentsandbox-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
57
+
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.
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## Understand the implementation
64
+
65
+ <details>
66
+ <summary>Implementation internals — click to expand</summary>
67
+
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).
69
+
70
+ ### Resolution precedence
71
+
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` supplies the workspace root; otherwise the configured fallback applies. Absolute execution-world spelling is preserved. Enforcing providers canonicalize the root on their own filesystem, so remote `symlink/..` paths are never resolved on the Harness host.
73
+
74
+ ### The per-session store
75
+
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`.
77
+
78
+ ### Model-visible text
79
+
80
+ The `sandbox:policy` contribution states the mode's capability-neutral file-effect contract and the recorded 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 `qilin-plan-mode`'s system section.
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
+
89
+ </details>
90
+
91
+ -----
92
+
93
+ <a id="further-exploration"></a>
94
+ ## Further Exploration
95
+
96
+ Start with the subsystem reference for the shared vocabulary, then the seam contract and the cross-family decision.
97
+
98
+ - [Process sandbox subsystem](../../../docs/subsystems/sandbox.md) — modes, per-call policy, and enforcement semantics.
99
+ - [Sandbox seam package](../sandbox/README.md) — the confinement contract every enforcing capability implements.
100
+ - [Cross-family file sandbox decision](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) — why one shared policy home exists.
101
+
102
+ -----
103
+
104
+ <a id="model-experience"></a>
105
+ ## Model Experience
106
+
107
+ ### Current file sandbox policy
108
+
109
+ #### What the model sees
110
+
111
+ One `sandbox:policy` contribution in the current runtime-context snapshot for every agent session. It does not enumerate mounted capabilities. Tool plugins retain operation and escalation guidance, approval policy contributes separately to the same snapshot, and plan guidance remains `qilin-plan-mode`'s system section.
112
+
113
+ ##### Read-only
114
+
115
+ ```markdown
116
+ Current QILIN file policy: read-only. Any available operation enforced by the QILIN file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.
117
+ ```
118
+
119
+ ##### Workspace-write
120
+
121
+ ```markdown
122
+ Current QILIN file policy: workspace-write. Any available operation enforced by the QILIN file sandbox may modify files under the session workspace: "<workspace root>". Some platform temporary areas may also be writable.
123
+ ```
124
+
125
+ ##### Danger-full-access
126
+
127
+ ```markdown
128
+ Current QILIN file policy: danger-full-access. The QILIN file sandbox does not restrict file modifications by available operations.
129
+ ```
130
+
131
+ #### Token effect
132
+
133
+ One concise durable context message on the first request and each effective policy change; unchanged requests add nothing. `workspace-write` carries only the recorded session workspace path; platform-specific temporary paths are summarized without adding host-dependent bytes.
134
+
135
+ #### KV Cache effect
136
+
137
+ The stable system prompt remains byte-identical across mode changes. A changed full context snapshot is appended after retained history, preserving the prior cached prefix; subsequent unchanged requests reuse that retained snapshot.
138
+
139
+ ## Known Limitations and Deferred Work
140
+
141
+ <a id="known-limitations-and-deferred-work"></a>
142
+
143
+
144
+ These limits define the policy surface this package provides. They are current package constraints, not a general sandbox comparison or a task backlog.
145
+
146
+ - **One primary workspace root per session** — policy resolves `SessionHeader.cwd`; extra writable roots are not part of `SandboxExecutionPolicy`.
147
+ - **File-effect modes only** — `SandboxMode` governs file effects; network and process policy are outside its vocabulary, so no knob here restricts them.
148
+ - **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.
149
+
150
+ <a id="dev-note"></a>
151
+ ### Dev Note
152
+
153
+ <details>
154
+ <summary>Working context for maintainers — click to expand</summary>
155
+
156
+ None.
157
+
158
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,158 @@
1
+ ---
2
+ description: "面向需要在各项负责强制执行的能力之间组合、配置或排查文件操作策略的用户与维护者,提供共享的逐调用沙箱策略解析器与当前模型上下文。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/sandbox-policy
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 使用本包可以让每次受限的 bash、文件系统和终端调用遵循同一份文件操作策略。部署方选择默认模式和回退工作区根目录,每个会话则可以独立切换模式。会话选择可跨重启保留,所有强制执行能力在一次调用中使用相同的模式和工作区。每次模型请求前,模型都会收到有效策略和工作区说明,但不会收到已挂载能力的清单。
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: '@qilin-agent/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 的会话所用的绝对回退根目录;相对值在加载时拒绝。普通 agent 调用使用会话的不可变 cwd |
49
+
50
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#qilin-agentsandbox-policy)是每个受支持字段及其 JSDoc 的穷尽式真源。
51
+
52
+ ### 切换会话模式
53
+
54
+ 会话的模式可以在运行时通过 UI 策略控件或显式切换来更改;切换记录在会话日志中,并在该会话的下一次受限调用时生效。切换通过回放跨重启保留,每个会话保持自己的模式——两个会话绝不会看到彼此状态。切换后的会话继续以不可变的工作区 cwd 作为写入边界。
55
+
56
+ ### 失败与恢复
57
+
58
+ 无效的配置模式会在插件加载时被拒绝,因此拼写错误会导致显式报错,而不是静默改变策略。没有 cwd 的会话与无 agent 调用回退到配置的工作区根目录;带已批准显式模式的调用只在该次调用中使用该模式。
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## 理解实现
64
+
65
+ <details>
66
+ <summary>实现细节——点击展开</summary>
67
+
68
+ 本节解释策略解析、逐会话存储与模型可见贡献;可观察行为已在[使用本包](#use-this-package)中完整说明。
69
+
70
+ ### 解析优先级
71
+
72
+ `resolve({ session, mode })` 返回一份完整的逐调用策略:已批准的显式模式优先于会话最后一条 `sandbox/mode` 事件,后者又优先于部署默认值。会话的不可变 `cwd` 提供工作区根目录;否则使用配置的回退值。执行环境中的绝对路径写法保持不变。执行限制的提供方在自己的文件系统上规范化根目录,因此远端 `symlink/..` 路径绝不会在 Harness 主机上解析。
73
+
74
+ ### 逐会话存储
75
+
76
+ 运行时切换是在对应会话日志中追加的一条仅写入日志的 `sandbox/mode` 事件——切换本身就是事件,任何机制都不会在带外修改模式状态。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖通过回放跨重启保留,两个会话也绝不会看到彼此状态。工作区标识无需事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。事件仍只进入日志;在每次请求前,归属方会把当前事实贡献给完整运行时上下文快照,agent loop(智能体循环)将该快照记录为一条带来源的 `user/message`。
77
+
78
+ ### 模型可见文本
79
+
80
+ `sandbox:policy` 贡献说明该模式与具体能力无关的文件操作约定,以及 `workspace-write` 下已记录的会话工作区。它不枚举已挂载能力;工具插件保留特定于操作的拒绝与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `qilin-plan-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
+
89
+ </details>
90
+
91
+ -----
92
+
93
+ <a id="further-exploration"></a>
94
+ ## 进一步探索
95
+
96
+ 先从子系统参考文档了解共享词汇,再看 seam 约定与跨家族决策。
97
+
98
+ - [进程沙箱子系统](../../../docs/subsystems/sandbox.zh.md)——模式、逐调用策略与强制执行语义。
99
+ - [沙箱 seam 包](../sandbox/README.zh.md)——每个强制执行能力实现的隔离约定。
100
+ - [跨家族文件沙箱决策](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md)——为何存在统一的共享策略归属位置。
101
+
102
+ -----
103
+
104
+ <a id="model-experience"></a>
105
+ ## 模型体验
106
+
107
+ ### 当前文件沙箱策略
108
+
109
+ #### 模型看到什么
110
+
111
+ 每个 agent 会话的当前运行时上下文快照中都有一项 `sandbox:policy` 贡献。它不枚举已挂载的能力。工具插件继续负责操作与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `qilin-plan-mode` 的系统段落管理。
112
+
113
+ ##### 只读
114
+
115
+ ```markdown
116
+ Current QILIN file policy: read-only. Any available operation enforced by the QILIN file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.
117
+ ```
118
+
119
+ ##### 工作区写入
120
+
121
+ ```markdown
122
+ Current QILIN file policy: workspace-write. Any available operation enforced by the QILIN file sandbox may modify files under the session workspace: "<workspace root>". Some platform temporary areas may also be writable.
123
+ ```
124
+
125
+ ##### 完全访问
126
+
127
+ ```markdown
128
+ Current QILIN file policy: danger-full-access. The QILIN file sandbox does not restrict file modifications by available operations.
129
+ ```
130
+
131
+ #### Token 影响
132
+
133
+ 首次请求和有效策略每次变化时增加一条简洁的持久上下文消息;未变化的请求不增加内容。`workspace-write` 只携带已记录的会话工作区路径;平台特定的临时路径会以摘要表述,不会加入依赖主机的字节。
134
+
135
+ #### KV Cache 影响
136
+
137
+ 模式切换时,稳定的系统提示词仍逐字节相同。变化后的完整上下文快照会追加到保留的历史之后,从而保留此前已缓存的前缀;后续未变化的请求会复用该保留快照。
138
+
139
+ ## 已知限制与延期工作
140
+
141
+ <a id="known-limitations-and-deferred-work"></a>
142
+
143
+
144
+ 这些限制界定了本包提供的策略范围。它们是当前的包级约束,并非通用沙箱对比,也不是待办事项清单。
145
+
146
+ - **每个会话只有一个主要工作区根目录**——策略解析 `SessionHeader.cwd`;额外可写根目录不属于 `SandboxExecutionPolicy`。
147
+ - **仅限文件操作模式**——`SandboxMode` 管控文件操作;网络和进程策略不在其词汇中,因此这里没有限制它们的旋钮。
148
+ - **有意概述临时区域**——强制执行后端会授予不同的平台临时区域,这些区域在策略解析后才会选定,因此无法在当前上下文中如实枚举。
149
+
150
+ <a id="dev-note"></a>
151
+ ### 开发备注
152
+
153
+ <details>
154
+ <summary>维护者的工作上下文——点击展开</summary>
155
+
156
+ 无。
157
+
158
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,159 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { Service } from "@qilin-agent/kylin";
3
+ import { z } from "zod";
4
+ import z$1 from "@qilin-agent/schemastery";
5
+ //#region lib/types/session-mode.js
6
+ /**
7
+ * Per-session sandbox-mode override: the session log as the store. A runtime
8
+ * switch (a UI policy control or test scenario) is recorded as one
9
+ * `sandbox/mode` event on the session it applies to;
10
+ * `effective = projection state ?? the deployment default`, so an override
11
+ * survives restart by replay, two sessions can never see each other's state,
12
+ * and there is no external config store. The event is log-only (the
13
+ * `approval/*` precedent): the policy owner projects the fold into each model
14
+ * request, while enforcing tools report operation-specific boundary markers.
15
+ * EXECUTION honors the same fold through `ctx.sandboxPolicy.resolve()` — it
16
+ * stamps the mode together with the calling session's workspace root onto each
17
+ * capability call, weakest-precedence beneath an escalation grant.
18
+ *
19
+ * The override is policy state shared by every enforcing family (bash and
20
+ * filesystem alike), so it lives here in the policy package rather than in any
21
+ * one capability's seam.
22
+ *
23
+ * @module qilin-sandbox-policy/session-mode
24
+ */
25
+ /** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
26
+ const SANDBOX_MODES = [
27
+ "read-only",
28
+ "workspace-write",
29
+ "danger-full-access"
30
+ ];
31
+ /**
32
+ * THE write path for a session's sandbox-mode override: appends exactly one
33
+ * `sandbox/mode` event — the switch IS its event; nothing mutates mode state
34
+ * out of band. Takes effect on the session's next confined call (bash or fs)
35
+ * — consumers read the shared projection state.
36
+ * @param session - the session the override belongs to.
37
+ * @param mode - the mode every subsequent confined call in this session runs
38
+ * under (until the next switch).
39
+ */
40
+ function setSandboxMode(session, mode) {
41
+ session.append("sandbox/mode", { mode });
42
+ }
43
+ //#endregion
44
+ //#region lib/types/index.js
45
+ /**
46
+ * The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
47
+ * deployment's sandbox fallbacks plus per-session resolution: the file-effect
48
+ * {@link SandboxMode}, the `workspace-write` root, and the override kit (the
49
+ * `sandbox/mode` event, its fold, and its write path; the fold is the
50
+ * `sandboxMode` session-projection unit registered here, while the event and
51
+ * write path come from `./session-mode.ts`).
52
+ * Before each agent request, the owner also contributes the resolved policy to
53
+ * the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
54
+ * model history, so replay reconstructs the same mode and root the enforcing
55
+ * consumers resolve without rewriting the stable system prompt.
56
+ *
57
+ * Enforcing filesystem, one-shot bash, and terminal backends read the SAME
58
+ * resolved policy here. The context describes that policy without inventorying
59
+ * capabilities, while each backend retains its own enforcement dialect and each
60
+ * tool owns its operation-specific denial and escalation guidance. The service
61
+ * reads session state once at each operation boundary; executors and providers
62
+ * remain session-free.
63
+ *
64
+ * @module @qilin-agent/sandbox-policy
65
+ */
66
+ /** Preserve execution-world spelling; enforcing providers resolve filesystem identity on their host. */
67
+ function resolveWorkspaceRoot(path) {
68
+ if (!isAbsolute(path)) throw new Error("sandbox-policy: workspace root must be an absolute execution-world path");
69
+ return path;
70
+ }
71
+ /** Render the policy without claiming which capabilities are mounted. */
72
+ function renderPolicyContext(policy) {
73
+ switch (policy.mode) {
74
+ case "read-only": return "Current QILIN file policy: read-only. Any available operation enforced by the QILIN file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.";
75
+ case "workspace-write": return `Current QILIN file policy: workspace-write. Any available operation enforced by the QILIN file sandbox may modify files under the session workspace: ${JSON.stringify(policy.workspaceRoot)}. Some platform temporary areas may also be writable.`;
76
+ case "danger-full-access": return "Current QILIN file policy: danger-full-access. The QILIN file sandbox does not restrict file modifications by available operations.";
77
+ /* v8 ignore next 4 -- SandboxMode is a typed same-process closed union; this branch is only the static exhaustiveness guard. */
78
+ default: {
79
+ const mode = policy.mode;
80
+ throw new Error(`unreachable sandbox mode: ${String(mode)}`);
81
+ }
82
+ }
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();
90
+ /**
91
+ * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
92
+ * default mode, fallback workspace root, and current request-time policy
93
+ * section. Tool layers call {@link resolve} for each execution so a session's
94
+ * mode log and immutable cwd travel together to every enforcing capability.
95
+ */
96
+ var SandboxPolicyService = class extends Service {
97
+ static Config = z$1.object({
98
+ mode: z$1.union([
99
+ "read-only",
100
+ "workspace-write",
101
+ "danger-full-access"
102
+ ]).default("read-only"),
103
+ workspaceRoot: z$1.string()
104
+ });
105
+ static inject = ["sessionProjections"];
106
+ /** The deployment default mode — the fallback beneath a session override. */
107
+ defaultMode;
108
+ /** The absolute `workspace-write` fallback root for calls without a session cwd. */
109
+ workspaceRoot;
110
+ constructor(ctx, config) {
111
+ super(ctx, "sandboxPolicy");
112
+ this.defaultMode = config.mode;
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
+ });
121
+ ctx.inject(["systemPrompt"], (scope) => {
122
+ scope.systemPrompt.context({
123
+ name: "sandbox:policy",
124
+ order: scope.systemPrompt.getContextOrder("SANDBOX_POLICY"),
125
+ text: (context) => {
126
+ const session = context.agent?.session;
127
+ return session === void 0 ? "" : renderPolicyContext(this.resolve({ session }));
128
+ }
129
+ });
130
+ });
131
+ }
132
+ /**
133
+ * Resolve the complete policy for one capability call. An approved explicit
134
+ * mode outranks the session's last `sandbox/mode` event, which outranks the
135
+ * deployment default. A session cwd is its workspace-write boundary; the
136
+ * configured root is the fallback for agentless calls and sessions without a
137
+ * cwd.
138
+ * @param request - optional session and approved mode override.
139
+ * @returns the fully resolved per-call mode and absolute workspace root.
140
+ */
141
+ resolve(request = {}) {
142
+ const { session } = request;
143
+ return {
144
+ mode: request.mode ?? (session === void 0 ? void 0 : this.overrideOf(session)) ?? this.defaultMode,
145
+ workspaceRoot: resolveWorkspaceRoot(session?.header.cwd ?? this.workspaceRoot),
146
+ ...session === void 0 ? {} : { sessionId: session.id }
147
+ };
148
+ }
149
+ /**
150
+ * Read the session override without applying the deployment default.
151
+ * @param session - session whose log supplies the override.
152
+ * @returns the last logged mode, or `undefined` without one.
153
+ */
154
+ overrideOf(session) {
155
+ return this.ctx.sessionProjections.stateOf(session, "sandboxMode") ?? void 0;
156
+ }
157
+ };
158
+ //#endregion
159
+ export { SANDBOX_MODES, SandboxPolicyService, SandboxPolicyService as default, setSandboxMode };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
3
+ * deployment's sandbox fallbacks plus per-session resolution: the file-effect
4
+ * {@link SandboxMode}, the `workspace-write` root, and the override kit (the
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`).
8
+ * Before each agent request, the owner also contributes the resolved policy to
9
+ * the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
10
+ * model history, so replay reconstructs the same mode and root the enforcing
11
+ * consumers resolve without rewriting the stable system prompt.
12
+ *
13
+ * Enforcing filesystem, one-shot bash, and terminal backends read the SAME
14
+ * resolved policy here. The context describes that policy without inventorying
15
+ * capabilities, while each backend retains its own enforcement dialect and each
16
+ * tool owns its operation-specific denial and escalation guidance. The service
17
+ * reads session state once at each operation boundary; executors and providers
18
+ * remain session-free.
19
+ *
20
+ * @module @qilin-agent/sandbox-policy
21
+ */
22
+ import { Context, Service } from '@qilin-agent/kylin';
23
+ import { z as zod } from 'zod';
24
+ import z from '@qilin-agent/schemastery';
25
+ import type { SandboxExecutionPolicy, SandboxMode } from '@qilin-agent/sandbox';
26
+ import type { Session } from '@qilin-agent/session';
27
+ export { SANDBOX_MODES, setSandboxMode } from './session-mode.ts';
28
+ declare module '@qilin-agent/kylin' {
29
+ interface Context {
30
+ sandboxPolicy: SandboxPolicyService;
31
+ }
32
+ }
33
+ /**
34
+ * Plugin config: the deployment's sandbox default. All optional — `Config`
35
+ * supplies the defaults (`mode: 'read-only'` is the fail-safe default; a
36
+ * deployment that wants a workspace-writable agent opts in explicitly). The
37
+ * runner choice is NOT here (it is the `ctx.sandbox` provider's config), nor
38
+ * is any per-family knob: this is the one shared policy home.
39
+ */
40
+ export interface Config {
41
+ /** File-sandbox mode a session starts from (default: `read-only`). */
42
+ mode?: SandboxMode;
43
+ /**
44
+ * Absolute fallback root for agentless calls and sessions without a cwd (default:
45
+ * `process.cwd()`). Normal agent calls use their session cwd instead.
46
+ */
47
+ workspaceRoot?: string;
48
+ }
49
+ /** Inputs that select the sandbox policy for one capability call. */
50
+ export interface SandboxPolicyRequest {
51
+ /** Calling session; its immutable cwd becomes the workspace boundary. */
52
+ session?: Session;
53
+ /** Explicit approved mode override, which outranks session policy. */
54
+ mode?: SandboxMode;
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 '@qilin-agent/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
+ }
65
+ /**
66
+ * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
67
+ * default mode, fallback workspace root, and current request-time policy
68
+ * section. Tool layers call {@link resolve} for each execution so a session's
69
+ * mode log and immutable cwd travel together to every enforcing capability.
70
+ */
71
+ export declare class SandboxPolicyService extends Service {
72
+ static Config: z<Config>;
73
+ static inject: string[];
74
+ /** The deployment default mode — the fallback beneath a session override. */
75
+ readonly defaultMode: SandboxMode;
76
+ /** The absolute `workspace-write` fallback root for calls without a session cwd. */
77
+ readonly workspaceRoot: string;
78
+ constructor(ctx: Context, config: Config);
79
+ /**
80
+ * Resolve the complete policy for one capability call. An approved explicit
81
+ * mode outranks the session's last `sandbox/mode` event, which outranks the
82
+ * deployment default. A session cwd is its workspace-write boundary; the
83
+ * configured root is the fallback for agentless calls and sessions without a
84
+ * cwd.
85
+ * @param request - optional session and approved mode override.
86
+ * @returns the fully resolved per-call mode and absolute workspace root.
87
+ */
88
+ resolve(request?: SandboxPolicyRequest): SandboxExecutionPolicy;
89
+ /**
90
+ * Read the session override without applying the deployment default.
91
+ * @param session - session whose log supplies the override.
92
+ * @returns the last logged mode, or `undefined` without one.
93
+ */
94
+ overrideOf(session: Session): SandboxMode | undefined;
95
+ }
96
+ export default SandboxPolicyService;
97
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Per-session sandbox-mode override: the session log as the store. A runtime
3
+ * switch (a UI policy control or test scenario) is recorded as one
4
+ * `sandbox/mode` event on the session it applies to;
5
+ * `effective = projection state ?? the deployment default`, so an override
6
+ * survives restart by replay, two sessions can never see each other's state,
7
+ * and there is no external config store. The event is log-only (the
8
+ * `approval/*` precedent): the policy owner projects the fold into each model
9
+ * request, while enforcing tools report operation-specific boundary markers.
10
+ * EXECUTION honors the same fold through `ctx.sandboxPolicy.resolve()` — it
11
+ * stamps the mode together with the calling session's workspace root onto each
12
+ * capability call, weakest-precedence beneath an escalation grant.
13
+ *
14
+ * The override is policy state shared by every enforcing family (bash and
15
+ * filesystem alike), so it lives here in the policy package rather than in any
16
+ * one capability's seam.
17
+ *
18
+ * @module qilin-sandbox-policy/session-mode
19
+ */
20
+ import type { Session } from '@qilin-agent/session';
21
+ import type { SandboxMode } from '@qilin-agent/sandbox';
22
+ declare module '@qilin-agent/session/types' {
23
+ interface SessionEventMap {
24
+ /**
25
+ * The session's sandbox mode was switched — log-only (like `approval/*`;
26
+ * NOT a surface event, carries no `surfaceOp`): durable and replayable,
27
+ * never in the model transcript. The LAST such event is the session's
28
+ * override (folded by the sandboxMode projection unit). `source: 'delegation'` marks
29
+ * an override seeded into a child; an absent source is a runtime switch.
30
+ */
31
+ 'sandbox/mode': {
32
+ mode: SandboxMode;
33
+ /** Marks an override seeded into a child at delegation. */
34
+ source?: 'delegation';
35
+ };
36
+ }
37
+ }
38
+ /** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
39
+ export declare const SANDBOX_MODES: readonly SandboxMode[];
40
+ /**
41
+ * THE write path for a session's sandbox-mode override: appends exactly one
42
+ * `sandbox/mode` event — the switch IS its event; nothing mutates mode state
43
+ * out of band. Takes effect on the session's next confined call (bash or fs)
44
+ * — consumers read the shared projection state.
45
+ * @param session - the session the override belongs to.
46
+ * @param mode - the mode every subsequent confined call in this session runs
47
+ * under (until the next switch).
48
+ */
49
+ export declare function setSandboxMode(session: Session, mode: SandboxMode): void;
50
+ //# sourceMappingURL=session-mode.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@qilin-agent/sandbox-policy",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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": "3.1.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/sandbox/sandbox-policy"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib/index.js",
29
+ "lib/types/**/*.d.ts"
30
+ ],
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "@qilin-agent/sandbox": "3.1.3",
34
+ "@qilin-agent/session": "3.1.3",
35
+ "@qilin-agent/system-prompt": "3.1.3",
36
+ "@qilin-agent/agent": "3.1.3",
37
+ "@qilin-agent/session-projection": "3.1.3",
38
+ "@qilin-agent/kylin": "~4.0.3"
39
+ },
40
+ "dependencies": {
41
+ "zod": "^4.4.3",
42
+ "@qilin-agent/schemastery": "~3.18.3"
43
+ },
44
+ "devDependencies": {
45
+ "@qilin-agent/session": "3.1.3",
46
+ "@qilin-agent/agent": "3.1.3",
47
+ "@qilin-agent/system-prompt": "3.1.3",
48
+ "@qilin-agent/sandbox": "3.1.3",
49
+ "@qilin-agent/kylin": "~4.0.3",
50
+ "@qilin-agent/session-projection": "3.1.3"
51
+ }
6
52
  }