@downcity/agent 1.1.96 → 1.1.99
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/bin/executor/composer/system/default/assets/core.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.d.ts.map +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js.map +1 -1
- package/bin/executor/tools/shell/ShellToolBridge.d.ts.map +1 -1
- package/bin/executor/tools/shell/ShellToolBridge.js +14 -0
- package/bin/executor/tools/shell/ShellToolBridge.js.map +1 -1
- package/bin/executor/tools/shell/types/ShellPlugin.d.ts +8 -0
- package/bin/executor/tools/shell/types/ShellPlugin.d.ts.map +1 -1
- package/bin/index.d.ts +1 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/plugin/core/ImagePlugin.d.ts +3 -64
- package/bin/plugin/core/ImagePlugin.d.ts.map +1 -1
- package/bin/plugin/core/ImagePlugin.js +12 -232
- package/bin/plugin/core/ImagePlugin.js.map +1 -1
- package/bin/sandbox/LinuxBubblewrapSandbox.d.ts +1 -3
- package/bin/sandbox/LinuxBubblewrapSandbox.d.ts.map +1 -1
- package/bin/sandbox/LinuxBubblewrapSandbox.js +31 -30
- package/bin/sandbox/LinuxBubblewrapSandbox.js.map +1 -1
- package/bin/sandbox/MacOsSeatbeltSandbox.d.ts +1 -1
- package/bin/sandbox/MacOsSeatbeltSandbox.d.ts.map +1 -1
- package/bin/sandbox/MacOsSeatbeltSandbox.js +30 -29
- package/bin/sandbox/MacOsSeatbeltSandbox.js.map +1 -1
- package/bin/sandbox/SandboxConfigResolver.d.ts +1 -0
- package/bin/sandbox/SandboxConfigResolver.d.ts.map +1 -1
- package/bin/sandbox/SandboxConfigResolver.js +13 -3
- package/bin/sandbox/SandboxConfigResolver.js.map +1 -1
- package/bin/sandbox/SandboxRunner.d.ts +17 -4
- package/bin/sandbox/SandboxRunner.d.ts.map +1 -1
- package/bin/sandbox/SandboxRunner.js +20 -5
- package/bin/sandbox/SandboxRunner.js.map +1 -1
- package/bin/sandbox/types/SandboxRuntime.d.ts +46 -6
- package/bin/sandbox/types/SandboxRuntime.d.ts.map +1 -1
- package/bin/sandbox/types/SandboxRuntime.js +2 -2
- package/bin/types/plugin/ImagePlugin.d.ts +2 -79
- package/bin/types/plugin/ImagePlugin.d.ts.map +1 -1
- package/package.json +2 -2
- package/scripts/image-plugin-job.test.mjs +21 -108
- package/scripts/linux-bubblewrap-sandbox.test.mjs +23 -14
- package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/core.prompt.ts.txt +5 -0
- package/src/executor/tools/shell/ShellToolBridge.ts +14 -0
- package/src/executor/tools/shell/types/ShellPlugin.ts +8 -0
- package/src/index.ts +0 -5
- package/src/plugin/core/ImagePlugin.ts +13 -286
- package/src/sandbox/LinuxBubblewrapSandbox.ts +35 -43
- package/src/sandbox/MacOsSeatbeltSandbox.ts +35 -41
- package/src/sandbox/SandboxConfigResolver.ts +15 -3
- package/src/sandbox/SandboxRunner.ts +32 -7
- package/src/sandbox/types/SandboxRuntime.ts +54 -6
- package/src/types/plugin/ImagePlugin.ts +2 -79
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 基于 `bwrap` 提供 Linux 本机 shell sandbox。
|
|
6
6
|
* - 继续保持“shell 命令必须进入 sandbox”的安全语义,不提供宿主机裸跑回退。
|
|
7
|
-
* - 边界与 macOS backend
|
|
7
|
+
* - 边界与 macOS backend 对齐:路径、环境变量、网络、agent 级共享 HOME/TMPDIR/cache。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
@@ -34,8 +34,9 @@ function dedupeExistingPaths(values: string[]): string[] {
|
|
|
34
34
|
function buildReadablePaths(params: {
|
|
35
35
|
rootPath: string;
|
|
36
36
|
shellPath: string;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
sandboxDir: string;
|
|
38
|
+
tmpDir: string;
|
|
39
|
+
cacheDir: string;
|
|
39
40
|
}): string[] {
|
|
40
41
|
return dedupeExistingPaths([
|
|
41
42
|
"/usr",
|
|
@@ -45,21 +46,20 @@ function buildReadablePaths(params: {
|
|
|
45
46
|
"/lib64",
|
|
46
47
|
"/etc",
|
|
47
48
|
params.rootPath,
|
|
48
|
-
params.
|
|
49
|
-
params.
|
|
49
|
+
params.sandboxDir,
|
|
50
|
+
params.tmpDir,
|
|
51
|
+
params.cacheDir,
|
|
50
52
|
path.dirname(params.shellPath),
|
|
51
53
|
]);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
function buildWritablePaths(params: SandboxSpawnParams
|
|
55
|
-
shellHomeDir: string;
|
|
56
|
-
shellTmpDir: string;
|
|
57
|
-
}): string[] {
|
|
56
|
+
function buildWritablePaths(params: SandboxSpawnParams): string[] {
|
|
58
57
|
return dedupeExistingPaths([
|
|
59
58
|
...params.config.writablePaths,
|
|
60
|
-
params.
|
|
61
|
-
params.
|
|
62
|
-
params.
|
|
59
|
+
params.executionDir,
|
|
60
|
+
params.config.sandboxDir,
|
|
61
|
+
params.config.tmpDir,
|
|
62
|
+
params.config.cacheDir,
|
|
63
63
|
]);
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -73,10 +73,7 @@ function isPathCoveredBy(paths: string[], targetPath: string): boolean {
|
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function buildSandboxEnv(params: SandboxSpawnParams
|
|
77
|
-
shellHomeDir: string;
|
|
78
|
-
shellTmpDir: string;
|
|
79
|
-
}): NodeJS.ProcessEnv {
|
|
76
|
+
function buildSandboxEnv(params: SandboxSpawnParams): NodeJS.ProcessEnv {
|
|
80
77
|
const env: NodeJS.ProcessEnv = {};
|
|
81
78
|
for (const key of params.config.envAllowlist) {
|
|
82
79
|
const value = params.baseEnv[key];
|
|
@@ -91,8 +88,13 @@ function buildSandboxEnv(params: SandboxSpawnParams & {
|
|
|
91
88
|
}
|
|
92
89
|
|
|
93
90
|
env.PATH = String(env.PATH || params.baseEnv.PATH || DEFAULT_PATH_VALUE);
|
|
94
|
-
env.HOME = params.
|
|
95
|
-
env.TMPDIR = params.
|
|
91
|
+
env.HOME = params.config.homeDir;
|
|
92
|
+
env.TMPDIR = params.config.tmpDir;
|
|
93
|
+
env.XDG_CACHE_HOME = params.config.cacheDir;
|
|
94
|
+
env.DC_SANDBOX = "1";
|
|
95
|
+
env.DC_SANDBOX_DIR = params.config.sandboxDir;
|
|
96
|
+
env.DC_SANDBOX_HOME = params.config.homeDir;
|
|
97
|
+
env.DC_SANDBOX_CACHE = params.config.cacheDir;
|
|
96
98
|
env.SHELL = params.shellPath;
|
|
97
99
|
|
|
98
100
|
return env;
|
|
@@ -119,20 +121,15 @@ function addParentDirs(args: string[], targetPath: string, createdDirs: Set<stri
|
|
|
119
121
|
|
|
120
122
|
export function buildLinuxBubblewrapArgs(params: SandboxSpawnParams & {
|
|
121
123
|
actualCwd: string;
|
|
122
|
-
shellHomeDir: string;
|
|
123
|
-
shellTmpDir: string;
|
|
124
124
|
}): string[] {
|
|
125
125
|
const readablePaths = buildReadablePaths({
|
|
126
126
|
rootPath: params.config.rootPath,
|
|
127
127
|
shellPath: params.shellPath,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const writablePaths = buildWritablePaths({
|
|
132
|
-
...params,
|
|
133
|
-
shellHomeDir: params.shellHomeDir,
|
|
134
|
-
shellTmpDir: params.shellTmpDir,
|
|
128
|
+
sandboxDir: params.config.sandboxDir,
|
|
129
|
+
tmpDir: params.config.tmpDir,
|
|
130
|
+
cacheDir: params.config.cacheDir,
|
|
135
131
|
});
|
|
132
|
+
const writablePaths = buildWritablePaths(params);
|
|
136
133
|
const writableSet = new Set(writablePaths);
|
|
137
134
|
const createdDirs = new Set<string>();
|
|
138
135
|
const mountedPaths: string[] = [];
|
|
@@ -159,9 +156,8 @@ export function buildLinuxBubblewrapArgs(params: SandboxSpawnParams & {
|
|
|
159
156
|
}
|
|
160
157
|
|
|
161
158
|
for (const writablePath of writablePaths) {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
}
|
|
159
|
+
if (isPathCoveredBy(mountedPaths, writablePath)) continue;
|
|
160
|
+
addParentDirs(args, writablePath, createdDirs);
|
|
165
161
|
addWritableBind(args, writablePath);
|
|
166
162
|
mountedPaths.push(writablePath);
|
|
167
163
|
}
|
|
@@ -192,28 +188,20 @@ export function buildLinuxBubblewrapArgs(params: SandboxSpawnParams & {
|
|
|
192
188
|
export async function spawnLinuxBubblewrapSandbox(
|
|
193
189
|
params: SandboxSpawnParams & { actualCwd: string },
|
|
194
190
|
): Promise<SandboxSpawnResult> {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
await fs.ensureDir(shellHomeDir);
|
|
200
|
-
await fs.ensureDir(shellTmpDir);
|
|
191
|
+
await fs.ensureDir(params.config.sandboxDir);
|
|
192
|
+
await fs.ensureDir(params.config.tmpDir);
|
|
193
|
+
await fs.ensureDir(params.config.cacheDir);
|
|
194
|
+
await fs.ensureDir(params.executionDir);
|
|
201
195
|
for (const writablePath of params.config.writablePaths) {
|
|
202
196
|
await fs.ensureDir(writablePath);
|
|
203
197
|
}
|
|
204
198
|
|
|
205
199
|
const child = spawn("bwrap", buildLinuxBubblewrapArgs({
|
|
206
200
|
...params,
|
|
207
|
-
shellHomeDir,
|
|
208
|
-
shellTmpDir,
|
|
209
201
|
}), {
|
|
210
202
|
cwd: params.actualCwd,
|
|
211
203
|
stdio: "pipe",
|
|
212
|
-
env: buildSandboxEnv(
|
|
213
|
-
...params,
|
|
214
|
-
shellHomeDir,
|
|
215
|
-
shellTmpDir,
|
|
216
|
-
}),
|
|
204
|
+
env: buildSandboxEnv(params),
|
|
217
205
|
});
|
|
218
206
|
|
|
219
207
|
child.stdout.setEncoding("utf8");
|
|
@@ -225,5 +213,9 @@ export async function spawnLinuxBubblewrapSandbox(
|
|
|
225
213
|
sandboxed: true,
|
|
226
214
|
backend: "linux-bubblewrap",
|
|
227
215
|
networkMode: params.config.networkMode,
|
|
216
|
+
sandboxDir: params.config.sandboxDir,
|
|
217
|
+
homeDir: params.config.homeDir,
|
|
218
|
+
tmpDir: params.config.tmpDir,
|
|
219
|
+
cacheDir: params.config.cacheDir,
|
|
228
220
|
};
|
|
229
221
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 当前最小实现直接基于系统自带 `sandbox-exec`。
|
|
6
6
|
* - 目标不是抽象完整 provider 体系,而是先把 shell 命令从“宿主机直跑”收敛成“带边界执行”。
|
|
7
|
-
* -
|
|
7
|
+
* - 边界只保留四类:路径、环境变量、网络、agent 级共享 HOME/TMPDIR/cache。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
@@ -37,8 +37,9 @@ function dedupePaths(values: string[]): string[] {
|
|
|
37
37
|
function buildReadablePaths(params: {
|
|
38
38
|
rootPath: string;
|
|
39
39
|
shellPath: string;
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
sandboxDir: string;
|
|
41
|
+
tmpDir: string;
|
|
42
|
+
cacheDir: string;
|
|
42
43
|
}): string[] {
|
|
43
44
|
return dedupePaths([
|
|
44
45
|
"/bin",
|
|
@@ -50,21 +51,20 @@ function buildReadablePaths(params: {
|
|
|
50
51
|
"/opt/homebrew",
|
|
51
52
|
"/usr/local",
|
|
52
53
|
params.rootPath,
|
|
53
|
-
params.
|
|
54
|
-
params.
|
|
54
|
+
params.sandboxDir,
|
|
55
|
+
params.tmpDir,
|
|
56
|
+
params.cacheDir,
|
|
55
57
|
path.dirname(params.shellPath),
|
|
56
58
|
]);
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
function buildWritablePaths(params: SandboxSpawnParams
|
|
60
|
-
shellHomeDir: string;
|
|
61
|
-
shellTmpDir: string;
|
|
62
|
-
}): string[] {
|
|
61
|
+
function buildWritablePaths(params: SandboxSpawnParams): string[] {
|
|
63
62
|
return dedupePaths([
|
|
64
63
|
...params.config.writablePaths,
|
|
65
|
-
params.
|
|
66
|
-
params.
|
|
67
|
-
params.
|
|
64
|
+
params.executionDir,
|
|
65
|
+
params.config.sandboxDir,
|
|
66
|
+
params.config.tmpDir,
|
|
67
|
+
params.config.cacheDir,
|
|
68
68
|
]);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -77,20 +77,15 @@ function buildNetworkRules(networkMode: SandboxSpawnParams["config"]["networkMod
|
|
|
77
77
|
|
|
78
78
|
function buildSeatbeltProfile(params: SandboxSpawnParams & {
|
|
79
79
|
actualCwd: string;
|
|
80
|
-
shellHomeDir: string;
|
|
81
|
-
shellTmpDir: string;
|
|
82
80
|
}): string {
|
|
83
81
|
const readablePaths = buildReadablePaths({
|
|
84
82
|
rootPath: params.config.rootPath,
|
|
85
83
|
shellPath: params.shellPath,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const writablePaths = buildWritablePaths({
|
|
90
|
-
...params,
|
|
91
|
-
shellHomeDir: params.shellHomeDir,
|
|
92
|
-
shellTmpDir: params.shellTmpDir,
|
|
84
|
+
sandboxDir: params.config.sandboxDir,
|
|
85
|
+
tmpDir: params.config.tmpDir,
|
|
86
|
+
cacheDir: params.config.cacheDir,
|
|
93
87
|
});
|
|
88
|
+
const writablePaths = buildWritablePaths(params);
|
|
94
89
|
const lines = [
|
|
95
90
|
"(version 1)",
|
|
96
91
|
"(deny default)",
|
|
@@ -116,10 +111,7 @@ function buildSeatbeltProfile(params: SandboxSpawnParams & {
|
|
|
116
111
|
return `${lines.join("\n")}\n`;
|
|
117
112
|
}
|
|
118
113
|
|
|
119
|
-
function buildSandboxEnv(params: SandboxSpawnParams
|
|
120
|
-
shellHomeDir: string;
|
|
121
|
-
shellTmpDir: string;
|
|
122
|
-
}): NodeJS.ProcessEnv {
|
|
114
|
+
function buildSandboxEnv(params: SandboxSpawnParams): NodeJS.ProcessEnv {
|
|
123
115
|
const env: NodeJS.ProcessEnv = {};
|
|
124
116
|
for (const key of params.config.envAllowlist) {
|
|
125
117
|
const value = params.baseEnv[key];
|
|
@@ -134,9 +126,14 @@ function buildSandboxEnv(params: SandboxSpawnParams & {
|
|
|
134
126
|
}
|
|
135
127
|
|
|
136
128
|
env.PATH = String(env.PATH || params.baseEnv.PATH || DEFAULT_PATH_VALUE);
|
|
137
|
-
env.HOME = params.
|
|
138
|
-
env.ZDOTDIR = params.
|
|
139
|
-
env.TMPDIR = params.
|
|
129
|
+
env.HOME = params.config.homeDir;
|
|
130
|
+
env.ZDOTDIR = params.config.homeDir;
|
|
131
|
+
env.TMPDIR = params.config.tmpDir;
|
|
132
|
+
env.XDG_CACHE_HOME = params.config.cacheDir;
|
|
133
|
+
env.DC_SANDBOX = "1";
|
|
134
|
+
env.DC_SANDBOX_DIR = params.config.sandboxDir;
|
|
135
|
+
env.DC_SANDBOX_HOME = params.config.homeDir;
|
|
136
|
+
env.DC_SANDBOX_CACHE = params.config.cacheDir;
|
|
140
137
|
env.SHELL = params.shellPath;
|
|
141
138
|
|
|
142
139
|
return env;
|
|
@@ -148,18 +145,15 @@ function buildSandboxEnv(params: SandboxSpawnParams & {
|
|
|
148
145
|
export async function spawnMacOsSeatbeltSandbox(
|
|
149
146
|
params: SandboxSpawnParams & { actualCwd: string },
|
|
150
147
|
): Promise<SandboxSpawnResult> {
|
|
151
|
-
const
|
|
152
|
-
const shellHomeDir = path.join(sandboxRootDir, "home");
|
|
153
|
-
const shellTmpDir = path.join(sandboxRootDir, "tmp");
|
|
154
|
-
const profilePath = path.join(sandboxRootDir, "profile.sb");
|
|
148
|
+
const profilePath = path.join(params.executionDir, "sandbox-profile.sb");
|
|
155
149
|
|
|
156
|
-
await fs.ensureDir(
|
|
157
|
-
await fs.ensureDir(
|
|
150
|
+
await fs.ensureDir(params.config.sandboxDir);
|
|
151
|
+
await fs.ensureDir(params.config.tmpDir);
|
|
152
|
+
await fs.ensureDir(params.config.cacheDir);
|
|
153
|
+
await fs.ensureDir(params.executionDir);
|
|
158
154
|
|
|
159
155
|
const profile = buildSeatbeltProfile({
|
|
160
156
|
...params,
|
|
161
|
-
shellHomeDir,
|
|
162
|
-
shellTmpDir,
|
|
163
157
|
});
|
|
164
158
|
await fs.writeFile(profilePath, profile, "utf-8");
|
|
165
159
|
|
|
@@ -175,11 +169,7 @@ export async function spawnMacOsSeatbeltSandbox(
|
|
|
175
169
|
{
|
|
176
170
|
cwd: params.actualCwd,
|
|
177
171
|
stdio: "pipe",
|
|
178
|
-
env: buildSandboxEnv(
|
|
179
|
-
...params,
|
|
180
|
-
shellHomeDir,
|
|
181
|
-
shellTmpDir,
|
|
182
|
-
}),
|
|
172
|
+
env: buildSandboxEnv(params),
|
|
183
173
|
},
|
|
184
174
|
);
|
|
185
175
|
|
|
@@ -192,5 +182,9 @@ export async function spawnMacOsSeatbeltSandbox(
|
|
|
192
182
|
sandboxed: true,
|
|
193
183
|
backend: "macos-seatbelt",
|
|
194
184
|
networkMode: params.config.networkMode,
|
|
185
|
+
sandboxDir: params.config.sandboxDir,
|
|
186
|
+
homeDir: params.config.homeDir,
|
|
187
|
+
tmpDir: params.config.tmpDir,
|
|
188
|
+
cacheDir: params.config.cacheDir,
|
|
195
189
|
};
|
|
196
190
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 这里负责把 `downcity.json` 中面向用户的最小配置,收敛成运行时可直接执行的绝对路径配置。
|
|
6
|
+
* - sandbox 是 agent 项目级能力,持久目录固定为 `<project>/.downcity/sandbox`。
|
|
6
7
|
* - 当前版本只服务 shell / CLI 这条命令执行链,不引入审批、profile 绑定或用户权限系统。
|
|
7
8
|
* - 解析结果只回答一个问题:这次命令执行的 sandbox 边界是什么。
|
|
8
9
|
*/
|
|
@@ -24,6 +25,8 @@ const DEFAULT_ENV_ALLOWLIST = [
|
|
|
24
25
|
"LOGNAME",
|
|
25
26
|
];
|
|
26
27
|
|
|
28
|
+
const SANDBOX_RELATIVE_DIR = path.join(".downcity", "sandbox");
|
|
29
|
+
|
|
27
30
|
function normalizeEnvAllowlist(values?: string[]): string[] {
|
|
28
31
|
const seen = new Set<string>();
|
|
29
32
|
const result: string[] = [];
|
|
@@ -49,14 +52,15 @@ export function isPathInsideRoot(rootPath: string, targetPath: string): boolean
|
|
|
49
52
|
|
|
50
53
|
function normalizeWritablePaths(params: {
|
|
51
54
|
rootPath: string;
|
|
55
|
+
sandboxDir: string;
|
|
52
56
|
writablePaths?: string[];
|
|
53
57
|
context: AgentContext;
|
|
54
58
|
}): string[] {
|
|
55
|
-
const { rootPath, writablePaths, context } = params;
|
|
59
|
+
const { rootPath, sandboxDir, writablePaths, context } = params;
|
|
56
60
|
const rawValues =
|
|
57
61
|
Array.isArray(writablePaths) && writablePaths.length > 0
|
|
58
|
-
? writablePaths
|
|
59
|
-
: [rootPath];
|
|
62
|
+
? [rootPath, sandboxDir, ...writablePaths]
|
|
63
|
+
: [rootPath, sandboxDir];
|
|
60
64
|
const seen = new Set<string>();
|
|
61
65
|
const result: string[] = [];
|
|
62
66
|
|
|
@@ -102,13 +106,21 @@ export function resolveSandboxBackend(): SandboxBackend {
|
|
|
102
106
|
export function resolveSandboxConfig(context: AgentContext): ResolvedSandboxConfig {
|
|
103
107
|
const rootPath = path.resolve(context.rootPath);
|
|
104
108
|
const projectConfig = context.config?.sandbox;
|
|
109
|
+
const sandboxDir = path.join(rootPath, SANDBOX_RELATIVE_DIR);
|
|
110
|
+
const tmpDir = path.join(sandboxDir, "tmp");
|
|
111
|
+
const cacheDir = path.join(sandboxDir, ".cache");
|
|
105
112
|
|
|
106
113
|
return {
|
|
107
114
|
backend: resolveSandboxBackend(),
|
|
108
115
|
rootPath,
|
|
116
|
+
sandboxDir,
|
|
117
|
+
homeDir: sandboxDir,
|
|
118
|
+
tmpDir,
|
|
119
|
+
cacheDir,
|
|
109
120
|
envAllowlist: normalizeEnvAllowlist(projectConfig?.envAllowlist),
|
|
110
121
|
writablePaths: normalizeWritablePaths({
|
|
111
122
|
rootPath,
|
|
123
|
+
sandboxDir,
|
|
112
124
|
writablePaths: projectConfig?.writablePaths,
|
|
113
125
|
context,
|
|
114
126
|
}),
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* SandboxRunner 入口。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* - 这里不实现完整的 session/read/write
|
|
5
|
+
* - 这里不实现完整的 session/read/write 协议,只负责本地子进程创建时统一进入 agent sandbox backend。
|
|
6
6
|
* - 当前版本接入 macOS seatbelt 与 Linux bubblewrap backend。
|
|
7
|
-
* -
|
|
7
|
+
* - 本地命令不再允许回退到宿主机普通子进程执行。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
|
|
@@ -25,6 +25,31 @@ export async function spawnShellProcess(params: {
|
|
|
25
25
|
shellPath: string;
|
|
26
26
|
login: boolean;
|
|
27
27
|
baseEnv: NodeJS.ProcessEnv;
|
|
28
|
+
}): Promise<SandboxSpawnResult> {
|
|
29
|
+
return spawnInSandbox({
|
|
30
|
+
context: params.context,
|
|
31
|
+
executionId: params.shellId,
|
|
32
|
+
executionDir: params.shellDir,
|
|
33
|
+
cmd: params.cmd,
|
|
34
|
+
cwd: params.cwd,
|
|
35
|
+
shellPath: params.shellPath,
|
|
36
|
+
login: params.login,
|
|
37
|
+
baseEnv: params.baseEnv,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 在当前 agent sandbox 中启动本地子进程。
|
|
43
|
+
*/
|
|
44
|
+
export async function spawnInSandbox(params: {
|
|
45
|
+
context: AgentContext;
|
|
46
|
+
executionId: string;
|
|
47
|
+
executionDir: string;
|
|
48
|
+
cmd: string;
|
|
49
|
+
cwd: string;
|
|
50
|
+
shellPath: string;
|
|
51
|
+
login: boolean;
|
|
52
|
+
baseEnv: NodeJS.ProcessEnv;
|
|
28
53
|
}): Promise<SandboxSpawnResult> {
|
|
29
54
|
const config = resolveSandboxConfig(params.context);
|
|
30
55
|
const actualCwd = resolveSandboxCwd({
|
|
@@ -33,8 +58,8 @@ export async function spawnShellProcess(params: {
|
|
|
33
58
|
context: params.context,
|
|
34
59
|
});
|
|
35
60
|
const spawnParams = {
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
executionId: params.executionId,
|
|
62
|
+
executionDir: params.executionDir,
|
|
38
63
|
cmd: params.cmd,
|
|
39
64
|
cwd: params.cwd,
|
|
40
65
|
shellPath: params.shellPath,
|
|
@@ -61,8 +86,8 @@ export async function spawnShellProcess(params: {
|
|
|
61
86
|
*/
|
|
62
87
|
export async function runSandboxCommand(params: {
|
|
63
88
|
context: AgentContext;
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
executionId: string;
|
|
90
|
+
executionDir: string;
|
|
66
91
|
cmd: string;
|
|
67
92
|
cwd: string;
|
|
68
93
|
shellPath: string;
|
|
@@ -74,7 +99,7 @@ export async function runSandboxCommand(params: {
|
|
|
74
99
|
exitCode: number;
|
|
75
100
|
spawn: SandboxSpawnResult;
|
|
76
101
|
}> {
|
|
77
|
-
const spawn = await
|
|
102
|
+
const spawn = await spawnInSandbox(params);
|
|
78
103
|
const stdoutChunks: string[] = [];
|
|
79
104
|
const stderrChunks: string[] = [];
|
|
80
105
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 这里放的是 agent 执行层内部使用的最小 sandbox 运行时类型。
|
|
6
|
-
* - 当前只围绕
|
|
7
|
-
* - 目标是让 shell
|
|
6
|
+
* - 当前只围绕 agent 级 sandbox spawn 设计,不引入复杂 provider / policy / binding 对象。
|
|
7
|
+
* - 目标是让 shell、task script 等本地执行入口都能复用同一个 agent sandbox 边界。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
@@ -304,6 +304,30 @@ export interface ResolvedSandboxConfig extends SandboxConfig {
|
|
|
304
304
|
* 当前运行时选中的 backend。
|
|
305
305
|
*/
|
|
306
306
|
backend: SandboxBackend;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 当前 agent 级 sandbox 的持久目录。
|
|
310
|
+
*
|
|
311
|
+
* 说明(中文)
|
|
312
|
+
* - 该目录不属于某个 shellId,而属于当前 agent 项目。
|
|
313
|
+
* - shell、task script 等所有 sandbox 子进程共享它作为 HOME/cache 根。
|
|
314
|
+
*/
|
|
315
|
+
sandboxDir: string;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* sandbox 子进程使用的 HOME。
|
|
319
|
+
*/
|
|
320
|
+
homeDir: string;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* sandbox 子进程使用的临时目录。
|
|
324
|
+
*/
|
|
325
|
+
tmpDir: string;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* sandbox 子进程使用的 XDG cache 目录。
|
|
329
|
+
*/
|
|
330
|
+
cacheDir: string;
|
|
307
331
|
}
|
|
308
332
|
|
|
309
333
|
/**
|
|
@@ -311,14 +335,18 @@ export interface ResolvedSandboxConfig extends SandboxConfig {
|
|
|
311
335
|
*/
|
|
312
336
|
export interface SandboxSpawnParams {
|
|
313
337
|
/**
|
|
314
|
-
*
|
|
338
|
+
* 当前执行记录标识。
|
|
339
|
+
*
|
|
340
|
+
* 说明(中文)
|
|
341
|
+
* - shell plugin 传入 shellId,task script 可以传入自己的 executionId。
|
|
342
|
+
* - 它只用于日志与诊断,不参与 sandbox HOME/cache/权限边界的计算。
|
|
315
343
|
*/
|
|
316
|
-
|
|
344
|
+
executionId: string;
|
|
317
345
|
|
|
318
346
|
/**
|
|
319
|
-
*
|
|
347
|
+
* 当前执行记录目录。
|
|
320
348
|
*/
|
|
321
|
-
|
|
349
|
+
executionDir: string;
|
|
322
350
|
|
|
323
351
|
/**
|
|
324
352
|
* 要执行的原始命令文本。
|
|
@@ -379,4 +407,24 @@ export interface SandboxSpawnResult {
|
|
|
379
407
|
* 当前实际采用的网络模式。
|
|
380
408
|
*/
|
|
381
409
|
networkMode: SandboxNetworkMode;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* 当前 agent 级 sandbox 的持久目录。
|
|
413
|
+
*/
|
|
414
|
+
sandboxDir: string;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 当前子进程使用的 HOME。
|
|
418
|
+
*/
|
|
419
|
+
homeDir: string;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 当前子进程使用的临时目录。
|
|
423
|
+
*/
|
|
424
|
+
tmpDir: string;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* 当前子进程使用的 XDG cache 目录。
|
|
428
|
+
*/
|
|
429
|
+
cacheDir: string;
|
|
382
430
|
}
|
|
@@ -75,7 +75,7 @@ export interface ImagePluginInput {
|
|
|
75
75
|
quality?: string;
|
|
76
76
|
/** 随机种子。 */
|
|
77
77
|
seed?: number;
|
|
78
|
-
/** 业务侧任务 ID
|
|
78
|
+
/** 业务侧任务 ID,用于 provider 侧幂等、追踪和恢复。 */
|
|
79
79
|
client_job_id?: string;
|
|
80
80
|
/** Provider 私有参数,例如 `{ openai: {...}, gemini: {...}, luchi: {...} }`。 */
|
|
81
81
|
provider_options?: JsonObject;
|
|
@@ -88,73 +88,6 @@ export interface ImagePluginInput {
|
|
|
88
88
|
*/
|
|
89
89
|
export type ImagePluginResult = UIMessage;
|
|
90
90
|
|
|
91
|
-
/**
|
|
92
|
-
* ImagePlugin 图片任务状态。
|
|
93
|
-
*/
|
|
94
|
-
export type ImagePluginJobStatus = "queued" | "running" | "succeeded" | "failed";
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* ImagePlugin 图片任务创建结果。
|
|
98
|
-
*/
|
|
99
|
-
export interface ImagePluginJobCreateResult {
|
|
100
|
-
/** 图片任务唯一 ID。 */
|
|
101
|
-
job_id: string;
|
|
102
|
-
/** 当前任务状态。 */
|
|
103
|
-
status: ImagePluginJobStatus;
|
|
104
|
-
/** 查询任务状态的路径或 URL。 */
|
|
105
|
-
status_path?: string;
|
|
106
|
-
/** 读取任务结果的路径或 URL。 */
|
|
107
|
-
result_path?: string;
|
|
108
|
-
/** 人类可读状态说明。 */
|
|
109
|
-
message?: string;
|
|
110
|
-
/** 建议下次轮询前等待的毫秒数。 */
|
|
111
|
-
poll_after_ms?: number;
|
|
112
|
-
/** 任务创建时间。 */
|
|
113
|
-
created_at?: string;
|
|
114
|
-
/** 任务更新时间。 */
|
|
115
|
-
updated_at?: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* ImagePlugin 图片任务状态查询结果。
|
|
120
|
-
*/
|
|
121
|
-
export interface ImagePluginJobStatusResult {
|
|
122
|
-
/** 图片任务唯一 ID。 */
|
|
123
|
-
job_id: string;
|
|
124
|
-
/** 当前任务状态。 */
|
|
125
|
-
status: ImagePluginJobStatus;
|
|
126
|
-
/** 人类可读状态说明。 */
|
|
127
|
-
message?: string;
|
|
128
|
-
/** 失败时的错误信息。 */
|
|
129
|
-
error?: string;
|
|
130
|
-
/** 建议下次轮询前等待的毫秒数。 */
|
|
131
|
-
poll_after_ms?: number;
|
|
132
|
-
/** 任务创建时间。 */
|
|
133
|
-
created_at?: string;
|
|
134
|
-
/** 任务更新时间。 */
|
|
135
|
-
updated_at?: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* ImagePlugin 图片任务结果查询结果。
|
|
140
|
-
*/
|
|
141
|
-
export interface ImagePluginJobResult {
|
|
142
|
-
/** 图片任务唯一 ID。 */
|
|
143
|
-
job_id: string;
|
|
144
|
-
/** 当前任务状态。 */
|
|
145
|
-
status: ImagePluginJobStatus;
|
|
146
|
-
/** 成功时的图片结果。 */
|
|
147
|
-
result?: ImagePluginResult;
|
|
148
|
-
/** 失败时的错误信息。 */
|
|
149
|
-
error?: string;
|
|
150
|
-
/** 人类可读状态说明。 */
|
|
151
|
-
message?: string;
|
|
152
|
-
/** 任务创建时间。 */
|
|
153
|
-
created_at?: string;
|
|
154
|
-
/** 任务更新时间。 */
|
|
155
|
-
updated_at?: string;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
91
|
/**
|
|
159
92
|
* ImagePlugin 构造参数。
|
|
160
93
|
*/
|
|
@@ -165,16 +98,6 @@ export interface ImagePluginOptions {
|
|
|
165
98
|
title?: string;
|
|
166
99
|
/** Plugin 用途说明。 */
|
|
167
100
|
description?: string;
|
|
168
|
-
/**
|
|
101
|
+
/** 图片生成函数,通常传入 `(input) => city.ai.image(input)`。 */
|
|
169
102
|
image?: (input: ImagePluginInput) => Promise<ImagePluginResult> | ImagePluginResult;
|
|
170
|
-
/** 可选:创建图片生成任务,通常传入 `(input) => city.ai.imageJobCreate(input)`。 */
|
|
171
|
-
create?: (input: ImagePluginInput) => Promise<ImagePluginJobCreateResult> | ImagePluginJobCreateResult;
|
|
172
|
-
/** 可选:查询图片生成任务状态,通常传入 `(input) => city.ai.imageJobStatus(input)`。 */
|
|
173
|
-
status?: (input: { job_id: string }) => Promise<ImagePluginJobStatusResult> | ImagePluginJobStatusResult;
|
|
174
|
-
/** 可选:读取图片生成任务结果,通常传入 `(input) => city.ai.imageJobResult(input)`。 */
|
|
175
|
-
result?: (input: { job_id: string }) => Promise<ImagePluginJobResult> | ImagePluginJobResult;
|
|
176
|
-
/** 兼容 `generate` 动作等待任务完成的最长毫秒数。 */
|
|
177
|
-
wait_timeout_ms?: number;
|
|
178
|
-
/** 兼容 `generate` 动作每次轮询间隔毫秒数。 */
|
|
179
|
-
poll_interval_ms?: number;
|
|
180
103
|
}
|