@downcity/agent 1.1.108 → 1.1.111

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 (70) 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 +22 -4
  7. package/bin/executor/tools/shell/ShellToolBridge.js.map +1 -1
  8. package/bin/executor/tools/shell/ShellToolDefinition.d.ts.map +1 -1
  9. package/bin/executor/tools/shell/ShellToolDefinition.js +10 -2
  10. package/bin/executor/tools/shell/ShellToolDefinition.js.map +1 -1
  11. package/bin/executor/tools/shell/ShellToolSchemas.d.ts +10 -0
  12. package/bin/executor/tools/shell/ShellToolSchemas.d.ts.map +1 -1
  13. package/bin/executor/tools/shell/ShellToolSchemas.js +13 -0
  14. package/bin/executor/tools/shell/ShellToolSchemas.js.map +1 -1
  15. package/bin/executor/tools/shell/types/Shell.d.ts +12 -0
  16. package/bin/executor/tools/shell/types/Shell.d.ts.map +1 -1
  17. package/bin/executor/tools/shell/types/ShellPlugin.d.ts +28 -0
  18. package/bin/executor/tools/shell/types/ShellPlugin.d.ts.map +1 -1
  19. package/bin/sandbox/LinuxBubblewrapSandbox.d.ts.map +1 -1
  20. package/bin/sandbox/LinuxBubblewrapSandbox.js +1 -0
  21. package/bin/sandbox/LinuxBubblewrapSandbox.js.map +1 -1
  22. package/bin/sandbox/MacOsSeatbeltSandbox.d.ts.map +1 -1
  23. package/bin/sandbox/MacOsSeatbeltSandbox.js +1 -0
  24. package/bin/sandbox/MacOsSeatbeltSandbox.js.map +1 -1
  25. package/bin/sandbox/SandboxRunner.d.ts +2 -0
  26. package/bin/sandbox/SandboxRunner.d.ts.map +1 -1
  27. package/bin/sandbox/SandboxRunner.js +14 -0
  28. package/bin/sandbox/SandboxRunner.js.map +1 -1
  29. package/bin/sandbox/UnrestrictedSandbox.d.ts +16 -0
  30. package/bin/sandbox/UnrestrictedSandbox.d.ts.map +1 -0
  31. package/bin/sandbox/UnrestrictedSandbox.js +39 -0
  32. package/bin/sandbox/UnrestrictedSandbox.js.map +1 -0
  33. package/bin/sandbox/types/SandboxRuntime.d.ts +9 -1
  34. package/bin/sandbox/types/SandboxRuntime.d.ts.map +1 -1
  35. package/bin/session/Session.d.ts.map +1 -1
  36. package/bin/session/Session.js +3 -0
  37. package/bin/session/Session.js.map +1 -1
  38. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  39. package/bin/session/services/SessionTurnService.js +1 -0
  40. package/bin/session/services/SessionTurnService.js.map +1 -1
  41. package/bin/session/storage/RuntimeSessionPort.d.ts +5 -1
  42. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  43. package/bin/session/storage/RuntimeSessionPort.js +3 -0
  44. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  45. package/bin/types/executor/SessionRunContext.d.ts +8 -0
  46. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  47. package/bin/types/runtime/agent/AgentContext.d.ts +9 -1
  48. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  49. package/bin/types/sdk/AgentSessionEvent.d.ts +75 -1
  50. package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -1
  51. package/package.json +2 -2
  52. package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
  53. package/src/executor/composer/system/default/assets/core.prompt.ts.txt +4 -2
  54. package/src/executor/tools/shell/ShellToolBridge.ts +24 -4
  55. package/src/executor/tools/shell/ShellToolDefinition.ts +12 -0
  56. package/src/executor/tools/shell/ShellToolSchemas.ts +15 -0
  57. package/src/executor/tools/shell/types/Shell.ts +13 -0
  58. package/src/executor/tools/shell/types/ShellPlugin.ts +30 -0
  59. package/src/sandbox/LinuxBubblewrapSandbox.ts +1 -0
  60. package/src/sandbox/MacOsSeatbeltSandbox.ts +1 -0
  61. package/src/sandbox/SandboxRunner.ts +17 -0
  62. package/src/sandbox/UnrestrictedSandbox.ts +53 -0
  63. package/src/sandbox/types/SandboxRuntime.ts +11 -1
  64. package/src/session/Session.ts +3 -0
  65. package/src/session/services/SessionTurnService.ts +1 -0
  66. package/src/session/storage/RuntimeSessionPort.ts +8 -0
  67. package/src/types/executor/SessionRunContext.ts +9 -0
  68. package/src/types/runtime/agent/AgentContext.ts +9 -0
  69. package/src/types/sdk/AgentSessionEvent.ts +92 -0
  70. package/tsconfig.tsbuildinfo +1 -1
