@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.
Files changed (53) hide show
  1. package/bin/executor/composer/system/default/assets/core.prompt.d.ts +1 -1
  2. package/bin/executor/composer/system/default/assets/core.prompt.d.ts.map +1 -1
  3. package/bin/executor/composer/system/default/assets/core.prompt.js +1 -1
  4. package/bin/executor/composer/system/default/assets/core.prompt.js.map +1 -1
  5. package/bin/executor/tools/shell/ShellToolBridge.d.ts.map +1 -1
  6. package/bin/executor/tools/shell/ShellToolBridge.js +14 -0
  7. package/bin/executor/tools/shell/ShellToolBridge.js.map +1 -1
  8. package/bin/executor/tools/shell/types/ShellPlugin.d.ts +8 -0
  9. package/bin/executor/tools/shell/types/ShellPlugin.d.ts.map +1 -1
  10. package/bin/index.d.ts +1 -1
  11. package/bin/index.d.ts.map +1 -1
  12. package/bin/index.js.map +1 -1
  13. package/bin/plugin/core/ImagePlugin.d.ts +3 -64
  14. package/bin/plugin/core/ImagePlugin.d.ts.map +1 -1
  15. package/bin/plugin/core/ImagePlugin.js +12 -232
  16. package/bin/plugin/core/ImagePlugin.js.map +1 -1
  17. package/bin/sandbox/LinuxBubblewrapSandbox.d.ts +1 -3
  18. package/bin/sandbox/LinuxBubblewrapSandbox.d.ts.map +1 -1
  19. package/bin/sandbox/LinuxBubblewrapSandbox.js +31 -30
  20. package/bin/sandbox/LinuxBubblewrapSandbox.js.map +1 -1
  21. package/bin/sandbox/MacOsSeatbeltSandbox.d.ts +1 -1
  22. package/bin/sandbox/MacOsSeatbeltSandbox.d.ts.map +1 -1
  23. package/bin/sandbox/MacOsSeatbeltSandbox.js +30 -29
  24. package/bin/sandbox/MacOsSeatbeltSandbox.js.map +1 -1
  25. package/bin/sandbox/SandboxConfigResolver.d.ts +1 -0
  26. package/bin/sandbox/SandboxConfigResolver.d.ts.map +1 -1
  27. package/bin/sandbox/SandboxConfigResolver.js +13 -3
  28. package/bin/sandbox/SandboxConfigResolver.js.map +1 -1
  29. package/bin/sandbox/SandboxRunner.d.ts +17 -4
  30. package/bin/sandbox/SandboxRunner.d.ts.map +1 -1
  31. package/bin/sandbox/SandboxRunner.js +20 -5
  32. package/bin/sandbox/SandboxRunner.js.map +1 -1
  33. package/bin/sandbox/types/SandboxRuntime.d.ts +46 -6
  34. package/bin/sandbox/types/SandboxRuntime.d.ts.map +1 -1
  35. package/bin/sandbox/types/SandboxRuntime.js +2 -2
  36. package/bin/types/plugin/ImagePlugin.d.ts +2 -79
  37. package/bin/types/plugin/ImagePlugin.d.ts.map +1 -1
  38. package/package.json +2 -2
  39. package/scripts/image-plugin-job.test.mjs +21 -108
  40. package/scripts/linux-bubblewrap-sandbox.test.mjs +23 -14
  41. package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
  42. package/src/executor/composer/system/default/assets/core.prompt.ts.txt +5 -0
  43. package/src/executor/tools/shell/ShellToolBridge.ts +14 -0
  44. package/src/executor/tools/shell/types/ShellPlugin.ts +8 -0
  45. package/src/index.ts +0 -5
  46. package/src/plugin/core/ImagePlugin.ts +13 -286
  47. package/src/sandbox/LinuxBubblewrapSandbox.ts +35 -43
  48. package/src/sandbox/MacOsSeatbeltSandbox.ts +35 -41
  49. package/src/sandbox/SandboxConfigResolver.ts +15 -3
  50. package/src/sandbox/SandboxRunner.ts +32 -7
  51. package/src/sandbox/types/SandboxRuntime.ts +54 -6
  52. package/src/types/plugin/ImagePlugin.ts +2 -79
  53. package/tsconfig.tsbuildinfo +1 -1
@@ -4,7 +4,7 @@
4
4
  * 关键点(中文)
5
5
  * - 基于 `bwrap` 提供 Linux 本机 shell sandbox。
6
6
  * - 继续保持“shell 命令必须进入 sandbox”的安全语义,不提供宿主机裸跑回退。
7
- * - 边界与 macOS backend 对齐:路径、环境变量、网络、隔离 HOME/TMPDIR。
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
- shellHomeDir: string;
38
- shellTmpDir: string;
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.shellHomeDir,
49
- params.shellTmpDir,
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.shellDir,
61
- params.shellHomeDir,
62
- params.shellTmpDir,
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.shellHomeDir;
95
- env.TMPDIR = params.shellTmpDir;
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
- shellHomeDir: params.shellHomeDir,
129
- shellTmpDir: params.shellTmpDir,
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 (!isPathCoveredBy(mountedPaths, writablePath)) {
163
- addParentDirs(args, writablePath, createdDirs);
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
- const sandboxRootDir = path.join(params.shellDir, "sandbox");
196
- const shellHomeDir = path.join(sandboxRootDir, "home");
197
- const shellTmpDir = path.join(sandboxRootDir, "tmp");
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
- * - 边界只保留四类:路径、环境变量、网络、隔离后的 HOME/TMPDIR。
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
- shellHomeDir: string;
41
- shellTmpDir: string;
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.shellHomeDir,
54
- params.shellTmpDir,
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.shellDir,
66
- params.shellHomeDir,
67
- params.shellTmpDir,
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
- shellHomeDir: params.shellHomeDir,
87
- shellTmpDir: params.shellTmpDir,
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.shellHomeDir;
138
- env.ZDOTDIR = params.shellHomeDir;
139
- env.TMPDIR = params.shellTmpDir;
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 sandboxRootDir = path.join(params.shellDir, "sandbox");
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(shellHomeDir);
157
- await fs.ensureDir(shellTmpDir);
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 协议,只负责 shell 子进程创建时统一进入 sandbox backend。
5
+ * - 这里不实现完整的 session/read/write 协议,只负责本地子进程创建时统一进入 agent sandbox backend。
6
6
  * - 当前版本接入 macOS seatbelt 与 Linux bubblewrap backend。
7
- * - shell 命令不再允许回退到宿主机普通子进程执行。
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
- shellId: params.shellId,
37
- shellDir: params.shellDir,
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
- shellId: string;
65
- shellDir: string;
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 spawnShellProcess(params);
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
- * - 当前只围绕 `SandboxRunner` 设计,不引入复杂 provider / policy / binding 对象。
7
- * - 目标是让 shell plugin runtime 可以直接把命令交给 `SandboxRunner` 执行。
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
- * 当前 shell 会话标识。
338
+ * 当前执行记录标识。
339
+ *
340
+ * 说明(中文)
341
+ * - shell plugin 传入 shellId,task script 可以传入自己的 executionId。
342
+ * - 它只用于日志与诊断,不参与 sandbox HOME/cache/权限边界的计算。
315
343
  */
316
- shellId: string;
344
+ executionId: string;
317
345
 
318
346
  /**
319
- * shell 会话目录。
347
+ * 当前执行记录目录。
320
348
  */
321
- shellDir: string;
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
- /** 可选:图片生成函数;未传 `create/status/result` 时用于本地后台任务兼容。 */
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
  }