@morlay/dsh-sandbox-local 0.0.3 → 0.0.5
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.md +23 -133
- package/dist/client.cjs +2 -11
- package/dist/client.d.cts +1 -10
- package/dist/client.d.mts +1 -10
- package/dist/index.d.cts +0 -7
- package/dist/index.d.mts +0 -7
- package/dist/index.mjs +51 -72
- package/dist/rows.d.cts +0 -9
- package/dist/rows.d.mts +0 -9
- package/dist/rows.mjs +0 -1
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +29 -27
- package/src/client/index.ts +8 -13
- package/src/client/locales.ts +3 -3
- package/src/config.ts +12 -23
- package/src/fs.ts +2 -2
- package/src/index.ts +4 -3
- package/src/policy.ts +86 -56
- package/src/rows.ts +6 -9
- package/src/sandbox.ts +2 -2
package/README.md
CHANGED
|
@@ -1,63 +1,12 @@
|
|
|
1
1
|
# @morlay/dsh-sandbox-local
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
可配置沙箱与审批的**运行时策略面** bundle:替换官方 `ctx.sandbox`(进程沙箱)与 `ctx.fs`(文件系统围栏),在官方
|
|
4
|
+
语义之上叠加 `access` 规则——`rw <path>` 追加工作区之外的可写根,`r- <path>` 只读(读放行、写拒绝),
|
|
5
|
+
`-- <pattern>` 拒绝访问(读与写都拒);并接管模型看到的运行时快照(`sandbox:policy` / `approval:policy`)。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 用法
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
`danger-full-access`)与 `workspaceRoot`;`workspace-write` 的可写路径是硬编码的
|
|
11
|
-
`[工作区, /tmp, os.tmpdir()]`(`vendor/deepseek-harness/packages/sandbox/sandbox/src/roots.ts:52-55`),
|
|
12
|
-
没有任何追加可写根或拒绝项的配置面。于是「让 agent 能写 `$XDG_CACHE_HOME`,
|
|
13
|
-
但永远不许碰项目里的 `mise.*.toml`」这类诉求只能整块放弃隔离。
|
|
14
|
-
|
|
15
|
-
本包把「能表达多少就说多少」明确下来:Seatbelt 完整生效,其余平台按方言降级,
|
|
16
|
-
并在加载期告警,而不是静默失效。
|
|
17
|
-
|
|
18
|
-
## 行为
|
|
19
|
-
|
|
20
|
-
替换两个服务,规则在两个入口保持同一语义:
|
|
21
|
-
|
|
22
|
-
| 条目 | `ctx.fs`(read / write / edit / list 工具) | `ctx.sandbox`(bash 等子进程) |
|
|
23
|
-
| ----------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
24
|
-
| `-- <path>` | 任何模式下读与写都拒(`resolve` 入口即拦截) | Seatbelt:读 + 写;bwrap:退化为只读;Landlock / Windows ACL:无表达(加载期告警) |
|
|
25
|
-
| `r- <path>` | 任何模式下读放行、写拒绝(优先于可写根) | Seatbelt:`(deny file-write* …)`;bwrap:`--ro-bind-try`(只读挂载);Landlock / Windows:无表达 |
|
|
26
|
-
| `rw <path>` | `workspace-write` 下计入可写根 | Seatbelt:`(allow file-write* (subpath …))`;bwrap:`--bind-try`;Landlock:`--rw`;Windows:无表达 |
|
|
27
|
-
| 无条目 | 与官方 `fs-sandbox` 行为一致 | 与官方 argv 逐字一致(不做任何改写) |
|
|
28
|
-
|
|
29
|
-
- **进程沙箱侧是复用,不是重写**:`ConfigurableSandboxProvider` 继承官方
|
|
30
|
-
`LocalSandboxProvider`,`confine` 先走 `super.confine()`(runner 探测与选择、
|
|
31
|
-
Windows ACL 私有 temp、拒绝方言与 runner 失败规则全部保留),再按方言把规则追加到
|
|
32
|
-
返回的 argv 上。
|
|
33
|
-
- **Seatbelt 规则追加在 profile 末尾**:SBPL 的后置规则覆盖先置规则,因此
|
|
34
|
-
`(deny file-read* file-write* …)` 能压过官方已写入的 `(allow file-write* (subpath …))`;
|
|
35
|
-
已用真实 `sandbox-exec` 验证(`src/__tests__/seatbelt.e2e.spec.ts`)。
|
|
36
|
-
- **三类条目在两个入口同步**:上游把 `writableRoots` 同时喂给 Seatbelt profile 与
|
|
37
|
-
进程内 fs 围栏,只改一侧会造出「bash 能写、write 工具不能写」的裂缝。
|
|
38
|
-
- **命中优先级 `--` > `r-` > `rw` / 平台可写根**:显式拒绝覆盖只读声明,只读声明覆盖
|
|
39
|
-
更宽的可写授予(例如 `rw {{ env.XDG_DATA_HOME }}` 与 `r- {{ env.XDG_DATA_HOME }}/secrets`
|
|
40
|
-
同时存在时,后者胜)。
|
|
41
|
-
- **`r-` / `--` 条目在 `danger-full-access` 下仍然生效**(`ctx.fs` 侧):它们是显式写下的
|
|
42
|
-
用户规则,不是模式的推论;进程沙箱侧在 `danger-full-access` 下不经过沙箱,本包也无从施加。
|
|
43
|
-
|
|
44
|
-
## 配置
|
|
45
|
-
|
|
46
|
-
六个字段都上设置页:`access` 可改(**改完规则当场重算**,不用重挂这一行);其余五个是**装配事实**,页面上只读
|
|
47
|
-
可见——它们在 `Config` 上标了 `.volatile().disabled()`,所以能看清「这一行现在装配成什么样」而改不了。`volatile`
|
|
48
|
-
会让解析后的值变成**引用**,而官方 `LocalSandboxProvider` / `LocalFileSystem` 在构造时读一次这些字段并做装配期
|
|
49
|
-
校验,所以交给基类的那份配置先由 `upstreamConfigOf` 解包成值(`src/config.ts`)。
|
|
50
|
-
|
|
51
|
-
| 字段 | 默认 | 含义 |
|
|
52
|
-
| ------------------------- | --------------- | --------------------------------------------------------------------------------------------- |
|
|
53
|
-
| `access` | `[]` | 规则条目:`rw <path>` 可写根 / `r- <path>` 只读 / `-- <pattern>` 拒绝访问;数组或一段多行文本 |
|
|
54
|
-
| `runnerCommand` | `[]` | 透传官方 `sandbox-local`:替换 runner argv(配置了它就不能用规则) |
|
|
55
|
-
| `runnerFailureSignatures` | `[]` | 透传官方 `sandbox-local`:自定义 runner 的失败签名 |
|
|
56
|
-
| `probeTimeoutMs` | `5000` | 透传官方 `sandbox-local`:候选 runner 的探测超时 |
|
|
57
|
-
| `cwd` | `process.cwd()` | 透传官方 `fs-local`:相对路径的解析基准 |
|
|
58
|
-
| `diffBasisMaxBytes` | `10485760` | 透传官方 `fs-local`:overwrite diff 单侧字节上限 |
|
|
59
|
-
|
|
60
|
-
两种写法等价(数组每项一条,或多行文本每行一条;多行文本的空行忽略):
|
|
9
|
+
`access` 的两种写法等价(数组每项一条,或多行文本每行一条;空行忽略):
|
|
61
10
|
|
|
62
11
|
```yaml
|
|
63
12
|
- id: sandbox-local
|
|
@@ -69,87 +18,28 @@
|
|
|
69
18
|
- "-- **/*.pem"
|
|
70
19
|
```
|
|
71
20
|
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
rw {{ env.XDG_CACHE_HOME }}
|
|
77
|
-
r- {{ env.XDG_CONFIG_HOME }}
|
|
78
|
-
-- mise.*.toml
|
|
79
|
-
-- **/*.pem
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
条目语法:
|
|
83
|
-
|
|
84
|
-
- 每条必须以 `rw ` / `r- ` / `-- ` 开头;缺前缀、或前缀后没有路径,加载即失败(规则
|
|
85
|
-
不因写法歧义而变形)。
|
|
86
|
-
- 语义:`rw` 允许读写;`r-` 只允许读;`--` 读与写都拒绝。优先级 `--` > `r-` > `rw`。
|
|
87
|
-
- `{{ env.NAME }}` 在加载期按进程环境展开;变量未设置或为空时插件加载失败。
|
|
88
|
-
- 相对路径相对**会话工作区**(不是 `cwd` 配置项)解析。
|
|
89
|
-
- `r-` 与 `--` 条目接受 glob:`*` 与 `?` 不跨 `/`,`**` 跨层级(`**/` 也匹配零层),
|
|
90
|
-
`[!ab]` 取反;生成的正则同时用于进程内匹配与 SBPL 的 `(regex #"…")`,因此只用两者
|
|
91
|
-
共有的语法。
|
|
92
|
-
- 字面(无通配)的 `r-` / `--` 条目命中自身**及其全部后代**;`rw` 条目必须是具体路径
|
|
93
|
-
(可写根没有「通配」语义)。
|
|
94
|
-
|
|
95
|
-
## 装配
|
|
96
|
-
|
|
97
|
-
本包自带 `cordis.patch.yml`(禁用官方 `sandbox` / `fs-sandbox` 两行 + 插入自己的一行),
|
|
98
|
-
把本包作为**独立 bundle** 采用的部署直接列进 `dsh.profile.bundles` 即可;行不带 config
|
|
99
|
-
(schema 默认是空规则),patch 内容见该文件。
|
|
21
|
+
- 每条必须以 `rw ` / `r- ` / `-- ` 开头;缺前缀或前缀后没有路径,加载即失败。
|
|
22
|
+
- 命中优先级 `--` > `r-` > `rw` / 平台可写根;`r-` / `--` 在 `danger-full-access` 下仍然生效。
|
|
23
|
+
- `{{ env.NAME }}` 加载期按进程环境展开(未设置或为空即失败);相对路径相对**会话工作区**解析。
|
|
24
|
+
- `r-` / `--` 接受 glob(`*` / `?` 不跨 `/`,`**` 跨层级,`[!ab]` 取反);`rw` 必须是具体路径。
|
|
100
25
|
|
|
101
|
-
|
|
102
|
-
[`@morlay/dsh-profile`](../../bundles/sandbox-profile/README.md) 之前),因此"禁用官方两行 + 插入本行"由这份
|
|
103
|
-
patch 负责;`access` 规则的值由 `dsh-profile` 按 id 做 config 覆盖——装配与配置各归一处
|
|
104
|
-
(`@morlay/dsh-profile` 已在 `dependencies` 声明本包)。patch 层级的合并顺序与放置理由见
|
|
105
|
-
[设计 host 层部署配置](../../bundles/sandbox-profile/README.md)。
|
|
106
|
-
两种采用方式互斥:同时上线会重复插入同一行。
|
|
26
|
+
字段、规则语义、平台表达能力与装配链路见[设计 可配置沙箱的规则面与平台降级](./.agents/designs/20260929-可配置沙箱的规则面与平台降级.md)。
|
|
107
27
|
|
|
108
|
-
##
|
|
28
|
+
## 运行时快照(runtime context)
|
|
109
29
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
workspace-write / 全权 + workspace root),不知道本部署追加的 `rw` / `r-` / `--` 规则。
|
|
30
|
+
模型每步看到的那段动态快照里,本部署替换掉两条(都在 agent 创建时按 agent 作用域注册同名 context,近的作用域遮蔽
|
|
31
|
+
上游在全局层注册的那条;agentless 装配仍读上游那条):
|
|
113
32
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
33
|
+
| context | 上游文本 | 本包的文本 |
|
|
34
|
+
| ----------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `sandbox:policy` | 官方三种 mode 的英文描述 | 中文简化版 + 本部署的规则行(`额外可写` / `只读(不可写)` / `拒绝(读写都拒)`) |
|
|
36
|
+
| `approval:policy` | 英文 `Approval policy: ask…` / `Approval prompts are disabled…` | 按会话有效策略(`ctx.approval` 的 `overrideOf` 优先,否则 `config.policy`,再退 `ask`)选中文那一段 |
|
|
118
37
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`vendor/deepseek-harness/packages/sandbox/sandbox-policy/src/index.ts`。
|
|
38
|
+
注册时机是 agent 创建(`agent/created` + 覆盖已存在的 agent):`SystemPrompt.assemble()` 先把 `contexts` merge 好
|
|
39
|
+
再进瀑布,挂在瀑布里注册只能从**第二次**装配起生效(会话里就是"先英文后中文")。
|
|
122
40
|
|
|
123
|
-
##
|
|
124
|
-
|
|
125
|
-
- 官方 `sandbox` 与 `fs-sandbox` 行必须禁用:同一 scope 内重复注册同名服务会 fail loud
|
|
126
|
-
(`service "sandbox" has been registered at …`),而不是覆盖。
|
|
127
|
-
- 启用规则的层必须同时做三件事——禁用官方两行、插入本包行、写规则:只做后两件时官方
|
|
128
|
-
实现仍在提供 `ctx.sandbox` / `ctx.fs`,规则没有生效点,沙箱静默退回「只有工作区 +
|
|
129
|
-
`/tmp` 可写」(命令照常跑,没有报错)。装配守卫见 `@morlay/dsh-profile` 的
|
|
130
|
-
`patch.spec.ts`。
|
|
131
|
-
- 规则与 `runnerCommand` 互斥:自定义 runner 的 argv 方言无法识别,此时配了规则会在
|
|
132
|
-
`confine` 抛错(宁可失败也不让规则静默失效)。
|
|
133
|
-
- `read-only` 模式不追加 `rw` 条目(显式选定的只读边界不因额外可写根放松),但 `r-`
|
|
134
|
-
与 `--` 条目仍然生效。
|
|
135
|
-
|
|
136
|
-
## 已知限制
|
|
137
|
-
|
|
138
|
-
- **Linux / Windows 的子进程侧降级**:bwrap 把 `r-` 与 `--` 都表达成只读挂载
|
|
139
|
-
(`--ro-bind-try`,所以 `--` 在 bwrap 上退化为「只拒写入」),Landlock 无法表达任何
|
|
140
|
-
子路径规则,Windows ACL runner 的 argv 没有承载额外 grant 的入口(`rw` 同样不生效)。
|
|
141
|
-
加载期对每种降级都打 warn,`ctx.fs` 侧(read / write / edit 工具)在所有平台保持完整
|
|
142
|
-
语义。
|
|
143
|
-
- **bwrap 参数顺序未实测**:`--bind-try` / `--ro-bind-try` 的「后挂载覆盖先挂载」与
|
|
144
|
-
`-try` 缺路径语义来自 bwrap 文档而非本仓库的测试证据(本机为 macOS)。
|
|
145
|
-
- **`r-` / `--` 条目不隐藏目录项**:`ls` 仍能看到被保护文件的名字,被拦的是内容读取
|
|
146
|
-
(仅 `--`)与写入。
|
|
147
|
-
- **`ctx.fs` 侧是策略检查,不是内核边界**:与它替换掉的官方 `fs-sandbox` 同一威胁模型
|
|
148
|
-
(受信代码 + 模型可控路径);内核级隔离仍是 `ctx.sandbox` 的职责。
|
|
149
|
-
- **Windows 额外授权未实现**:官方 `AclWriteGrant` 可以做预授权,但没有把 `AclWriteGrant`
|
|
150
|
-
接进 `confine` 的现成路径,本版只告警。
|
|
151
|
-
|
|
152
|
-
## 验证
|
|
41
|
+
## 装配
|
|
153
42
|
|
|
154
|
-
|
|
155
|
-
|
|
43
|
+
行数据在本包的 `./rows` 出口(禁官方 `sandbox` / `fs-sandbox` 两行 + 插入本包一行);采用它的部署 bundle 是
|
|
44
|
+
[`@morlay/sandbox-profile`](../../bundles/sandbox-profile/README.md)——它渲染出这份 patch,并在同一份 patch 里按 id
|
|
45
|
+
给 `sandbox-local` 的 `access` 值。部署侧只需把那个 bundle 列进 `dsh.profile.bundles`。
|
package/dist/client.cjs
CHANGED
|
@@ -5,29 +5,20 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
//#region src/client/locales.ts
|
|
8
|
-
/** 本包在配置页上的字段文案(命名空间 `settings.sandbox-local`)。 */
|
|
9
8
|
const zh = {
|
|
10
9
|
access: "额外可写根与拒绝项",
|
|
11
|
-
accessHint: "一行一条:`rw
|
|
10
|
+
accessHint: "一行一条:`rw <路径>` 追加可写根,`r- <路径>` 只读,`-- <路径>` 拒绝;支持环境变量模板与 glob(`rw` 项必须是具体路径)。改完当场生效。"
|
|
12
11
|
};
|
|
13
12
|
const en = {
|
|
14
13
|
access: "Extra writable roots and refusals",
|
|
15
|
-
accessHint: "One entry per line: `rw
|
|
14
|
+
accessHint: "One entry per line: `rw <path>` grants a writable root, `r- <path>` makes it read-only, `-- <path>` denies it; env templates and globs work (`rw` entries must name a concrete path). Edits apply immediately."
|
|
16
15
|
};
|
|
17
16
|
//#endregion
|
|
18
17
|
//#region src/client/index.ts
|
|
19
|
-
/** 本包 host 行 id:行配置页读的就是这个命名空间。 */
|
|
20
18
|
const SANDBOX_NS = "sandbox-local";
|
|
21
|
-
/** 这一行里需要中文文案的字段。 */
|
|
22
19
|
const FIELDS = ["access"];
|
|
23
|
-
/** 本页字典的命名空间。 */
|
|
24
20
|
const NS = "settings.sandbox-local";
|
|
25
|
-
/** 需要的服务:槽位(字段槽)与字典。 */
|
|
26
21
|
const inject = ["locale"];
|
|
27
|
-
/**
|
|
28
|
-
* 给 `access` 补文案。
|
|
29
|
-
* @param ctx - 浏览器插件上下文。
|
|
30
|
-
*/
|
|
31
22
|
function apply(ctx) {
|
|
32
23
|
const t = ctx.locale.bind(NS);
|
|
33
24
|
ctx.effect(() => ctx.locale.register(NS, {
|
package/dist/client.d.cts
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
1
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
//#region src/client/locales.d.ts
|
|
3
|
-
/** 本包在配置页上的字段文案(命名空间 `settings.sandbox-local`)。 */
|
|
4
3
|
declare const zh: {
|
|
5
4
|
readonly access: "额外可写根与拒绝项";
|
|
6
|
-
readonly accessHint: "一行一条:`rw
|
|
5
|
+
readonly accessHint: "一行一条:`rw <路径>` 追加可写根,`r- <路径>` 只读,`-- <路径>` 拒绝;支持环境变量模板与 glob(`rw` 项必须是具体路径)。改完当场生效。";
|
|
7
6
|
};
|
|
8
7
|
type SandboxFieldLocaleKey = keyof typeof zh;
|
|
9
8
|
//#endregion
|
|
10
9
|
//#region src/client/index.d.ts
|
|
11
|
-
/** 本包 host 行 id:行配置页读的就是这个命名空间。 */
|
|
12
10
|
declare const SANDBOX_NS = "sandbox-local";
|
|
13
|
-
/** 本页字典的命名空间。 */
|
|
14
11
|
declare const NS = "settings.sandbox-local";
|
|
15
12
|
declare module "@deepseek-ai/dsh-client-ui-slots" {
|
|
16
13
|
interface LocaleNamespaceMap {
|
|
17
|
-
/** 设置页里这一行的字段文案。 */
|
|
18
14
|
"settings.sandbox-local": SandboxFieldLocaleKey;
|
|
19
15
|
}
|
|
20
16
|
}
|
|
21
|
-
/** 需要的服务:槽位(字段槽)与字典。 */
|
|
22
17
|
declare const inject: string[];
|
|
23
|
-
/**
|
|
24
|
-
* 给 `access` 补文案。
|
|
25
|
-
* @param ctx - 浏览器插件上下文。
|
|
26
|
-
*/
|
|
27
18
|
declare function apply(ctx: Context): void;
|
|
28
19
|
//#endregion
|
|
29
20
|
export { NS, SANDBOX_NS, apply, inject };
|
package/dist/client.d.mts
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
1
|
import { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
//#region src/client/locales.d.ts
|
|
3
|
-
/** 本包在配置页上的字段文案(命名空间 `settings.sandbox-local`)。 */
|
|
4
3
|
declare const zh: {
|
|
5
4
|
readonly access: "额外可写根与拒绝项";
|
|
6
|
-
readonly accessHint: "一行一条:`rw
|
|
5
|
+
readonly accessHint: "一行一条:`rw <路径>` 追加可写根,`r- <路径>` 只读,`-- <路径>` 拒绝;支持环境变量模板与 glob(`rw` 项必须是具体路径)。改完当场生效。";
|
|
7
6
|
};
|
|
8
7
|
type SandboxFieldLocaleKey = keyof typeof zh;
|
|
9
8
|
//#endregion
|
|
10
9
|
//#region src/client/index.d.ts
|
|
11
|
-
/** 本包 host 行 id:行配置页读的就是这个命名空间。 */
|
|
12
10
|
declare const SANDBOX_NS = "sandbox-local";
|
|
13
|
-
/** 本页字典的命名空间。 */
|
|
14
11
|
declare const NS = "settings.sandbox-local";
|
|
15
12
|
declare module "@deepseek-ai/dsh-client-ui-slots" {
|
|
16
13
|
interface LocaleNamespaceMap {
|
|
17
|
-
/** 设置页里这一行的字段文案。 */
|
|
18
14
|
"settings.sandbox-local": SandboxFieldLocaleKey;
|
|
19
15
|
}
|
|
20
16
|
}
|
|
21
|
-
/** 需要的服务:槽位(字段槽)与字典。 */
|
|
22
17
|
declare const inject: string[];
|
|
23
|
-
/**
|
|
24
|
-
* 给 `access` 补文案。
|
|
25
|
-
* @param ctx - 浏览器插件上下文。
|
|
26
|
-
*/
|
|
27
18
|
declare function apply(ctx: Context): void;
|
|
28
19
|
//#endregion
|
|
29
20
|
export { NS, SANDBOX_NS, apply, inject };
|
package/dist/index.d.cts
CHANGED
|
@@ -7,13 +7,6 @@ import { Config as Config$2 } from "@deepseek-ai/dsh-sandbox-local";
|
|
|
7
7
|
interface Config extends Config$2, Config$1 {
|
|
8
8
|
access?: string | string[];
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* schema 解析之后的形状:`access` 是 **volatile 稳定引用**(页面可改的那一项),读它要过 `.get()`。
|
|
12
|
-
*
|
|
13
|
-
* 其余字段也是 volatile,但页面把它们画成**只读**(schema 上的 `disabled()`):它们是装配事实——runner 命令、
|
|
14
|
-
* 进程 cwd、上游基类读一次的超时与差额上限——放在页面上是为了看清「这一行现在装配成什么样」,改它们要重挂
|
|
15
|
-
* 这一行。所以给上游基类的那份配置是**解包后的值**({@link upstreamConfigOf}):基类读的是值,不是引用。
|
|
16
|
-
*/
|
|
17
10
|
interface ResolvedConfig extends Omit<Config, "access" | "runnerCommand" | "runnerFailureSignatures" | "probeTimeoutMs" | "cwd" | "diffBasisMaxBytes"> {
|
|
18
11
|
readonly access: Volatile<string | string[]>;
|
|
19
12
|
readonly runnerCommand: Volatile<readonly string[]>;
|
package/dist/index.d.mts
CHANGED
|
@@ -7,13 +7,6 @@ import { Volatile } from "@deepseek-ai/cosmokit";
|
|
|
7
7
|
interface Config extends Config$2, Config$1 {
|
|
8
8
|
access?: string | string[];
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* schema 解析之后的形状:`access` 是 **volatile 稳定引用**(页面可改的那一项),读它要过 `.get()`。
|
|
12
|
-
*
|
|
13
|
-
* 其余字段也是 volatile,但页面把它们画成**只读**(schema 上的 `disabled()`):它们是装配事实——runner 命令、
|
|
14
|
-
* 进程 cwd、上游基类读一次的超时与差额上限——放在页面上是为了看清「这一行现在装配成什么样」,改它们要重挂
|
|
15
|
-
* 这一行。所以给上游基类的那份配置是**解包后的值**({@link upstreamConfigOf}):基类读的是值,不是引用。
|
|
16
|
-
*/
|
|
17
10
|
interface ResolvedConfig extends Omit<Config, "access" | "runnerCommand" | "runnerFailureSignatures" | "probeTimeoutMs" | "cwd" | "diffBasisMaxBytes"> {
|
|
18
11
|
readonly access: Volatile<string | string[]>;
|
|
19
12
|
readonly runnerCommand: Volatile<readonly string[]>;
|
package/dist/index.mjs
CHANGED
|
@@ -6,15 +6,11 @@ import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
|
6
6
|
import { canonicalPath, writableRoots } from "@deepseek-ai/dsh-sandbox";
|
|
7
7
|
import { LocalSandboxProvider } from "@deepseek-ai/dsh-sandbox-local";
|
|
8
8
|
//#region src/config.ts
|
|
9
|
-
/**
|
|
10
|
-
* 本地化说明:`description()` 的类型签名只声明 `string`,而 meta 本身接受 `Dict<string>`
|
|
11
|
-
* (`vendor/schemastery/src/index.ts` 的 `mergeDesc` 就是按字典合并的),所以这里只做一次类型放行。
|
|
12
|
-
*/
|
|
13
9
|
const localized = (text) => text;
|
|
14
10
|
const Config = z.object({
|
|
15
11
|
access: z.union([z.array(z.string()), z.string()]).default([]).description(localized({
|
|
16
|
-
zh: "额外可写根与拒绝项,一行一条:`rw
|
|
17
|
-
en: "Extra writable roots and refusals, one entry per line: `rw
|
|
12
|
+
zh: "额外可写根与拒绝项,一行一条:`rw <路径>` 追加可写根,`r- <路径>` 只读,`-- <路径>` 拒绝;路径可用环境变量模板与 glob(`rw` 项必须是具体路径)。改它当场生效(沙箱规则每次按当前值重算),不用重挂这一行。",
|
|
13
|
+
en: "Extra writable roots and refusals, one entry per line: `rw <path>` grants a writable root, `r- <path>` makes it read-only, `-- <path>` denies it; env templates and globs work (`rw` entries must name a concrete path). Edits take effect on the next rule compile without remounting the row."
|
|
18
14
|
})).volatile(),
|
|
19
15
|
runnerCommand: z.array(z.string()).default([]).disabled().description(localized({
|
|
20
16
|
zh: "沙箱 runner 的启动命令;空数组表示按平台自带的链去选。改它要重挂这一行。",
|
|
@@ -37,14 +33,6 @@ const Config = z.object({
|
|
|
37
33
|
en: "Per-side byte cap for overwrite diffs (10 MiB by default)."
|
|
38
34
|
})).volatile()
|
|
39
35
|
});
|
|
40
|
-
/**
|
|
41
|
-
* 交给上游基类的那份配置:装配事实取当前值。
|
|
42
|
-
*
|
|
43
|
-
* 上游 `LocalSandboxProvider` / `LocalFileSystem` 在构造时读一次这些字段并做装配期校验,所以它们要的是值;
|
|
44
|
-
* 设置页改它们走的是「写盘 → Loader 重挂这一行 → 重新解析」这条路。
|
|
45
|
-
* @param config - 本行解析后的配置。
|
|
46
|
-
* @returns 上游两个基类认的普通配置对象。
|
|
47
|
-
*/
|
|
48
36
|
function upstreamConfigOf(config) {
|
|
49
37
|
return {
|
|
50
38
|
runnerCommand: [...config.runnerCommand.get()],
|
|
@@ -248,7 +236,6 @@ var ConfigurableFileSystem = class extends LocalFileSystem {
|
|
|
248
236
|
this.defaultMode = ctx.sandboxPolicy.defaultMode;
|
|
249
237
|
this.access = config.access;
|
|
250
238
|
}
|
|
251
|
-
/** 规则源现场重算:`access` 是 volatile 引用,页面改完下一次编译就是新规则。 */
|
|
252
239
|
get source() {
|
|
253
240
|
return ruleSourceOf(this.access.get(), process.env);
|
|
254
241
|
}
|
|
@@ -287,7 +274,6 @@ var ConfigurableFileSystem = class extends LocalFileSystem {
|
|
|
287
274
|
for (const root of writableRootsWith(rules, policy)) if (await isPathUnder(fresh.targetKey, root)) return fresh;
|
|
288
275
|
throw new FsError(`cannot write "${target.displayPath}": file access denied under workspace-write mode`, "FS_SANDBOX_DENIED");
|
|
289
276
|
}
|
|
290
|
-
/** 编译结果按「工作区 + 当前 access」缓存:access 是页面可改的引用,改了就是另一份规则。 */
|
|
291
277
|
rulesFor(workspaceRoot) {
|
|
292
278
|
const access = this.access.get();
|
|
293
279
|
const key = `${workspaceRoot}\u0000${JSON.stringify(access ?? null)}`;
|
|
@@ -300,74 +286,69 @@ var ConfigurableFileSystem = class extends LocalFileSystem {
|
|
|
300
286
|
};
|
|
301
287
|
//#endregion
|
|
302
288
|
//#region src/policy.ts
|
|
303
|
-
/**
|
|
304
|
-
* 接管 `sandbox:policy` 那条运行时上下文。
|
|
305
|
-
*
|
|
306
|
-
* 上游 [`@deepseek-ai/dsh-sandbox-policy`] 在**全局层**注册它,文本只描述官方策略(只读 /
|
|
307
|
-
* workspace-write / 全权 + workspace root)——它不知道本部署追加的 `rw` / `r-` / `--` 规则,
|
|
308
|
-
* 模型因此拿不到"哪些额外路径可写、哪些被拒"。本包替换了 `ctx.sandbox` / `ctx.fs`,这条文本也得跟着换。
|
|
309
|
-
*
|
|
310
|
-
* 全局层同名注册会抛错(`NamedEntries.insert`),上游给的官方路径是**按 agent 作用域覆盖**:
|
|
311
|
-
* `systemPrompt.context()` 在 scope 上注册同名项,装配时近的作用域遮蔽全局那条
|
|
312
|
-
* (`ScopedLayers.merge`)。所以这里在每个 agent 的 `ctx` 上注册一次。
|
|
313
|
-
*/
|
|
314
|
-
/** 上游注册的运行时上下文名:同名才叫接管。 */
|
|
315
289
|
const SANDBOX_POLICY_CONTEXT = "sandbox:policy";
|
|
316
|
-
|
|
317
|
-
* 策略文本:官方三种 mode 的语义(重写中文,与其它注入文案一致)+ 本部署追加的规则。
|
|
318
|
-
* @param policy - 该会话解析出来的策略(模式与 workspace root)。
|
|
319
|
-
* @param rules - 本部署解析后的访问规则(已展开环境变量模板)。
|
|
320
|
-
* @returns 给模型看的一段文本。
|
|
321
|
-
*/
|
|
290
|
+
const APPROVAL_POLICY_CONTEXT = "approval:policy";
|
|
322
291
|
function renderPolicyContext(policy, rules) {
|
|
323
292
|
const base = (() => {
|
|
324
293
|
switch (policy.mode) {
|
|
325
|
-
case "read-only": return "
|
|
326
|
-
case "workspace-write": return
|
|
327
|
-
case "danger-full-access": return "
|
|
294
|
+
case "read-only": return "文件策略:只读——不能修改任何文件。别因此拒绝必需的修改:照常尝试工具,按它的拒绝与升级指引行动。";
|
|
295
|
+
case "workspace-write": return `文件策略:workspace-write——可改会话工作区 ${JSON.stringify(policy.workspaceRoot)} 下的文件,部分平台临时目录同样可写。`;
|
|
296
|
+
case "danger-full-access": return "文件策略:全权——文件改动不再受限。";
|
|
328
297
|
}
|
|
329
298
|
})();
|
|
330
299
|
const extras = [];
|
|
331
|
-
if (rules.allowWrite.length > 0) extras.push(
|
|
332
|
-
if (rules.readOnly.length > 0) extras.push(
|
|
333
|
-
if (rules.deny.length > 0) extras.push(
|
|
300
|
+
if (rules.allowWrite.length > 0) extras.push(`额外可写:${rules.allowWrite.join("、")}。`);
|
|
301
|
+
if (rules.readOnly.length > 0) extras.push(`只读(不可写):${rules.readOnly.join("、")}。`);
|
|
302
|
+
if (rules.deny.length > 0) extras.push(`拒绝(读写都拒):${rules.deny.join("、")}。`);
|
|
334
303
|
return extras.length === 0 ? base : `${base} ${extras.join("")}`;
|
|
335
304
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
305
|
+
const APPROVAL_ASK_TEXT = "审批策略:ask——需要审批的操作会询问用户;没有可用的应答者时直接失败。";
|
|
306
|
+
const APPROVAL_NEVER_TEXT = "审批提示已禁用:需要审批的操作一律自动拒绝——不要请求沙箱升级(不要设 sandbox_permissions)。";
|
|
307
|
+
function renderApprovalContext(policy) {
|
|
308
|
+
return policy === "never" ? APPROVAL_NEVER_TEXT : APPROVAL_ASK_TEXT;
|
|
309
|
+
}
|
|
310
|
+
function effectiveApprovalPolicy(approval, session) {
|
|
311
|
+
return approval.overrideOf(session) ?? approval.config.policy ?? "ask";
|
|
312
|
+
}
|
|
342
313
|
function registerPolicyContext(scope, rules, resolve) {
|
|
343
|
-
scope.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
314
|
+
const prompt = scope.get("systemPrompt");
|
|
315
|
+
if (prompt === void 0) return;
|
|
316
|
+
prompt.context({
|
|
317
|
+
name: SANDBOX_POLICY_CONTEXT,
|
|
318
|
+
order: prompt.getContextOrder("SANDBOX_POLICY"),
|
|
319
|
+
text: (context) => {
|
|
320
|
+
const session = context.agent?.session;
|
|
321
|
+
return session === void 0 ? "" : renderPolicyContext(resolve(session), rules);
|
|
322
|
+
}
|
|
352
323
|
});
|
|
353
324
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
registered.add(agent);
|
|
366
|
-
registerPolicyContext(agent.ctx, rules, resolve);
|
|
325
|
+
function registerApprovalContext(scope) {
|
|
326
|
+
const prompt = scope.get("systemPrompt");
|
|
327
|
+
if (prompt === void 0) return;
|
|
328
|
+
prompt.context({
|
|
329
|
+
name: APPROVAL_POLICY_CONTEXT,
|
|
330
|
+
order: prompt.getContextOrder("APPROVAL_POLICY"),
|
|
331
|
+
text: (context) => {
|
|
332
|
+
const agent = context.agent;
|
|
333
|
+
if (agent === void 0) return "";
|
|
334
|
+
const approval = agent.ctx.get("approval");
|
|
335
|
+
return approval === void 0 ? "" : renderApprovalContext(effectiveApprovalPolicy(approval, agent.session));
|
|
367
336
|
}
|
|
368
|
-
return next();
|
|
369
337
|
});
|
|
370
338
|
}
|
|
339
|
+
function installRuntimeContexts(ctx, rules, resolve) {
|
|
340
|
+
const installed = /* @__PURE__ */ new WeakSet();
|
|
341
|
+
const install = (agent) => {
|
|
342
|
+
if (installed.has(agent)) return;
|
|
343
|
+
installed.add(agent);
|
|
344
|
+
registerPolicyContext(agent.ctx, rules, resolve);
|
|
345
|
+
registerApprovalContext(agent.ctx);
|
|
346
|
+
};
|
|
347
|
+
ctx.on("agent/created", ({ agent }) => {
|
|
348
|
+
install(agent);
|
|
349
|
+
});
|
|
350
|
+
for (const agent of ctx.get("agents")?.list() ?? []) install(agent);
|
|
351
|
+
}
|
|
371
352
|
//#endregion
|
|
372
353
|
//#region src/dialects.ts
|
|
373
354
|
const SEPARATOR = "--";
|
|
@@ -448,7 +429,6 @@ var ConfigurableSandboxProvider = class extends LocalSandboxProvider {
|
|
|
448
429
|
super(ctx, upstreamConfigOf(config));
|
|
449
430
|
this.access = config.access;
|
|
450
431
|
}
|
|
451
|
-
/** 规则源现场重算:`access` 是 volatile 引用,页面改完下一次编译就是新规则。 */
|
|
452
432
|
get source() {
|
|
453
433
|
return ruleSourceOf(this.access.get(), process.env);
|
|
454
434
|
}
|
|
@@ -462,7 +442,6 @@ var ConfigurableSandboxProvider = class extends LocalSandboxProvider {
|
|
|
462
442
|
argv: extendConfinedArgv(confined.argv, effective)
|
|
463
443
|
};
|
|
464
444
|
}
|
|
465
|
-
/** 编译结果按「工作区 + 当前 access」缓存:access 是页面可改的引用,改了就是另一份规则。 */
|
|
466
445
|
rulesFor(workspaceRoot) {
|
|
467
446
|
const access = this.access.get();
|
|
468
447
|
const key = `${workspaceRoot}\u0000${JSON.stringify(access ?? null)}`;
|
|
@@ -491,7 +470,7 @@ function apply(ctx, config) {
|
|
|
491
470
|
warnAboutDegradedRules(ctx, rules);
|
|
492
471
|
new ConfigurableSandboxProvider(ctx, config);
|
|
493
472
|
new ConfigurableFileSystem(ctx, config);
|
|
494
|
-
|
|
473
|
+
installRuntimeContexts(ctx, rules, (session) => ctx.sandboxPolicy.resolve({ session }));
|
|
495
474
|
}
|
|
496
475
|
//#endregion
|
|
497
476
|
export { Config, apply, inject, name };
|
package/dist/rows.d.cts
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
//#region src/rows.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* 本包作为能力包发布的**装配数据**:行本身在部署那一层装(`packages/bundles/sandbox-profile` 引用它渲染 patch)。
|
|
4
|
-
*
|
|
5
|
-
* 官方 `sandbox`(进程沙箱)与 `fs-sandbox`(文件围栏)两个服务只能有一份实现,所以那两行禁掉、换成
|
|
6
|
-
* 本包那一行;`config.access` 的值由部署那层给(schema 默认是空规则)。
|
|
7
|
-
*/
|
|
8
|
-
/** 一行装配条目:与 `cordis.patch.yml` 的顶层结构同形。 */
|
|
9
2
|
interface PatchRow {
|
|
10
3
|
readonly id?: string;
|
|
11
4
|
readonly disabled?: boolean;
|
|
12
5
|
readonly insert?: readonly RowEntry[];
|
|
13
6
|
}
|
|
14
|
-
/** `insert` 里的一个条目。 */
|
|
15
7
|
interface RowEntry {
|
|
16
8
|
readonly id: string;
|
|
17
9
|
readonly name: string;
|
|
18
10
|
readonly config?: Readonly<Record<string, unknown>>;
|
|
19
11
|
}
|
|
20
|
-
/** 沙箱替换的装配行:禁官方两行 + 插本包一行(值由部署那层按 id 覆盖)。 */
|
|
21
12
|
declare function sandboxRows(): readonly PatchRow[];
|
|
22
13
|
//#endregion
|
|
23
14
|
export { PatchRow, RowEntry, sandboxRows };
|
package/dist/rows.d.mts
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
//#region src/rows.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* 本包作为能力包发布的**装配数据**:行本身在部署那一层装(`packages/bundles/sandbox-profile` 引用它渲染 patch)。
|
|
4
|
-
*
|
|
5
|
-
* 官方 `sandbox`(进程沙箱)与 `fs-sandbox`(文件围栏)两个服务只能有一份实现,所以那两行禁掉、换成
|
|
6
|
-
* 本包那一行;`config.access` 的值由部署那层给(schema 默认是空规则)。
|
|
7
|
-
*/
|
|
8
|
-
/** 一行装配条目:与 `cordis.patch.yml` 的顶层结构同形。 */
|
|
9
2
|
interface PatchRow {
|
|
10
3
|
readonly id?: string;
|
|
11
4
|
readonly disabled?: boolean;
|
|
12
5
|
readonly insert?: readonly RowEntry[];
|
|
13
6
|
}
|
|
14
|
-
/** `insert` 里的一个条目。 */
|
|
15
7
|
interface RowEntry {
|
|
16
8
|
readonly id: string;
|
|
17
9
|
readonly name: string;
|
|
18
10
|
readonly config?: Readonly<Record<string, unknown>>;
|
|
19
11
|
}
|
|
20
|
-
/** 沙箱替换的装配行:禁官方两行 + 插本包一行(值由部署那层按 id 覆盖)。 */
|
|
21
12
|
declare function sandboxRows(): readonly PatchRow[];
|
|
22
13
|
//#endregion
|
|
23
14
|
export { PatchRow, RowEntry, sandboxRows };
|
package/dist/rows.mjs
CHANGED
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "Configurable Sandbox",
|
|
4
|
-
"description": "Replaces the shipped process sandbox and filesystem fence with implementations that add extra writable roots, read-only roots and access denials."
|
|
3
|
+
"title": "Configurable Sandbox & Approval",
|
|
4
|
+
"description": "Replaces the shipped process sandbox and filesystem fence with implementations that add extra writable roots, read-only roots and access denials, and owns the runtime-context snapshot (sandbox:policy / approval:policy) wording."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/dsh-sandbox-local",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Configurable sandbox bundle: replaces the shipped process-sandbox provider and filesystem fence with implementations that add extra writable roots, read-only roots and access denials on top of the upstream semantics.",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "Configurable sandbox bundle: replaces the shipped process-sandbox provider and filesystem fence with implementations that add extra writable roots, read-only roots and access denials on top of the upstream semantics, and owns the runtime-context snapshot (`sandbox:policy` / `approval:policy`) with the deployment's own wording.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
7
7
|
"dsh-bundle",
|
|
@@ -34,36 +34,38 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@deepseek-ai/cordis": "4.0.4",
|
|
36
36
|
"@deepseek-ai/cosmokit": "1.8.5",
|
|
37
|
-
"@deepseek-ai/dsh-agent": "0.
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
39
|
-
"@deepseek-ai/dsh-client-
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
41
|
-
"@deepseek-ai/dsh-fs
|
|
42
|
-
"@deepseek-ai/dsh-fs-
|
|
43
|
-
"@deepseek-ai/dsh-sandbox": "0.
|
|
44
|
-
"@deepseek-ai/dsh-sandbox
|
|
45
|
-
"@deepseek-ai/dsh-sandbox-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@
|
|
37
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "0.2.0-rc.1",
|
|
39
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-fs-local": "0.2.0-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-fs-sandbox": "0.2.0-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-sandbox-local": "0.2.0-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-scope": "0.2.0-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-user-approval": "0.2.0-rc.1",
|
|
51
|
+
"@morlay/dsh-client-ui-primitives": "0.0.5"
|
|
50
52
|
},
|
|
51
53
|
"peerDependencies": {
|
|
52
54
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
53
55
|
"@deepseek-ai/cosmokit": "^1.8.5",
|
|
54
|
-
"@deepseek-ai/dsh-agent": "^0.
|
|
55
|
-
"@deepseek-ai/dsh-client-locale": "^0.
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.
|
|
57
|
-
"@deepseek-ai/dsh-fs": "^0.
|
|
58
|
-
"@deepseek-ai/dsh-fs-local": "^0.
|
|
59
|
-
"@deepseek-ai/dsh-fs-sandbox": "^0.
|
|
60
|
-
"@deepseek-ai/dsh-sandbox": "^0.
|
|
61
|
-
"@deepseek-ai/dsh-sandbox-local": "^0.
|
|
62
|
-
"@deepseek-ai/dsh-sandbox-policy": "^0.
|
|
63
|
-
"@deepseek-ai/dsh-session": "^0.
|
|
64
|
-
"@deepseek-ai/dsh-system-prompt": "^0.
|
|
56
|
+
"@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-fs": "^0.2.0-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-fs-local": "^0.2.0-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-fs-sandbox": "^0.2.0-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-sandbox": "^0.2.0-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-sandbox-local": "^0.2.0-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.2.0-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-session": "^0.2.0-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.1",
|
|
65
67
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
66
|
-
"@morlay/dsh-client-ui-primitives": "0.0.
|
|
68
|
+
"@morlay/dsh-client-ui-primitives": "0.0.5"
|
|
67
69
|
},
|
|
68
70
|
"dsh": {
|
|
69
71
|
"client": {
|
package/src/client/index.ts
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* 当场重算),本包只把键名换成中文标签与说明。
|
|
4
|
-
*/
|
|
1
|
+
// client 半:这一行的配置页由通用 schema 表单按 volatile 字段生成(`access` 是页面可改的那一项,改完规则
|
|
2
|
+
// 当场重算),本包只把键名换成中文标签与说明。
|
|
5
3
|
|
|
6
4
|
import type { Context } from "@deepseek-ai/cordis";
|
|
7
5
|
import type {} from "@deepseek-ai/dsh-client-locale/client";
|
|
8
6
|
import type {} from "@deepseek-ai/dsh-client-ui-slots";
|
|
9
7
|
import { en, zh, type SandboxFieldLocaleKey } from "./locales.ts";
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
// 本包 host 行 id:行配置页读的就是这个命名空间。
|
|
12
10
|
export const SANDBOX_NS = "sandbox-local";
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
// 这一行里需要中文文案的字段。
|
|
15
13
|
const FIELDS = ["access"] as const;
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
// 本页字典的命名空间。
|
|
18
16
|
export const NS = "settings.sandbox-local";
|
|
19
17
|
|
|
20
18
|
declare module "@deepseek-ai/dsh-client-ui-slots" {
|
|
21
19
|
interface LocaleNamespaceMap {
|
|
22
|
-
|
|
20
|
+
// 设置页里这一行的字段文案。
|
|
23
21
|
"settings.sandbox-local": SandboxFieldLocaleKey;
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
// 需要的服务:槽位(字段槽)与字典。
|
|
28
26
|
export const inject = ["locale"];
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
* 给 `access` 补文案。
|
|
32
|
-
* @param ctx - 浏览器插件上下文。
|
|
33
|
-
*/
|
|
28
|
+
// 给 `access` 补文案。
|
|
34
29
|
export function apply(ctx: Context): void {
|
|
35
30
|
const t = ctx.locale.bind(NS);
|
|
36
31
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "sandbox-local: field locale");
|
package/src/client/locales.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// 本包在配置页上的字段文案(命名空间 `settings.sandbox-local`)。
|
|
2
2
|
|
|
3
3
|
export const zh = {
|
|
4
4
|
access: "额外可写根与拒绝项",
|
|
5
5
|
accessHint:
|
|
6
|
-
"一行一条:`rw
|
|
6
|
+
"一行一条:`rw <路径>` 追加可写根,`r- <路径>` 只读,`-- <路径>` 拒绝;支持环境变量模板与 glob(`rw` 项必须是具体路径)。改完当场生效。",
|
|
7
7
|
} as const;
|
|
8
8
|
|
|
9
9
|
export const en: Record<keyof typeof zh, string> = {
|
|
10
10
|
access: "Extra writable roots and refusals",
|
|
11
11
|
accessHint:
|
|
12
|
-
"One entry per line: `rw
|
|
12
|
+
"One entry per line: `rw <path>` grants a writable root, `r- <path>` makes it read-only, `-- <path>` denies it; env templates and globs work (`rw` entries must name a concrete path). Edits apply immediately.",
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export type SandboxFieldLocaleKey = keyof typeof zh;
|
package/src/config.ts
CHANGED
|
@@ -7,13 +7,9 @@ export interface Config extends UpstreamSandboxConfig, UpstreamFsConfig {
|
|
|
7
7
|
access?: string | string[];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* 其余字段也是 volatile,但页面把它们画成**只读**(schema 上的 `disabled()`):它们是装配事实——runner 命令、
|
|
14
|
-
* 进程 cwd、上游基类读一次的超时与差额上限——放在页面上是为了看清「这一行现在装配成什么样」,改它们要重挂
|
|
15
|
-
* 这一行。所以给上游基类的那份配置是**解包后的值**({@link upstreamConfigOf}):基类读的是值,不是引用。
|
|
16
|
-
*/
|
|
10
|
+
// schema 解析之后的形状:`access` 是 volatile 稳定引用(读它要过 `.get()`);其余字段也是 volatile,
|
|
11
|
+
// 但 schema 上标了 `disabled()`,页面把它们画成**只读**的装配事实。所以给上游基类的那份配置是
|
|
12
|
+
// **解包后的值**(`upstreamConfigOf`)——基类读的是值,不是引用。
|
|
17
13
|
export interface ResolvedConfig extends Omit<
|
|
18
14
|
Config,
|
|
19
15
|
| "access"
|
|
@@ -31,10 +27,8 @@ export interface ResolvedConfig extends Omit<
|
|
|
31
27
|
readonly diffBasisMaxBytes: Volatile<number>;
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* (`vendor/schemastery/src/index.ts` 的 `mergeDesc` 就是按字典合并的),所以这里只做一次类型放行。
|
|
37
|
-
*/
|
|
30
|
+
// 本地化说明:`description()` 的类型签名只声明 `string`,而 meta 本身接受 `Dict<string>`
|
|
31
|
+
// (`vendor/schemastery/src/index.ts` 的 `mergeDesc` 就是按字典合并的),所以这里只做一次类型放行。
|
|
38
32
|
const localized = (text: { zh: string; en: string }): string => text as unknown as string;
|
|
39
33
|
|
|
40
34
|
export const Config: z<Config, ResolvedConfig> = z.object({
|
|
@@ -44,11 +38,12 @@ export const Config: z<Config, ResolvedConfig> = z.object({
|
|
|
44
38
|
.description(
|
|
45
39
|
localized({
|
|
46
40
|
zh:
|
|
47
|
-
"额外可写根与拒绝项,一行一条:`rw
|
|
48
|
-
"
|
|
41
|
+
"额外可写根与拒绝项,一行一条:`rw <路径>` 追加可写根,`r- <路径>` 只读,`-- <路径>` 拒绝;" +
|
|
42
|
+
"路径可用环境变量模板与 glob(`rw` 项必须是具体路径)。改它当场生效(沙箱规则每次按当前值重算),不用重挂这一行。",
|
|
49
43
|
en:
|
|
50
|
-
"Extra writable roots and refusals, one entry per line: `rw
|
|
51
|
-
"`r
|
|
44
|
+
"Extra writable roots and refusals, one entry per line: `rw <path>` grants a writable root, " +
|
|
45
|
+
"`r- <path>` makes it read-only, `-- <path>` denies it; env templates and globs work " +
|
|
46
|
+
"(`rw` entries must name a concrete path). " +
|
|
52
47
|
"Edits take effect on the next rule compile without remounting the row.",
|
|
53
48
|
}),
|
|
54
49
|
)
|
|
@@ -110,14 +105,8 @@ export const Config: z<Config, ResolvedConfig> = z.object({
|
|
|
110
105
|
.volatile(),
|
|
111
106
|
});
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*
|
|
116
|
-
* 上游 `LocalSandboxProvider` / `LocalFileSystem` 在构造时读一次这些字段并做装配期校验,所以它们要的是值;
|
|
117
|
-
* 设置页改它们走的是「写盘 → Loader 重挂这一行 → 重新解析」这条路。
|
|
118
|
-
* @param config - 本行解析后的配置。
|
|
119
|
-
* @returns 上游两个基类认的普通配置对象。
|
|
120
|
-
*/
|
|
108
|
+
// 交给上游基类的配置:装配事实取当前值——基类在构造时读一次这些字段并做装配期校验,所以它们要的是值;
|
|
109
|
+
// 设置页改它们走「写盘 → Loader 重挂这一行 → 重新解析」。返回上游两个基类认的普通配置对象。
|
|
121
110
|
export function upstreamConfigOf(config: ResolvedConfig): UpstreamSandboxConfig & UpstreamFsConfig {
|
|
122
111
|
return {
|
|
123
112
|
runnerCommand: [...config.runnerCommand.get()],
|
package/src/fs.ts
CHANGED
|
@@ -37,7 +37,7 @@ export class ConfigurableFileSystem extends LocalFileSystem {
|
|
|
37
37
|
this.access = config.access;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// 规则源现场重算:`access` 是 volatile 引用,页面改完下一次编译就是新规则。
|
|
41
41
|
private get source(): RuleSource {
|
|
42
42
|
return ruleSourceOf(this.access.get(), process.env);
|
|
43
43
|
}
|
|
@@ -134,7 +134,7 @@ export class ConfigurableFileSystem extends LocalFileSystem {
|
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
// 编译结果按「工作区 + 当前 access」缓存:access 是页面可改的引用,改了就是另一份规则。
|
|
138
138
|
private rulesFor(workspaceRoot: string): CompiledRules {
|
|
139
139
|
const access = this.access.get();
|
|
140
140
|
const key = `${workspaceRoot}\u0000${JSON.stringify(access ?? null)}`;
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
import type { ResolvedConfig } from "./config.ts";
|
|
3
3
|
import { ConfigurableFileSystem } from "./fs.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { installRuntimeContexts } from "./policy.ts";
|
|
5
5
|
import { ruleSourceOf, type RuleSource } from "./rules.ts";
|
|
6
6
|
import { ConfigurableSandboxProvider } from "./sandbox.ts";
|
|
7
7
|
|
|
@@ -39,6 +39,7 @@ export function apply(ctx: Context, config: ResolvedConfig): void {
|
|
|
39
39
|
warnAboutDegradedRules(ctx, rules);
|
|
40
40
|
new ConfigurableSandboxProvider(ctx, config);
|
|
41
41
|
new ConfigurableFileSystem(ctx, config);
|
|
42
|
-
// 替换了 ctx.sandbox / ctx.fs,`sandbox:policy`
|
|
43
|
-
|
|
42
|
+
// 替换了 ctx.sandbox / ctx.fs,`sandbox:policy` 那段运行时文本也要跟着换,`approval:policy` 换成中文
|
|
43
|
+
// (见 policy.ts)。
|
|
44
|
+
installRuntimeContexts(ctx, rules, (session) => ctx.sandboxPolicy.resolve({ session }));
|
|
44
45
|
}
|
package/src/policy.ts
CHANGED
|
@@ -3,96 +3,126 @@ import type { Context } from "@deepseek-ai/cordis";
|
|
|
3
3
|
import type { SandboxExecutionPolicy } from "@deepseek-ai/dsh-sandbox";
|
|
4
4
|
import type { Session } from "@deepseek-ai/dsh-session";
|
|
5
5
|
import type {} from "@deepseek-ai/dsh-system-prompt";
|
|
6
|
+
// Type-only:`approval:policy` 的取值面(`ApprovalPolicy` 与 `ApprovalService.overrideOf`)。
|
|
7
|
+
import type { ApprovalPolicy } from "@deepseek-ai/dsh-user-approval";
|
|
6
8
|
import type { RuleSource } from "./rules.ts";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* `systemPrompt.context()` 在 scope 上注册同名项,装配时近的作用域遮蔽全局那条
|
|
17
|
-
* (`ScopedLayers.merge`)。所以这里在每个 agent 的 `ctx` 上注册一次。
|
|
18
|
-
*/
|
|
10
|
+
// 接管两条运行时上下文:上游在**全局层**注册的 `sandbox:policy`(`@deepseek-ai/dsh-sandbox-policy`)与
|
|
11
|
+
// `approval:policy`(`@deepseek-ai/dsh-user-approval`)。本部署换了沙箱实现、也要自己的中文措辞,两条都在
|
|
12
|
+
// agent 自己的 `ctx` 上注册**同名** context——装配时近的作用域遮蔽全局那条(`ScopedLayers.merge`),全局那条
|
|
13
|
+
// 对 agentless 装配仍然生效。全局层同名注册会抛错(`NamedEntries.insert`),作用域遮蔽是上游给的官方路径。
|
|
14
|
+
//
|
|
15
|
+
// 时机:注册必须早于该 agent 的**第一次**装配。`SystemPrompt.assemble()` 先把 `contexts` merge 好、再进
|
|
16
|
+
// `system-prompt/assemble` 瀑布,所以挂在瀑布里注册只能从第二次装配起生效(会话里表现为"先英文后中文");
|
|
17
|
+
// `agent/created` 是创建期最后一步、早于第一次装配,在那里注册即可(persona 用的是同一手法)。
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
// 上游注册的运行时上下文名:同名才叫接管。
|
|
21
20
|
export const SANDBOX_POLICY_CONTEXT = "sandbox:policy";
|
|
21
|
+
export const APPROVAL_POLICY_CONTEXT = "approval:policy";
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
* 策略文本:官方三种 mode 的语义(重写中文,与其它注入文案一致)+ 本部署追加的规则。
|
|
25
|
-
* @param policy - 该会话解析出来的策略(模式与 workspace root)。
|
|
26
|
-
* @param rules - 本部署解析后的访问规则(已展开环境变量模板)。
|
|
27
|
-
* @returns 给模型看的一段文本。
|
|
28
|
-
*/
|
|
23
|
+
// 策略文本:官方三种 mode 的中文语义 + 本部署追加的规则;返回给模型看的一段文本。
|
|
29
24
|
export function renderPolicyContext(policy: SandboxExecutionPolicy, rules: RuleSource): string {
|
|
30
25
|
const base = ((): string => {
|
|
31
26
|
switch (policy.mode) {
|
|
32
27
|
case "read-only":
|
|
33
|
-
return "
|
|
28
|
+
return "文件策略:只读——不能修改任何文件。别因此拒绝必需的修改:照常尝试工具,按它的拒绝与升级指引行动。";
|
|
34
29
|
case "workspace-write":
|
|
35
|
-
return
|
|
30
|
+
return `文件策略:workspace-write——可改会话工作区 ${JSON.stringify(policy.workspaceRoot)} 下的文件,部分平台临时目录同样可写。`;
|
|
36
31
|
case "danger-full-access":
|
|
37
|
-
return "
|
|
32
|
+
return "文件策略:全权——文件改动不再受限。";
|
|
38
33
|
}
|
|
39
34
|
})();
|
|
40
35
|
|
|
41
36
|
const extras: string[] = [];
|
|
42
37
|
if (rules.allowWrite.length > 0) {
|
|
43
|
-
extras.push(
|
|
38
|
+
extras.push(`额外可写:${rules.allowWrite.join("、")}。`);
|
|
44
39
|
}
|
|
45
40
|
if (rules.readOnly.length > 0) {
|
|
46
|
-
extras.push(
|
|
41
|
+
extras.push(`只读(不可写):${rules.readOnly.join("、")}。`);
|
|
47
42
|
}
|
|
48
43
|
if (rules.deny.length > 0) {
|
|
49
|
-
extras.push(
|
|
44
|
+
extras.push(`拒绝(读写都拒):${rules.deny.join("、")}。`);
|
|
50
45
|
}
|
|
51
46
|
return extras.length === 0 ? base : `${base} ${extras.join("")}`;
|
|
52
47
|
}
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export function
|
|
49
|
+
// `approval:policy` 的两段文本。`never` 是确定性拒绝,所以顺带告诉模型别去要沙箱升级。
|
|
50
|
+
const APPROVAL_ASK_TEXT = "审批策略:ask——需要审批的操作会询问用户;没有可用的应答者时直接失败。";
|
|
51
|
+
const APPROVAL_NEVER_TEXT =
|
|
52
|
+
"审批提示已禁用:需要审批的操作一律自动拒绝——不要请求沙箱升级(不要设 sandbox_permissions)。";
|
|
53
|
+
|
|
54
|
+
// 策略文本按会话的有效策略选段。
|
|
55
|
+
export function renderApprovalContext(policy: ApprovalPolicy): string {
|
|
56
|
+
return policy === "never" ? APPROVAL_NEVER_TEXT : APPROVAL_ASK_TEXT;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 上游 `ApprovalService` 的公开面:`overrideOf`(会话日志里最后一条 `approval/policy`)与 `config.policy`
|
|
60
|
+
// (部署默认)。`effectivePolicy` 是私有的,所以在这里复刻它的同一算法(override 优先,否则 config 默认,
|
|
61
|
+
// 再退到 `ask`——schema 已经把缺省填成 `ask`,这一层只是兜住手写的装配 config)。
|
|
62
|
+
interface ApprovalSource {
|
|
63
|
+
overrideOf(session: Session): ApprovalPolicy | undefined;
|
|
64
|
+
readonly config: { readonly policy?: ApprovalPolicy };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 某个会话此刻生效的审批策略。
|
|
68
|
+
function effectiveApprovalPolicy(approval: ApprovalSource, session: Session): ApprovalPolicy {
|
|
69
|
+
return approval.overrideOf(session) ?? approval.config.policy ?? "ask";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 在给定作用域注册同名策略文本(agent 的 `ctx`)——该 scope 因此遮蔽全局那条。
|
|
73
|
+
// `ctx.get` 而不是 `ctx.systemPrompt`:本行没在 `inject` 里点名 systemPrompt,属性访问会被 cordis 拒绝。
|
|
74
|
+
function registerPolicyContext(
|
|
61
75
|
scope: Context,
|
|
62
76
|
rules: RuleSource,
|
|
63
77
|
resolve: (session: Session) => SandboxExecutionPolicy,
|
|
64
78
|
): void {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
79
|
+
const prompt = scope.get("systemPrompt");
|
|
80
|
+
if (prompt === undefined) return;
|
|
81
|
+
prompt.context({
|
|
82
|
+
name: SANDBOX_POLICY_CONTEXT,
|
|
83
|
+
order: prompt.getContextOrder("SANDBOX_POLICY"),
|
|
84
|
+
text: (context) => {
|
|
85
|
+
const session = context.agent?.session;
|
|
86
|
+
return session === undefined ? "" : renderPolicyContext(resolve(session), rules);
|
|
87
|
+
},
|
|
75
88
|
});
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
// 同上,接管 `approval:policy`:读的是上游服务公开面里的有效策略,文本按它选段。
|
|
92
|
+
function registerApprovalContext(scope: Context): void {
|
|
93
|
+
const prompt = scope.get("systemPrompt");
|
|
94
|
+
if (prompt === undefined) return;
|
|
95
|
+
prompt.context({
|
|
96
|
+
name: APPROVAL_POLICY_CONTEXT,
|
|
97
|
+
order: prompt.getContextOrder("APPROVAL_POLICY"),
|
|
98
|
+
text: (context) => {
|
|
99
|
+
const agent = context.agent;
|
|
100
|
+
if (agent === undefined) return "";
|
|
101
|
+
// 审批能力是可选的(headless 部署可能没装):没装就按"没有这条"读,空串遮蔽不了任何东西。
|
|
102
|
+
const approval = agent.ctx.get("approval") as ApprovalSource | undefined;
|
|
103
|
+
return approval === undefined
|
|
104
|
+
? ""
|
|
105
|
+
: renderApprovalContext(effectiveApprovalPolicy(approval, agent.session));
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 每个 agent 注册一次,时机是它创建时——必须早于第一次装配(见文件头)。
|
|
111
|
+
export function installRuntimeContexts(
|
|
85
112
|
ctx: Context,
|
|
86
113
|
rules: RuleSource,
|
|
87
114
|
resolve: (session: Session) => SandboxExecutionPolicy,
|
|
88
115
|
): void {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
const installed = new WeakSet<Agent>();
|
|
117
|
+
const install = (agent: Agent): void => {
|
|
118
|
+
if (installed.has(agent)) return;
|
|
119
|
+
installed.add(agent);
|
|
120
|
+
registerPolicyContext(agent.ctx, rules, resolve);
|
|
121
|
+
registerApprovalContext(agent.ctx);
|
|
122
|
+
};
|
|
123
|
+
ctx.on("agent/created", ({ agent }) => {
|
|
124
|
+
install(agent);
|
|
97
125
|
});
|
|
126
|
+
// 本行晚于已有 agent 挂载时(行重挂 / HMR)也要覆盖它们:`agent/created` 不会再为它们发一次。
|
|
127
|
+
for (const agent of ctx.get("agents")?.list() ?? []) install(agent);
|
|
98
128
|
}
|
package/src/rows.ts
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 官方 `sandbox`(进程沙箱)与 `fs-sandbox`(文件围栏)两个服务只能有一份实现,所以那两行禁掉、换成
|
|
5
|
-
* 本包那一行;`config.access` 的值由部署那层给(schema 默认是空规则)。
|
|
6
|
-
*/
|
|
1
|
+
// 本包作为能力包发布的**装配数据**:行本身在部署那一层装(`packages/bundles/sandbox-profile` 引用它
|
|
2
|
+
// 渲染 patch)。官方 `sandbox` 与 `fs-sandbox` 两个服务只能有一份实现,所以那两行禁掉、换成本包那一行;
|
|
3
|
+
// `config.access` 的值由部署那层给(schema 默认是空规则)。
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
// 一行装配条目:与 `cordis.patch.yml` 的顶层结构同形。
|
|
9
6
|
export interface PatchRow {
|
|
10
7
|
readonly id?: string;
|
|
11
8
|
readonly disabled?: boolean;
|
|
12
9
|
readonly insert?: readonly RowEntry[];
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
// `insert` 里的一个条目。
|
|
16
13
|
export interface RowEntry {
|
|
17
14
|
readonly id: string;
|
|
18
15
|
readonly name: string;
|
|
19
16
|
readonly config?: Readonly<Record<string, unknown>>;
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
// 沙箱替换的装配行:禁官方两行 + 插本包一行(值由部署那层按 id 覆盖)。
|
|
23
20
|
export function sandboxRows(): readonly PatchRow[] {
|
|
24
21
|
return [
|
|
25
22
|
{ id: "sandbox", disabled: true },
|
package/src/sandbox.ts
CHANGED
|
@@ -23,7 +23,7 @@ export class ConfigurableSandboxProvider extends LocalSandboxProvider {
|
|
|
23
23
|
this.access = config.access;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// 规则源现场重算:`access` 是 volatile 引用,页面改完下一次编译就是新规则。
|
|
27
27
|
private get source(): RuleSource {
|
|
28
28
|
return ruleSourceOf(this.access.get(), process.env);
|
|
29
29
|
}
|
|
@@ -40,7 +40,7 @@ export class ConfigurableSandboxProvider extends LocalSandboxProvider {
|
|
|
40
40
|
return { ...confined, argv: extendConfinedArgv(confined.argv, effective) };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// 编译结果按「工作区 + 当前 access」缓存:access 是页面可改的引用,改了就是另一份规则。
|
|
44
44
|
private rulesFor(workspaceRoot: string): CompiledRules {
|
|
45
45
|
const access = this.access.get();
|
|
46
46
|
const key = `${workspaceRoot}\u0000${JSON.stringify(access ?? null)}`;
|