@@ -23,9 +23,11 @@
23
23
  - 需要向进程 stdin 输入内容时,使用 `shell_write`。
24
24
  - 命令会话完成后若不再需要,使用 `shell_close` 主动释放资源。
25
25
  - 不要把原始超长 shell 输出直接转发给用户,应先总结。
26
- - shell 命令默认在当前 agent sandbox 中执行:项目目录可读写,网络可用,HOME 指向 `.downcity/sandbox/`,真实用户 HOME 与系统目录不可写。
26
+ - shell 命令默认在 Safe Sandbox 中执行:项目目录可读写,网络可用,HOME 指向 `.downcity/sandbox/`,真实用户 HOME 与系统目录不可写。
27
27
  - 安装 Python 依赖时优先使用项目内 `.venv`,不要使用 `pip install --user`。
28
- - 不要尝试 `sudo`、`brew install`、Xcode Command Line Tools 安装或写 `/usr/local`、`/opt/homebrew`、`/System` 等宿主系统目录;如果确实缺少系统级依赖,直接告诉用户需要在宿主机安装。
28
+ - 需要全局安装、写宿主目录、访问宿主级能力时,可以请求 `sandbox: "unrestricted"`;必须提供清楚的 `reason`,等待用户确认后才能执行。
29
+ - 不要尝试 `sudo`、Xcode Command Line Tools 安装、修改 SSH/keychain/shell profile,或执行明显破坏性命令;这些请求即使使用 unrestricted sandbox 也会被拒绝。
30
+ - 用户拒绝 unrestricted sandbox 请求后,不要反复请求同一命令,应解释影响并给出项目内替代方案。
29
31
  - 下载模型、工具缓存、临时状态应自然落在 `.downcity/sandbox/` 或项目目录中,不要假设可复用真实用户缓存。
30
32
 
31
33
  # 默认决策与澄清
