@downcity/plugins 1.0.61 → 1.0.66

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.
@@ -19,6 +19,7 @@ import { generateId } from "@downcity/agent/internal/utils/Id.js";
19
19
  import { readChatMetaBySessionId } from "@/chat/runtime/ChatMetaStore.js";
20
20
  import type {
21
21
  ShellActionResponse,
22
+ ShellApprovalStatus,
22
23
  ShellCloseRequest,
23
24
  ShellExecRequest,
24
25
  ShellQueryRequest,
@@ -46,6 +47,12 @@ import {
46
47
  updateSessionSnapshot,
47
48
  } from "./ShellActionRuntimeSupport.js";
48
49
  import { attachShellProcessEventHandlers } from "./ShellProcessEvents.js";
50
+ import {
51
+ listPendingApprovals,
52
+ requestUnrestrictedApproval,
53
+ resolveApproval,
54
+ validateUnrestrictedRequest,
55
+ } from "./ShellApprovalRuntime.js";
49
56
 
50
57
  export { createShellPluginState } from "./ShellActionRuntimeSupport.js";
51
58
 
@@ -72,6 +79,20 @@ export async function closeAllShellSessions(
72
79
  state: ShellPluginState,
73
80
  force = false,
74
81
  ): Promise<void> {
82
+ for (const approval of Array.from(state.approvals.values())) {
83
+ if (state.context) {
84
+ await resolveApproval({
85
+ state,
86
+ context: state.context,
87
+ approvalId: approval.approvalId,
88
+ decision: "expired",
89
+ }).catch(() => undefined);
90
+ continue;
91
+ }
92
+ clearTimeout(approval.timer);
93
+ state.approvals.delete(approval.approvalId);
94
+ approval.resolve("expired");
95
+ }
75
96
  const closing = Array.from(state.sessions.values()).map(async (session) => {
76
97
  if (
77
98
  session.snapshot.status !== "running" &&
@@ -93,6 +114,95 @@ export async function closeAllShellSessions(
93
114
  await Promise.all(closing);
94
115
  }
95
116
 
117
+ function resolveSandboxMode(value: unknown): "safe" | "unrestricted" {
118
+ return value === "unrestricted" ? "unrestricted" : "safe";
119
+ }
120
+
121
+ function buildDeniedApprovalResponse(params: {
122
+ shellId: string;
123
+ ownerContextId?: string;
124
+ cmd: string;
125
+ cwd: string;
126
+ shellPath: string;
127
+ approvalId: string;
128
+ reason: string;
129
+ approvalStatus: ShellApprovalStatus;
130
+ }): ShellActionResponse {
131
+ const now = nowMs();
132
+ const message = params.approvalStatus === "expired"
133
+ ? "Unrestricted sandbox approval expired."
134
+ : "User denied unrestricted sandbox execution.";
135
+ return buildActionResponse({
136
+ shell: {
137
+ shellId: params.shellId,
138
+ ...(params.ownerContextId ? { ownerContextId: params.ownerContextId } : {}),
139
+ cmd: params.cmd,
140
+ cwd: params.cwd,
141
+ shellPath: params.shellPath,
142
+ sandboxed: false,
143
+ sandboxMode: "unrestricted",
144
+ sandboxBackend: "unrestricted-host",
145
+ sandboxNetworkMode: "full",
146
+ approvalStatus: params.approvalStatus,
147
+ approvalId: params.approvalId,
148
+ approvalReason: params.reason,
149
+ stdinWritable: true,
150
+ status: params.approvalStatus === "expired" ? "expired" : "failed",
151
+ startedAt: now,
152
+ updatedAt: now,
153
+ endedAt: now,
154
+ exitCode: -1,
155
+ lastOutputPreview: message,
156
+ outputChars: message.length,
157
+ droppedChars: 0,
158
+ version: 1,
159
+ autoNotifyOnExit: false,
160
+ notificationSent: false,
161
+ externalRefs: [],
162
+ },
163
+ chunk: {
164
+ shellId: params.shellId,
165
+ output: message,
166
+ startCursor: 0,
167
+ endCursor: message.length,
168
+ originalChars: message.length,
169
+ originalLines: 1,
170
+ hasMoreOutput: false,
171
+ },
172
+ note: message,
173
+ });
174
+ }
175
+
176
+ function buildDeniedWriteApprovalResponse(params: {
177
+ session: ShellSessionRuntimeState;
178
+ approvalId: string;
179
+ reason: string;
180
+ approvalStatus: ShellApprovalStatus;
181
+ }): ShellActionResponse {
182
+ const message = params.approvalStatus === "expired"
183
+ ? "Unrestricted sandbox approval expired."
184
+ : "User denied unrestricted sandbox execution.";
185
+ return buildActionResponse({
186
+ shell: {
187
+ ...params.session.snapshot,
188
+ approvalStatus: params.approvalStatus,
189
+ approvalId: params.approvalId,
190
+ approvalReason: params.reason,
191
+ stdinWritable: true,
192
+ },
193
+ chunk: {
194
+ shellId: params.session.snapshot.shellId,
195
+ output: message,
196
+ startCursor: 0,
197
+ endCursor: message.length,
198
+ originalChars: message.length,
199
+ originalLines: 1,
200
+ hasMoreOutput: false,
201
+ },
202
+ note: message,
203
+ });
204
+ }
205
+
96
206
  /**
97
207
  * 启动一个 shell session。
98
208
  */
@@ -111,6 +221,8 @@ export async function startShellSession(
111
221
  const shellPath =
112
222
  String(request.shell || resolveDefaultShellPath()).trim() || resolveDefaultShellPath();
113
223
  const login = request.login !== false;
224
+ const sandboxMode = resolveSandboxMode(request.sandbox);
225
+ const reason = String(request.reason || "").trim();
114
226
  const ownerContextId = resolveOwnerContextId(request.ownerContextId);
115
227
  const canAutoNotifyByContext = ownerContextId
116
228
  ? Boolean(
@@ -124,6 +236,37 @@ export async function startShellSession(
124
236
  await fs.ensureDir(shellDir);
125
237
  await fs.writeFile(outputFilePath, "", "utf-8");
126
238
 
239
+ let approvalId: string | undefined;
240
+ let approvalStatus: ShellApprovalStatus | undefined;
241
+ if (sandboxMode === "unrestricted") {
242
+ const validationError = validateUnrestrictedRequest({ cmd, reason });
243
+ if (validationError) throw new Error(validationError);
244
+ const approval = await requestUnrestrictedApproval({
245
+ state,
246
+ context,
247
+ shellId,
248
+ toolName: request.approvalToolName || "shell_start",
249
+ cmd,
250
+ cwd,
251
+ reason,
252
+ ...(ownerContextId ? { ownerContextId } : {}),
253
+ });
254
+ approvalId = approval.approvalId;
255
+ approvalStatus = approval.status;
256
+ if (approval.status !== "approved") {
257
+ return buildDeniedApprovalResponse({
258
+ shellId,
259
+ ...(ownerContextId ? { ownerContextId } : {}),
260
+ cmd,
261
+ cwd,
262
+ shellPath,
263
+ approvalId: approval.approvalId,
264
+ reason,
265
+ approvalStatus: approval.status,
266
+ });
267
+ }
268
+ }
269
+
127
270
  const spawnResult = await spawnShellProcess({
128
271
  context,
129
272
  shellId,
@@ -133,6 +276,7 @@ export async function startShellSession(
133
276
  shellPath,
134
277
  login,
135
278
  baseEnv: buildShellEnv(context),
279
+ sandboxMode,
136
280
  });
137
281
  const child = spawnResult.child;
138
282
  const actualCwd = spawnResult.cwd;
@@ -150,12 +294,17 @@ export async function startShellSession(
150
294
  cwd: actualCwd,
151
295
  shellPath,
152
296
  sandboxed: spawnResult.sandboxed,
297
+ sandboxMode: spawnResult.sandboxMode || sandboxMode,
153
298
  sandboxBackend: spawnResult.backend,
154
299
  sandboxNetworkMode: spawnResult.networkMode,
155
300
  sandboxDir: spawnResult.sandboxDir,
156
301
  sandboxHomeDir: spawnResult.homeDir,
157
302
  sandboxTmpDir: spawnResult.tmpDir,
158
303
  sandboxCacheDir: spawnResult.cacheDir,
304
+ ...(approvalStatus ? { approvalStatus } : {}),
305
+ ...(approvalId ? { approvalId } : {}),
306
+ ...(reason ? { approvalReason: reason } : {}),
307
+ stdinWritable: true,
159
308
  status: "running",
160
309
  ...(typeof child.pid === "number" ? { pid: child.pid } : {}),
161
310
  startedAt,
@@ -302,6 +451,39 @@ export async function writeShellSession(
302
451
  if (!session.child.stdin.writable) {
303
452
  throw new Error(`shell session ${shellId} stdin is closed`);
304
453
  }
454
+ if (session.snapshot.stdinWritable === false) {
455
+ throw new Error(`shell session ${shellId} stdin is closed`);
456
+ }
457
+
458
+ let approvalId: string | undefined;
459
+ let approvalStatus: ShellApprovalStatus | undefined;
460
+ const reason = String(request.reason || "").trim();
461
+ if (session.snapshot.sandboxMode === "unrestricted") {
462
+ const validationError = validateUnrestrictedRequest({ cmd: chars, reason });
463
+ if (validationError) throw new Error(validationError);
464
+ const approval = await requestUnrestrictedApproval({
465
+ state,
466
+ context,
467
+ shellId,
468
+ toolName: "shell_write",
469
+ cmd: chars,
470
+ cwd: session.snapshot.cwd,
471
+ reason,
472
+ ...(session.snapshot.ownerContextId ? { ownerContextId: session.snapshot.ownerContextId } : {}),
473
+ inputPreview: chars,
474
+ inputChars: chars.length,
475
+ });
476
+ approvalId = approval.approvalId;
477
+ approvalStatus = approval.status;
478
+ if (approval.status !== "approved") {
479
+ return buildDeniedWriteApprovalResponse({
480
+ session,
481
+ approvalId: approval.approvalId,
482
+ reason,
483
+ approvalStatus: approval.status,
484
+ });
485
+ }
486
+ }
305
487
  await new Promise<void>((resolve, reject) => {
306
488
  session.child.stdin.write(chars, (error) => {
307
489
  if (error) {
@@ -312,7 +494,13 @@ export async function writeShellSession(
312
494
  });
313
495
  });
314
496
  return buildActionResponse({
315
- shell: session.snapshot,
497
+ shell: {
498
+ ...session.snapshot,
499
+ ...(approvalStatus ? { approvalStatus } : {}),
500
+ ...(approvalId ? { approvalId } : {}),
501
+ ...(reason ? { approvalReason: reason } : {}),
502
+ stdinWritable: true,
503
+ },
316
504
  note: chars ? "stdin written" : "no chars written",
317
505
  });
318
506
  }
@@ -460,6 +648,9 @@ export async function execShellCommand(
460
648
  ...(request.cwd ? { cwd: request.cwd } : {}),
461
649
  ...(request.shell ? { shell: request.shell } : {}),
462
650
  login: request.login,
651
+ sandbox: request.sandbox,
652
+ reason: request.reason,
653
+ approvalToolName: "shell_exec",
463
654
  inlineWaitMs: Math.min(state.options.defaultInlineWaitMs, timeoutMs),
464
655
  maxOutputTokens: request.maxOutputTokens,
465
656
  autoNotifyOnExit: false,
@@ -562,3 +753,42 @@ export async function execShellCommand(
562
753
  note: "shell exec completed in one-shot mode",
563
754
  });
564
755
  }
756
+
757
+ /**
758
+ * 列出 pending unrestricted sandbox 审批。
759
+ */
760
+ export function listShellApprovals(state: ShellPluginState) {
761
+ return listPendingApprovals(state);
762
+ }
763
+
764
+ /**
765
+ * 批准 pending unrestricted sandbox 审批。
766
+ */
767
+ export async function approveShellApproval(
768
+ state: ShellPluginState,
769
+ context: AgentContext,
770
+ approvalId: string,
771
+ ): Promise<boolean> {
772
+ return await resolveApproval({
773
+ state,
774
+ context,
775
+ approvalId,
776
+ decision: "approved",
777
+ });
778
+ }
779
+
780
+ /**
781
+ * 拒绝 pending unrestricted sandbox 审批。
782
+ */
783
+ export async function denyShellApproval(
784
+ state: ShellPluginState,
785
+ context: AgentContext,
786
+ approvalId: string,
787
+ ): Promise<boolean> {
788
+ return await resolveApproval({
789
+ state,
790
+ context,
791
+ approvalId,
792
+ decision: "denied",
793
+ });
794
+ }
@@ -46,6 +46,7 @@ const DEFAULT_SHELL_PLUGIN_OPTIONS: ResolvedShellPluginOptions = {
46
46
  defaultInlineWaitMs: 1_200,
47
47
  defaultWaitTimeoutMs: 10_000,
48
48
  defaultExecTimeoutMs: 60_000,
49
+ defaultApprovalTimeoutMs: 120_000,
49
50
  };
50
51
 
51
52
  /**
@@ -121,6 +122,10 @@ export function resolveShellPluginOptions(
121
122
  options.defaultExecTimeoutMs,
122
123
  DEFAULT_SHELL_PLUGIN_OPTIONS.defaultExecTimeoutMs,
123
124
  ),
125
+ defaultApprovalTimeoutMs: readPositiveInteger(
126
+ options.defaultApprovalTimeoutMs,
127
+ DEFAULT_SHELL_PLUGIN_OPTIONS.defaultApprovalTimeoutMs,
128
+ ),
124
129
  };
125
130
  }
126
131
 
@@ -133,6 +138,7 @@ export function createShellPluginState(
133
138
  return {
134
139
  options: resolveShellPluginOptions(options),
135
140
  sessions: new Map<string, ShellSessionRuntimeState>(),
141
+ approvals: new Map(),
136
142
  context: null,
137
143
  };
138
144
  }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Shell unrestricted sandbox 审批运行时。
3
+ *
4
+ * 关键点(中文)
5
+ * - agent 只能通过 shell tool 请求 unrestricted sandbox;真正执行前必须等待用户确认。
6
+ * - 审批结果最终回到原 tool result;session event 只用于 UI/CLI/Console 展示和操作。
7
+ * - V1 授权粒度固定为单次命令、单次 shell_start 启动,或单次 shell_write 输入。
8
+ */
9
+
10
+ import fs from "fs-extra";
11
+ import path from "node:path";
12
+ import { generateId } from "@downcity/agent/internal/utils/Id.js";
13
+ import { getSessionRunContext } from "@downcity/agent/internal/executor/SessionRunScope.js";
14
+ import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
15
+ import type {
16
+ ShellApprovalStatus,
17
+ ShellApprovalToolName,
18
+ } from "@downcity/agent/internal/executor/tools/shell/types/ShellPlugin.js";
19
+ import type { ShellPluginState } from "@/shell/ShellRuntimeTypes.js";
20
+ import { nowMs } from "./ShellActionRuntimeSupport.js";
21
+
22
+ const DANGEROUS_COMMAND_PATTERNS = [
23
+ /\bsudo\b/,
24
+ /\brm\s+-[^&|;\n]*r[^&|;\n]*f\s+\/(?:\s|$)/,
25
+ /\bchmod\s+-R\s+777\s+\/(?:\s|$)/,
26
+ /\bssh-keygen\b/,
27
+ /\bsecurity\s+(?:add|delete|unlock|set|import|export)-/i,
28
+ /(?:^|[\s;&|])(?:nohup\s+)?[^;&|\n]*(?:&)\s*$/,
29
+ ];
30
+
31
+ function isDangerousCommand(cmd: string): boolean {
32
+ return DANGEROUS_COMMAND_PATTERNS.some((pattern) => pattern.test(cmd));
33
+ }
34
+
35
+ function resolveApprovalOperation(toolName: ShellApprovalToolName): "exec" | "start" | "write" {
36
+ if (toolName === "shell_write") return "write";
37
+ if (toolName === "shell_exec") return "exec";
38
+ return "start";
39
+ }
40
+
41
+ function buildInputPreview(value: string): string {
42
+ const normalized = String(value || "");
43
+ if (normalized.length <= 240) return normalized;
44
+ return `${normalized.slice(0, 240)}...`;
45
+ }
46
+
47
+ function resolveAuditPath(context: AgentContext): string {
48
+ return path.join(context.rootPath, ".downcity", "logs", "unrestricted-sandbox-audit.jsonl");
49
+ }
50
+
51
+ async function appendAudit(params: {
52
+ context: AgentContext;
53
+ record: Record<string, unknown>;
54
+ }): Promise<void> {
55
+ const filePath = resolveAuditPath(params.context);
56
+ await fs.ensureDir(path.dirname(filePath));
57
+ await fs.appendFile(filePath, `${JSON.stringify(params.record)}\n`, "utf-8");
58
+ }
59
+
60
+ function publishApprovalResult(params: {
61
+ context: AgentContext;
62
+ ownerContextId?: string;
63
+ approvalId: string;
64
+ shellId: string;
65
+ toolName: ShellApprovalToolName;
66
+ decision: ShellApprovalStatus;
67
+ }): void {
68
+ const sessionId = String(params.ownerContextId || "").trim();
69
+ if (!sessionId) return;
70
+ const turnId = String(getSessionRunContext()?.turnId || sessionId).trim();
71
+ try {
72
+ params.context.session.get(sessionId).publishEvent({
73
+ type: "tool-approval-result",
74
+ turnId,
75
+ toolCallId: params.shellId,
76
+ toolName: params.toolName,
77
+ approvalId: params.approvalId,
78
+ decision: params.decision,
79
+ });
80
+ } catch {
81
+ // ignore event delivery failures
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 校验 unrestricted sandbox 请求。
87
+ */
88
+ export function validateUnrestrictedRequest(params: {
89
+ cmd: string;
90
+ reason?: string;
91
+ }): string | null {
92
+ const reason = String(params.reason || "").trim();
93
+ if (!reason) {
94
+ return "unrestricted sandbox requires a non-empty reason";
95
+ }
96
+ if (isDangerousCommand(params.cmd)) {
97
+ return "unrestricted sandbox rejected a dangerous command";
98
+ }
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * 请求用户批准 unrestricted sandbox 执行。
104
+ */
105
+ export async function requestUnrestrictedApproval(params: {
106
+ state: ShellPluginState;
107
+ context: AgentContext;
108
+ shellId: string;
109
+ toolName: ShellApprovalToolName;
110
+ cmd: string;
111
+ cwd: string;
112
+ reason: string;
113
+ ownerContextId?: string;
114
+ inputPreview?: string;
115
+ inputChars?: number;
116
+ }): Promise<{
117
+ approvalId: string;
118
+ status: ShellApprovalStatus;
119
+ }> {
120
+ const approvalId = `ap_${generateId()}`;
121
+ const createdAt = nowMs();
122
+ const ownerContextId = String(params.ownerContextId || "").trim() || undefined;
123
+ const operation = resolveApprovalOperation(params.toolName);
124
+ const inputPreview = params.inputPreview !== undefined
125
+ ? buildInputPreview(params.inputPreview)
126
+ : undefined;
127
+
128
+ const status = await new Promise<ShellApprovalStatus>((resolve) => {
129
+ const timer = setTimeout(() => {
130
+ resolveApproval({
131
+ state: params.state,
132
+ context: params.context,
133
+ approvalId,
134
+ decision: "expired",
135
+ }).catch(() => undefined);
136
+ }, params.state.options.defaultApprovalTimeoutMs);
137
+ if (typeof timer.unref === "function") timer.unref();
138
+
139
+ params.state.approvals.set(approvalId, {
140
+ approvalId,
141
+ shellId: params.shellId,
142
+ ...(ownerContextId ? { ownerContextId } : {}),
143
+ toolName: params.toolName,
144
+ cmd: params.cmd,
145
+ operation,
146
+ ...(inputPreview !== undefined ? { inputPreview } : {}),
147
+ ...(typeof params.inputChars === "number" ? { inputChars: params.inputChars } : {}),
148
+ cwd: params.cwd,
149
+ reason: params.reason,
150
+ createdAt,
151
+ timer,
152
+ resolve,
153
+ });
154
+
155
+ if (ownerContextId) {
156
+ const turnId = String(getSessionRunContext()?.turnId || ownerContextId).trim();
157
+ try {
158
+ params.context.session.get(ownerContextId).publishEvent({
159
+ type: "tool-approval-request",
160
+ turnId,
161
+ toolCallId: params.shellId,
162
+ toolName: params.toolName,
163
+ approvalId,
164
+ sandbox: "unrestricted",
165
+ cmd: params.cmd,
166
+ cwd: params.cwd,
167
+ reason: params.reason,
168
+ status: "pending",
169
+ operation,
170
+ shellId: params.shellId,
171
+ ...(inputPreview !== undefined ? { inputPreview } : {}),
172
+ ...(typeof params.inputChars === "number" ? { inputChars: params.inputChars } : {}),
173
+ });
174
+ } catch {
175
+ // ignore event delivery failures
176
+ }
177
+ }
178
+
179
+ appendAudit({
180
+ context: params.context,
181
+ record: {
182
+ event: "approval_requested",
183
+ approval_id: approvalId,
184
+ session_id: ownerContextId || null,
185
+ tool_call_id: params.shellId,
186
+ agent_id: params.context.config?.id || null,
187
+ cmd: params.cmd,
188
+ operation,
189
+ ...(inputPreview !== undefined ? { input_preview: inputPreview } : {}),
190
+ ...(typeof params.inputChars === "number" ? { input_chars: params.inputChars } : {}),
191
+ cwd: params.cwd,
192
+ reason: params.reason,
193
+ created_at: new Date(createdAt).toISOString(),
194
+ },
195
+ }).catch(() => undefined);
196
+ });
197
+
198
+ return { approvalId, status };
199
+ }
200
+
201
+ /**
202
+ * 兑现 unrestricted sandbox 审批。
203
+ */
204
+ export async function resolveApproval(params: {
205
+ state: ShellPluginState;
206
+ context: AgentContext;
207
+ approvalId: string;
208
+ decision: ShellApprovalStatus;
209
+ }): Promise<boolean> {
210
+ const approval = params.state.approvals.get(params.approvalId);
211
+ if (!approval) return false;
212
+ params.state.approvals.delete(params.approvalId);
213
+ clearTimeout(approval.timer);
214
+ approval.resolve(params.decision);
215
+
216
+ publishApprovalResult({
217
+ context: params.context,
218
+ ownerContextId: approval.ownerContextId,
219
+ approvalId: approval.approvalId,
220
+ shellId: approval.shellId,
221
+ toolName: approval.toolName,
222
+ decision: params.decision,
223
+ });
224
+
225
+ await appendAudit({
226
+ context: params.context,
227
+ record: {
228
+ event: "approval_resolved",
229
+ approval_id: approval.approvalId,
230
+ session_id: approval.ownerContextId || null,
231
+ tool_call_id: approval.shellId,
232
+ agent_id: params.context.config?.id || null,
233
+ cmd: approval.cmd,
234
+ operation: approval.operation,
235
+ ...(approval.inputPreview !== undefined ? { input_preview: approval.inputPreview } : {}),
236
+ ...(typeof approval.inputChars === "number" ? { input_chars: approval.inputChars } : {}),
237
+ cwd: approval.cwd,
238
+ reason: approval.reason,
239
+ decision: params.decision,
240
+ resolved_at: new Date(nowMs()).toISOString(),
241
+ },
242
+ }).catch(() => undefined);
243
+
244
+ return true;
245
+ }
246
+
247
+ /**
248
+ * 列出 pending unrestricted sandbox 审批。
249
+ */
250
+ export function listPendingApprovals(state: ShellPluginState): Array<{
251
+ approvalId: string;
252
+ shellId: string;
253
+ ownerContextId?: string;
254
+ toolName: ShellApprovalToolName;
255
+ cmd: string;
256
+ operation: "exec" | "start" | "write";
257
+ inputPreview?: string;
258
+ inputChars?: number;
259
+ cwd: string;
260
+ reason: string;
261
+ createdAt: number;
262
+ }> {
263
+ return Array.from(state.approvals.values()).map((approval) => ({
264
+ approvalId: approval.approvalId,
265
+ shellId: approval.shellId,
266
+ ...(approval.ownerContextId ? { ownerContextId: approval.ownerContextId } : {}),
267
+ toolName: approval.toolName,
268
+ cmd: approval.cmd,
269
+ operation: approval.operation,
270
+ ...(approval.inputPreview !== undefined ? { inputPreview: approval.inputPreview } : {}),
271
+ ...(typeof approval.inputChars === "number" ? { inputChars: approval.inputChars } : {}),
272
+ cwd: approval.cwd,
273
+ reason: approval.reason,
274
+ createdAt: approval.createdAt,
275
+ }));
276
+ }
@@ -59,6 +59,11 @@ export interface ShellPluginOptions {
59
59
  * `shell.exec` 默认总超时,单位毫秒。
60
60
  */
61
61
  defaultExecTimeoutMs?: number;
62
+
63
+ /**
64
+ * unrestricted sandbox 审批默认超时时间,单位毫秒。
65
+ */
66
+ defaultApprovalTimeoutMs?: number;
62
67
  }
63
68
 
64
69
  /**
@@ -109,4 +114,9 @@ export interface ResolvedShellPluginOptions {
109
114
  * `shell.exec` 默认总超时,单位毫秒。
110
115
  */
111
116
  defaultExecTimeoutMs: number;
117
+
118
+ /**
119
+ * unrestricted sandbox 审批默认超时时间,单位毫秒。
120
+ */
121
+ defaultApprovalTimeoutMs: number;
112
122
  }