@@ -205,13 +205,23 @@ export function flattenShellActionResponse(params: {
205
205
  }): JsonObject {
206
206
  const shell = params.response.shell;
207
207
  const chunk = params.response.chunk;
208
+ const exitCode = typeof shell.exitCode === "number" ? shell.exitCode : null;
209
+ const success =
210
+ shell.approvalStatus !== "denied" &&
211
+ shell.approvalStatus !== "expired" &&
212
+ (exitCode === null || exitCode === 0);
208
213
  return {
209
- success: true,
214
+ success,
210
215
  shell_id: shell.shellId,
211
216
  status: shell.status,
212
217
  cmd: shell.cmd,
213
218
  cwd: shell.cwd,
214
219
  sandboxed: shell.sandboxed === true,
220
+ sandbox: shell.sandboxMode || (shell.sandboxed === false ? "unrestricted" : "safe"),
221
+ approval_status: shell.approvalStatus || null,
222
+ approval_id: shell.approvalId || null,
223
+ approval_reason: shell.approvalReason || null,
224
+ stdin_writable: shell.stdinWritable !== false,
215
225
  sandbox_backend: shell.sandboxBackend || null,
216
226
  sandbox_network_mode: shell.sandboxNetworkMode || null,
217
227
  sandbox_dir: shell.sandboxDir || null,
@@ -223,7 +233,7 @@ export function flattenShellActionResponse(params: {
223
233
  started_at: shell.startedAt,
224
234
  updated_at: shell.updatedAt,
225
235
  ended_at: typeof shell.endedAt === "number" ? shell.endedAt : null,
226
- exit_code: typeof shell.exitCode === "number" ? shell.exitCode : null,
236
+ exit_code: exitCode,
227
237
  output: chunk?.output || "",
228
238
  start_cursor: typeof chunk?.startCursor === "number" ? chunk.startCursor : null,
229
239
  end_cursor: typeof chunk?.endCursor === "number" ? chunk.endCursor : null,
@@ -257,19 +267,29 @@ export function flattenShellExecResponse(params: {
257
267
  }): JsonObject {
258
268
  const shell = params.response.shell;
259
269
  const chunk = params.response.chunk;
270
+ const exitCode = typeof shell.exitCode === "number" ? shell.exitCode : null;
271
+ const success =
272
+ shell.approvalStatus !== "denied" &&
273
+ shell.approvalStatus !== "expired" &&
274
+ (exitCode === null || exitCode === 0);
260
275
  return {
261
- success: true,
276
+ success,
262
277
  status: shell.status,
263
278
  cmd: shell.cmd,
264
279
  cwd: shell.cwd,
265
280
  sandboxed: shell.sandboxed === true,
281
+ sandbox: shell.sandboxMode || (shell.sandboxed === false ? "unrestricted" : "safe"),
282
+ approval_status: shell.approvalStatus || null,
283
+ approval_id: shell.approvalId || null,
284
+ approval_reason: shell.approvalReason || null,
285
+ stdin_writable: shell.stdinWritable !== false,
266
286
  sandbox_backend: shell.sandboxBackend || null,
267
287
  sandbox_network_mode: shell.sandboxNetworkMode || null,
268
288
  sandbox_dir: shell.sandboxDir || null,
269
289
  sandbox_home_dir: shell.sandboxHomeDir || null,
270
290
  sandbox_tmp_dir: shell.sandboxTmpDir || null,
271
291
  sandbox_cache_dir: shell.sandboxCacheDir || null,
272
- exit_code: typeof shell.exitCode === "number" ? shell.exitCode : null,
292
+ exit_code: exitCode,
273
293
  output: chunk?.output || "",
274
294
  original_chars: chunk?.originalChars ?? 0,
275
295
  original_lines: chunk?.originalLines ?? 0,
@@ -85,6 +85,8 @@ export const shell_start = tool({
85
85
  inline_wait_ms = 1200,
86
86
  max_output_tokens,
87
87
  auto_notify_on_exit,
88
+ sandbox = "safe",
89
+ reason,
88
90
  }: ShellStartInput) => {
89
91
  const startedAt = Date.now();
90
92
 
@@ -99,6 +101,8 @@ export const shell_start = tool({
99
101
  inline_wait_ms,
100
102
  max_output_tokens: max_output_tokens ?? null,
101
103
  auto_notify_on_exit: auto_notify_on_exit ?? null,
104
+ sandbox,
105
+ reason: reason || "",
102
106
  }),
103
107
  );
104
108
 
@@ -128,6 +132,8 @@ export const shell_start = tool({
128
132
  ...(typeof auto_notify_on_exit === "boolean"
129
133
  ? { autoNotifyOnExit: auto_notify_on_exit }
130
134
  : {}),
135
+ sandbox,
136
+ ...(reason ? { reason } : {}),
131
137
  },
132
138
  });
133
139
 
@@ -173,6 +179,8 @@ export const shell_exec = tool({
173
179
  login = true,
174
180
  timeout_ms = 60000,
175
181
  max_output_tokens,
182
+ sandbox = "safe",
183
+ reason,
176
184
  }: ShellExecInput) => {
177
185
  const startedAt = Date.now();
178
186
 
@@ -186,6 +194,8 @@ export const shell_exec = tool({
186
194
  login,
187
195
  timeout_ms,
188
196
  max_output_tokens: max_output_tokens ?? null,
197
+ sandbox,
198
+ reason: reason || "",
189
199
  }),
190
200
  );
191
201
 
@@ -212,6 +222,8 @@ export const shell_exec = tool({
212
222
  ...(typeof max_output_tokens === "number"
213
223
  ? { maxOutputTokens: max_output_tokens }
214
224
  : {}),
225
+ sandbox,
226
+ ...(reason ? { reason } : {}),
215
227
  },
216
228
  });
217
229
 
@@ -8,6 +8,17 @@
8
8
 
9
9
  import { z } from "zod";
10
10
 
11
+ const shellSandboxModeSchema = z
12
+ .enum(["safe", "unrestricted"])
13
+ .optional()
14
+ .default("safe")
15
+ .describe("Sandbox mode. safe is the default; unrestricted requires user approval.");
16
+
17
+ const shellUnrestrictedReasonSchema = z
18
+ .string()
19
+ .optional()
20
+ .describe("Required when sandbox is unrestricted. Explain why host-level execution is needed.");
21
+
11
22
  export const shellStartInputSchema = z.object({
12
23
  cmd: z.string().describe("Shell command to execute."),
13
24
  workdir: z
@@ -36,6 +47,8 @@ export const shellStartInputSchema = z.object({
36
47
  .boolean()
37
48
  .optional()
38
49
  .describe("Whether the shell plugin runtime should auto-return to the owning chat agent when the command exits."),
50
+ sandbox: shellSandboxModeSchema,
51
+ reason: shellUnrestrictedReasonSchema,
39
52
  });
40
53
 
41
54
  export const shellExecInputSchema = z.object({
@@ -62,6 +75,8 @@ export const shellExecInputSchema = z.object({
62
75
  .number()
63
76
  .optional()
64
77
  .describe("Maximum output tokens returned in the final result."),
78
+ sandbox: shellSandboxModeSchema,
79
+ reason: shellUnrestrictedReasonSchema,
65
80
  });
66
81
 
67
82
  export const shellStatusInputSchema = z.object({
@@ -6,6 +6,11 @@
6
6
  * - `shell_id` 与 chat `sessionId` 严格分离,避免语义混淆。
7
7
  */
8
8
 
9
+ /**
10
+ * shell 执行 sandbox 模式。
11
+ */
12
+ export type ShellSandboxMode = "safe" | "unrestricted";
13
+
9
14
  /**
10
15
  * 启动一个交互式 shell session 的输入。
11
16
  */
@@ -24,6 +29,10 @@ export type ShellStartInput = {
24
29
  max_output_tokens?: number;
25
30
  /** 进程退出时是否自动通知调用方。 */
26
31
  auto_notify_on_exit?: boolean;
32
+ /** 命令执行 sandbox 模式;默认 safe。 */
33
+ sandbox?: ShellSandboxMode;
34
+ /** 请求 unrestricted sandbox 时展示给用户的原因。 */
35
+ reason?: string;
27
36
  };
28
37
 
29
38
  /**
@@ -42,6 +51,10 @@ export type ShellExecInput = {
42
51
  timeout_ms?: number;
43
52
  /** 最多返回多少输出 token。 */
44
53
  max_output_tokens?: number;
54
+ /** 命令执行 sandbox 模式;默认 safe。 */
55
+ sandbox?: ShellSandboxMode;
56
+ /** 请求 unrestricted sandbox 时展示给用户的原因。 */
57
+ reason?: string;
45
58
  };
46
59
 
47
60
  /**
@@ -14,6 +14,16 @@ export type ShellSessionStatus =
14
14
  | "killed"
15
15
  | "expired";
16
16
 
17
+ /**
18
+ * shell 执行 sandbox 模式。
19
+ */
20
+ export type ShellSandboxMode = "safe" | "unrestricted";
21
+
22
+ /**
23
+ * unrestricted sandbox 审批状态。
24
+ */
25
+ export type ShellApprovalStatus = "approved" | "denied" | "expired";
26
+
17
27
  /**
18
28
  * shell 会话关联的外部引用。
19
29
  *
@@ -50,6 +60,16 @@ export type ShellSessionSnapshot = {
50
60
  shellPath: string;
51
61
  /** 当前 shell 是否运行在 sandbox 中。 */
52
62
  sandboxed?: boolean;
63
+ /** 当前 shell 的 Downcity sandbox 模式。 */
64
+ sandboxMode?: ShellSandboxMode;
65
+ /** unrestricted sandbox 审批状态。 */
66
+ approvalStatus?: ShellApprovalStatus;
67
+ /** unrestricted sandbox 审批请求 ID。 */
68
+ approvalId?: string;
69
+ /** unrestricted sandbox 申请原因。 */
70
+ approvalReason?: string;
71
+ /** 当前 shell 是否允许继续写入 stdin。 */
72
+ stdinWritable?: boolean;
53
73
  /** 当前 shell 使用的 sandbox backend。 */
54
74
  sandboxBackend?: string;
55
75
  /** 当前 shell 采用的 sandbox 网络模式。 */
@@ -112,6 +132,12 @@ export type ShellStartRequest = {
112
132
  ownerContextId?: string;
113
133
  /** 是否在 shell 结束后自动回投主 chat agent。 */
114
134
  autoNotifyOnExit?: boolean;
135
+ /** 命令执行 sandbox 模式;默认 safe。 */
136
+ sandbox?: ShellSandboxMode;
137
+ /** 请求 unrestricted sandbox 时展示给用户的原因。 */
138
+ reason?: string;
139
+ /** 内部审批来源工具名;普通调用方不需要传。 */
140
+ approvalToolName?: "shell_exec" | "shell_start";
115
141
  };
116
142
 
117
143
  /**
@@ -134,6 +160,10 @@ export type ShellExecRequest = {
134
160
  timeoutMs?: number;
135
161
  /** 单次读取输出返回给模型的 token 上限。 */
136
162
  maxOutputTokens?: number;
163
+ /** 命令执行 sandbox 模式;默认 safe。 */
164
+ sandbox?: ShellSandboxMode;
165
+ /** 请求 unrestricted sandbox 时展示给用户的原因。 */
166
+ reason?: string;
137
167
  };
138
168
 
139
169
  /**
@@ -211,6 +211,7 @@ export async function spawnLinuxBubblewrapSandbox(
211
211
  child,
212
212
  cwd: params.actualCwd,
213
213
  sandboxed: true,
214
+ sandboxMode: "safe",
214
215
  backend: "linux-bubblewrap",
215
216
  networkMode: params.config.networkMode,
216
217
  sandboxDir: params.config.sandboxDir,
@@ -180,6 +180,7 @@ export async function spawnMacOsSeatbeltSandbox(
180
180
  child,
181
181
  cwd: params.actualCwd,
182
182
  sandboxed: true,
183
+ sandboxMode: "safe",
183
184
  backend: "macos-seatbelt",
184
185
  networkMode: params.config.networkMode,
185
186
  sandboxDir: params.config.sandboxDir,
@@ -12,6 +12,7 @@ import type { SandboxSpawnResult } from "@/sandbox/types/SandboxRuntime.js";
12
12
  import { resolveSandboxConfig, resolveSandboxCwd } from "@/sandbox/SandboxConfigResolver.js";
13
13
  import { spawnMacOsSeatbeltSandbox } from "@/sandbox/MacOsSeatbeltSandbox.js";
14
14
  import { spawnLinuxBubblewrapSandbox } from "@/sandbox/LinuxBubblewrapSandbox.js";
15
+ import { spawnUnrestrictedSandbox } from "@/sandbox/UnrestrictedSandbox.js";
15
16
 
16
17
  /**
17
18
  * 启动 shell 子进程。
@@ -25,6 +26,7 @@ export async function spawnShellProcess(params: {
25
26
  shellPath: string;
26
27
  login: boolean;
27
28
  baseEnv: NodeJS.ProcessEnv;
29
+ sandboxMode?: "safe" | "unrestricted";
28
30
  }): Promise<SandboxSpawnResult> {
29
31
  return spawnInSandbox({
30
32
  context: params.context,
@@ -35,6 +37,7 @@ export async function spawnShellProcess(params: {
35
37
  shellPath: params.shellPath,
36
38
  login: params.login,
37
39
  baseEnv: params.baseEnv,
40
+ sandboxMode: params.sandboxMode,
38
41
  });
39
42
  }
40
43
 
@@ -50,7 +53,21 @@ export async function spawnInSandbox(params: {
50
53
  shellPath: string;
51
54
  login: boolean;
52
55
  baseEnv: NodeJS.ProcessEnv;
56
+ sandboxMode?: "safe" | "unrestricted";
53
57
  }): Promise<SandboxSpawnResult> {
58
+ if (params.sandboxMode === "unrestricted") {
59
+ return spawnUnrestrictedSandbox({
60
+ executionId: params.executionId,
61
+ executionDir: params.executionDir,
62
+ cmd: params.cmd,
63
+ cwd: params.cwd,
64
+ shellPath: params.shellPath,
65
+ login: params.login,
66
+ baseEnv: params.baseEnv,
67
+ actualCwd: params.cwd,
68
+ });
69
+ }
70
+
54
71
  const config = resolveSandboxConfig(params.context);
55
72
  const actualCwd = resolveSandboxCwd({
56
73
  rootPath: config.rootPath,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Unrestricted sandbox backend。
3
+ *
4
+ * 关键点(中文)
5
+ * - 这是 Downcity Runtime 管理的高权限执行环境,不是 agent 直接访问宿主 shell。
6
+ * - 进程继承宿主可见文件系统与环境边界,但必须由上层 approval 流程批准后才能调用。
7
+ * - 本 backend 只负责 spawn,不做审批、审计或风险判断。
8
+ */
9
+
10
+ import { spawn } from "node:child_process";
11
+ import fs from "fs-extra";
12
+ import type {
13
+ SandboxSpawnParams,
14
+ SandboxSpawnResult,
15
+ } from "@/sandbox/types/SandboxRuntime.js";
16
+
17
+ /**
18
+ * 在 unrestricted sandbox 中启动 shell 子进程。
19
+ */
20
+ export async function spawnUnrestrictedSandbox(
21
+ params: Omit<SandboxSpawnParams, "config"> & { actualCwd: string },
22
+ ): Promise<SandboxSpawnResult> {
23
+ await fs.ensureDir(params.executionDir);
24
+
25
+ const child = spawn(
26
+ params.shellPath,
27
+ [
28
+ params.login ? "-lc" : "-c",
29
+ params.cmd,
30
+ ],
31
+ {
32
+ cwd: params.actualCwd,
33
+ stdio: "pipe",
34
+ env: params.baseEnv,
35
+ },
36
+ );
37
+
38
+ child.stdout.setEncoding("utf8");
39
+ child.stderr.setEncoding("utf8");
40
+
41
+ return {
42
+ child,
43
+ cwd: params.actualCwd,
44
+ sandboxed: false,
45
+ sandboxMode: "unrestricted",
46
+ backend: "unrestricted-host",
47
+ networkMode: "full",
48
+ sandboxDir: "",
49
+ homeDir: String(params.baseEnv.HOME || ""),
50
+ tmpDir: String(params.baseEnv.TMPDIR || "/tmp"),
51
+ cacheDir: String(params.baseEnv.XDG_CACHE_HOME || ""),
52
+ };
53
+ }
@@ -14,7 +14,7 @@ import type { SandboxNetworkMode } from "@/sandbox/types/Sandbox.js";
14
14
  /**
15
15
  * 当前内置支持的 sandbox backend。
16
16
  */
17
- export type SandboxBackend = "macos-seatbelt" | "linux-bubblewrap";
17
+ export type SandboxBackend = "macos-seatbelt" | "linux-bubblewrap" | "unrestricted-host";
18
18
 
19
19
  /**
20
20
  * sandbox 会话状态。
@@ -305,6 +305,11 @@ export interface ResolvedSandboxConfig extends SandboxConfig {
305
305
  */
306
306
  backend: SandboxBackend;
307
307
 
308
+ /**
309
+ * 当前 Downcity sandbox 模式。
310
+ */
311
+ sandboxMode?: "safe" | "unrestricted";
312
+
308
313
  /**
309
314
  * 当前 agent 级 sandbox 的持久目录。
310
315
  *
@@ -398,6 +403,11 @@ export interface SandboxSpawnResult {
398
403
  */
399
404
  sandboxed: boolean;
400
405
 
406
+ /**
407
+ * 当前 Downcity sandbox 模式。
408
+ */
409
+ sandboxMode?: "safe" | "unrestricted";
410
+
401
411
  /**
402
412
  * 当前使用的 backend 名称。
403
413
  */
@@ -270,6 +270,9 @@ export class Session implements AgentSession {
270
270
  getExecutor: () => this.executor.getExecutor(),
271
271
  prompt: async (input) => await this.prompt(input),
272
272
  subscribe: (subscriber) => this.subscribe(subscriber),
273
+ publishEvent: (event) => {
274
+ this.eventHub.publish(event);
275
+ },
273
276
  clearExecutor: () => {
274
277
  this.executor.clearExecutor();
275
278
  },
@@ -140,6 +140,7 @@ export class SessionTurnService {
140
140
  }> {
141
141
  const tool_name_by_call_id = new Map<string, string>();
142
142
  const run_context: SessionRunContext = {
143
+ turnId: input.turnId,
143
144
  sessionId: this.session_id,
144
145
  projectRoot: this.project_root,
145
146
  onStepCallback: input.onStepMerge,
@@ -10,6 +10,7 @@ import type { SessionPort } from "@/types/runtime/agent/AgentContext.js";
10
10
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
11
11
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
12
12
  import type {
13
+ AgentSessionEvent,
13
14
  AgentSessionSubscriber,
14
15
  AgentSessionUnsubscribe,
15
16
  } from "@/types/sdk/AgentSessionEvent.js";
@@ -37,6 +38,10 @@ export interface CreateRuntimeSessionPortParams {
37
38
  subscribe: (
38
39
  subscriber: AgentSessionSubscriber,
39
40
  ) => AgentSessionUnsubscribe;
41
+ /**
42
+ * 发布一条 session runtime 事件。
43
+ */
44
+ publishEvent: (event: AgentSessionEvent) => void;
40
45
  /**
41
46
  * 清理当前 session executor 状态。
42
47
  */
@@ -88,6 +93,9 @@ export function createRuntimeSessionPort(
88
93
  subscribe: (subscriber) => {
89
94
  return params.subscribe(subscriber);
90
95
  },
96
+ publishEvent: (event) => {
97
+ params.publishEvent(event);
98
+ },
91
99
  clearExecutor: () => {
92
100
  params.clearExecutor();
93
101
  },
@@ -18,6 +18,15 @@ import type { FileUIPart } from "ai";
18
18
  * 单次 session run 的运行上下文。
19
19
  */
20
20
  export interface SessionRunContext {
21
+ /**
22
+ * 当前执行所属的 turn 标识。
23
+ *
24
+ * 关键点(中文)
25
+ * - session 是长期对话容器,turn 是单次用户输入触发的执行轮次。
26
+ * - 工具运行时发布 session event 时应优先使用该字段,避免把 sessionId 误当 turnId。
27
+ */
28
+ turnId?: string;
29
+
21
30
  /**
22
31
  * 当前执行所属的 session 标识。
23
32
  */
@@ -31,6 +31,7 @@ import type {
31
31
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
32
32
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
33
33
  import type {
34
+ AgentSessionEvent,
34
35
  AgentSessionSubscriber,
35
36
  AgentSessionUnsubscribe,
36
37
  } from "@/types/sdk/AgentSessionEvent.js";
@@ -132,6 +133,14 @@ export interface SessionPort {
132
133
  * - 历史消息仍通过 `getHistoryStore()` / SDK `history()` 读取。
133
134
  */
134
135
  subscribe(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe;
136
+ /**
137
+ * 发布一条 session runtime 事件。
138
+ *
139
+ * 关键点(中文)
140
+ * - plugin runtime 用它把审批、外部进度等非模型 chunk 事件推送给订阅方。
141
+ * - 历史消息持久化仍由 appendUserMessage / appendAssistantMessage 负责。
142
+ */
143
+ publishEvent(event: AgentSessionEvent): void;
135
144
  /**
136
145
  * 清理当前 session 的 executor 缓存。
137
146
  */
@@ -128,6 +128,96 @@ export interface AgentSessionToolResultEvent {
128
128
  result: JsonValue;
129
129
  }
130
130
 
131
+ /**
132
+ * 工具审批请求事件。
133
+ */
134
+ export interface AgentSessionToolApprovalRequestEvent {
135
+ /**
136
+ * 当前事件类型。
137
+ */
138
+ type: "tool-approval-request";
139
+
140
+ /**
141
+ * 当前审批所属 turn。
142
+ */
143
+ turnId: string;
144
+
145
+ /**
146
+ * 当前工具调用唯一标识。
147
+ */
148
+ toolCallId: string;
149
+
150
+ /**
151
+ * 当前工具名称。
152
+ */
153
+ toolName: string;
154
+
155
+ /**
156
+ * 当前审批请求唯一标识。
157
+ */
158
+ approvalId: string;
159
+
160
+ /**
161
+ * 请求执行的 sandbox 模式。
162
+ */
163
+ sandbox: "unrestricted";
164
+
165
+ /**
166
+ * 请求执行的命令文本。
167
+ */
168
+ cmd: string;
169
+
170
+ /**
171
+ * 命令执行目录。
172
+ */
173
+ cwd: string;
174
+
175
+ /**
176
+ * Agent 给用户展示的申请原因。
177
+ */
178
+ reason: string;
179
+
180
+ /**
181
+ * 当前审批状态。
182
+ */
183
+ status: "pending";
184
+ }
185
+
186
+ /**
187
+ * 工具审批结果事件。
188
+ */
189
+ export interface AgentSessionToolApprovalResultEvent {
190
+ /**
191
+ * 当前事件类型。
192
+ */
193
+ type: "tool-approval-result";
194
+
195
+ /**
196
+ * 当前审批所属 turn。
197
+ */
198
+ turnId: string;
199
+
200
+ /**
201
+ * 当前工具调用唯一标识。
202
+ */
203
+ toolCallId: string;
204
+
205
+ /**
206
+ * 当前工具名称。
207
+ */
208
+ toolName: string;
209
+
210
+ /**
211
+ * 当前审批请求唯一标识。
212
+ */
213
+ approvalId: string;
214
+
215
+ /**
216
+ * 用户最终决策。
217
+ */
218
+ decision: "approved" | "denied" | "expired";
219
+ }
220
+
131
221
  /**
132
222
  * assistant step 完成事件。
133
223
  */
@@ -236,6 +326,8 @@ export type AgentSessionEvent =
236
326
  | AgentSessionReasoningDeltaEvent
237
327
  | AgentSessionToolCallEvent
238
328
  | AgentSessionToolResultEvent
329
+ | AgentSessionToolApprovalRequestEvent
330
+ | AgentSessionToolApprovalResultEvent
239
331
  | AgentSessionAssistantStepEvent
240
332
  | AgentSessionTitleEvent
241
333
  | AgentSessionTurnFinishEvent