@trim21/personal-pi-extensions 0.1.20261012023047 → 0.1.20261012031237

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,8 +35,9 @@
35
35
  `sleep`。
36
36
 
37
37
  - **嵌套调用经工具总线**:`call("Bash", { command })` 最终执行的是 `Bash` 工具自己的
38
- `execute`,所以工具的审批照常生效(Bash 沙箱外执行会弹自己的提权确认)。codemode
39
- 不额外加确认层:脚本里连发十次调用就是十次工具自己的审批(需要审批的那些)。
38
+ `execute`。**脚本里不弹审批**:需要人工授权的调用(写工作区外、Bash 申请沙箱外执行)
39
+ 在 codemode 内直接以 `CallFailedError` 失败,错误信息提示改用直调;模型直调同一个工具时
40
+ 审批照常出现。此外 codemode 不额外加确认层:不需要审批的调用不会多出任何确认框。
40
41
  - **可调用集合 = 声明了结构化输出的工具**(有 `structuredSchema` 的才进),执行时再与当前
41
42
  active 列表求交,所以 `personalExtensions.disabledTools`、pi 的 `defaultTools` /
42
43
  `--tools`、子代理的工具白名单都同样约束脚本。脚本拿到的返回值必须有确定的形状,因此没给
@@ -62,10 +63,14 @@
62
63
  结构的工具不进集合,`call()` 会报「not available in codemode」。
63
64
  - **脚本接口**:`call` / `CallFailedError` / `ALL_TOOLS` / `fs.read` / `fs.write` /
64
65
  `text` / `image` / `exit` / `sleep` / `console.*` / `store.set` / `store.get` / `store.list`
65
- (会话内持久的键值表);首行可选 `// @options: {"max_output_tokens": 10000}`。
66
- 脚本没有超时:死循环由调用方中止(Esc)结束,等嵌套调用返回(含用户审批弹窗)多久都不算超时。
66
+ (会话内持久的键值表);首行可选
67
+ `// @options: {"max_output_tokens": 10000, "timeout_ms": 600000}`——选项留在脚本里而不是工具参数,
68
+ 因为 pi 的 grammar 约束(`constrainedSampling`)只支持单个自由文本参数,多出来的参数在支持
69
+ grammar 的模型上会被静默忽略。不声明 `timeout_ms` 时脚本没有时间上限:死循环由调用方中止(Esc)
70
+ 结束,等嵌套调用返回(含慢命令)多久都不算超时。声明了 `timeout_ms` 就有墙钟预算(含等待嵌套
71
+ 调用的时间),到点时正在跑的调用被中止、结果以超时结束,已产生的输出与调用记录仍然返回。
67
72
  `await sleep(seconds)`(秒,可为小数)是脚本唯一的等待手段,由 worker 线程自己的定时器驱动
68
- (不经主线程、不产生进度帧),也没有上限——要等多久由脚本决定,不想等就中止。
73
+ (不经主线程、不产生进度帧),它自己也没有上限——但会被 `timeout_ms` 结束。
69
74
  - **工具描述**里给出每个可调用工具的 `declare function call(name, args): Promise<T>` 重载,
70
75
  参数与返回类型都取自工具自己的 schema,所以模型在写脚本前就知道返回值形状。
71
76
  - **store** 记在每次成功调用工具结果的 `details.store` 上(与 `src/lib/file-reads.ts` 的
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.20261012023047",
3
+ "version": "0.1.20261012031237",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -86,6 +86,8 @@ export interface BwrapExecutionRequest {
86
86
  signal?: AbortSignal;
87
87
  onUpdate?: AgentToolUpdateCallback;
88
88
  ctx: ExtensionContext;
89
+ /** 本次调用不可请求用户审批(codemode 的嵌套调用):提权请求直接失败,不弹框。 */
90
+ unapprovable?: boolean;
89
91
  }
90
92
 
91
93
  /**
@@ -333,6 +335,13 @@ function describeLimits(resolved: ResolvedBwrap): string {
333
335
  */
334
336
  const UNSANDBOXED_DENIED = "User denied unsandboxed execution.";
335
337
 
338
+ /**
339
+ * codemode 内的提权请求:脚本里弹不了审批框(那次工具调用只经 codemode 暴露),
340
+ * 因此直接拒绝,并让模型知道改用直调就能走正常审批。
341
+ */
342
+ const UNSANDBOXED_DENIED_IN_CODEMODE =
343
+ "Unsandboxed execution needs user approval, which codemode cannot ask for. Call Bash directly instead of from a script.";
344
+
336
345
  /** 命令没经沙箱时沿用 prompt 里的说法,给出在沙盒外重跑的手段。 */
337
346
  const SANDBOX_ESCAPE_HATCH =
338
347
  "If the command needs more than that, use the `dangerouslyDisableSandbox` parameter to request unsandboxed execution; the user must approve this request.";
@@ -571,6 +580,7 @@ export class BwrapRuntime {
571
580
  request.description,
572
581
  execCwd,
573
582
  ruleSet,
583
+ request.unapprovable === true,
574
584
  )) === "sandbox";
575
585
  }
576
586
  }
@@ -706,11 +716,16 @@ export class BwrapRuntime {
706
716
  reason: string | undefined,
707
717
  execCwd: string,
708
718
  ruleSet: ApprovalRuleSet,
719
+ unapprovable: boolean,
709
720
  ): Promise<FullAccessGrant> {
710
721
  // hasUI 判定推迟到审批时刻:无 UI 会话弹不了审批框,按用户点 Deny 的标准文案拒绝
711
722
  if (!ctx.hasUI) {
712
723
  throw new Error(UNSANDBOXED_DENIED);
713
724
  }
725
+ // codemode 内一律不弹审批:提权请求直接失败,并提示改用直调
726
+ if (unapprovable) {
727
+ throw new Error(UNSANDBOXED_DENIED_IN_CODEMODE);
728
+ }
714
729
  const decision = await this.approveFullAccessUI(ctx, command, reason, execCwd, ruleSet);
715
730
  // 关闭对话框 = 中断并拒绝,不循环重问
716
731
  if (decision === undefined) {
@@ -127,7 +127,7 @@ export function registerShellTools(bus: ToolBus, pi: ExtensionAPI, runtime: Bwra
127
127
  },
128
128
  { additionalProperties: false },
129
129
  ),
130
- async execute(id, params, signal, onUpdate, ctx) {
130
+ async execute(id, params, signal, onUpdate, ctx, callOptions) {
131
131
  const timeout = params.timeout ?? BASH_DEFAULT_TIMEOUT_MS;
132
132
  if (!Number.isFinite(timeout) || timeout <= 0 || timeout > BASH_MAX_TIMEOUT_MS) {
133
133
  throw new Error(`timeout must be between 1 and ${BASH_MAX_TIMEOUT_MS} milliseconds`);
@@ -147,6 +147,7 @@ export function registerShellTools(bus: ToolBus, pi: ExtensionAPI, runtime: Bwra
147
147
  description: params.description,
148
148
  signal,
149
149
  onUpdate,
150
+ unapprovable: callOptions?.unapprovable,
150
151
  });
151
152
  } catch (error) {
152
153
  if (!(error instanceof Error)) {
@@ -36,6 +36,8 @@ const FS_WRITE = "fs.write";
36
36
  export interface FsCallContext {
37
37
  ctx: ExtensionContext;
38
38
  signal?: AbortSignal;
39
+ /** 本次调用不可请求用户审批(脚本里弹不了框):区外写入直接失败。 */
40
+ unapprovable?: boolean;
39
41
  }
40
42
 
41
43
  /** 一次 fs 原语的结果:`value` 回给脚本,`reads` 是本次新增的已读记账(进 details.reads)。 */
@@ -114,6 +116,7 @@ export function createCodemodeFs(options: CodemodeFsOptions): CodemodeFs {
114
116
  },
115
117
  policy,
116
118
  signal: call.signal,
119
+ unapprovable: call.unapprovable,
117
120
  });
118
121
  await mkdir(dirname(absolutePath), { recursive: true });
119
122
  await writeFile(absolutePath, content, { encoding: "utf8", signal: call.signal });
@@ -351,7 +351,7 @@ export const PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson
351
351
  stringify({
352
352
  name: "Error",
353
353
  message:
354
- "The script is waiting on a promise that can never settle: no tool call is pending, and timers do not exist here.",
354
+ "The script is waiting on a promise that can never settle: no tool call and no sleep is pending.",
355
355
  }),
356
356
  );
357
357
  return true;
@@ -84,7 +84,7 @@ export interface WorkerBootstrap {
84
84
  export type CodemodeOutputItem =
85
85
  { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
86
86
 
87
- export type ScriptErrorKind = "script" | "aborted" | "sandbox";
87
+ export type ScriptErrorKind = "script" | "aborted" | "timeout" | "sandbox";
88
88
 
89
89
  export interface ScriptError {
90
90
  kind: ScriptErrorKind;
@@ -52,8 +52,18 @@ export interface SandboxRunOptions {
52
52
  toolCallId: string;
53
53
  /** 调用方中止本次执行;脚本没有自己的超时,死循环只能靠它结束。 */
54
54
  signal?: AbortSignal;
55
- /** 每次嵌套调用:由调用方执行工具并把结果回给脚本。 */
56
- onCall(request: { id: number; name: string; args: unknown }): Promise<SandboxCallOutcome>;
55
+ /**
56
+ * 本次执行的墙钟预算(毫秒),含等待嵌套调用的时间;缺省不设上限。
57
+ * 到点后执行以 `kind: "timeout"` 结束,正在跑的嵌套调用收到中止信号。
58
+ */
59
+ timeoutMs?: number;
60
+ /** 每次嵌套调用:由调用方执行工具并把结果回给脚本。`signal` 含调用方中止与本次预算。 */
61
+ onCall(request: {
62
+ id: number;
63
+ name: string;
64
+ args: unknown;
65
+ signal: AbortSignal;
66
+ }): Promise<SandboxCallOutcome>;
57
67
  /** 脚本流式产生的输出项,用于 toolcall 进度。 */
58
68
  onOutput?(items: readonly CodemodeOutputItem[]): void;
59
69
  /** 嵌套调用的开始与结束,用于 toolcall 进度;事件里带截至此刻的调用记录快照。 */
@@ -108,6 +118,15 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
108
118
  const started = new Map<number, { name: string; at: number }>();
109
119
  let callSequence = 0;
110
120
  let settled = false;
121
+ let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
122
+
123
+ // 超时与调用方中止合成一条信号,交给嵌套调用:预算到点时正在跑的工具也要停,
124
+ // 否则 terminate 只杀 worker,工具会在主线程继续跑完并留下副作用
125
+ const aborter = new AbortController();
126
+ const signal =
127
+ options.signal === undefined
128
+ ? aborter.signal
129
+ : AbortSignal.any([options.signal, aborter.signal]);
111
130
 
112
131
  // 进度帧发出去的是拷贝:记录随后会被改成终态,已经发出去的帧不能被它改掉
113
132
  const snapshotCalls = (): ScriptCall[] => calls.map((call) => ({ ...call }));
@@ -121,6 +140,7 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
121
140
  return;
122
141
  }
123
142
  settled = true;
143
+ clearTimeout(timeoutTimer);
124
144
  void worker.terminate();
125
145
  resolve({ ...body, output, calls });
126
146
  };
@@ -143,6 +163,15 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
143
163
  );
144
164
  }
145
165
 
166
+ if (options.timeoutMs !== undefined) {
167
+ const timeoutMs = options.timeoutMs;
168
+ timeoutTimer = setTimeout(() => {
169
+ // 先中止正在跑的嵌套调用,再定结果:terminate 不会自动停主线程里的工具
170
+ aborter.abort();
171
+ fail(`Execution timed out after ${timeoutMs} ms`, "timeout");
172
+ }, timeoutMs);
173
+ }
174
+
146
175
  const handleCall = async (id: number, name: string, args: unknown): Promise<void> => {
147
176
  callSequence += 1;
148
177
  const record: ScriptCall = {
@@ -157,7 +186,7 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
157
186
  options.onCallProgress?.({ phase: "start", name, args, calls: snapshotCalls() });
158
187
  let outcome: SandboxCallOutcome;
159
188
  try {
160
- outcome = await options.onCall({ id, name, args });
189
+ outcome = await options.onCall({ id, name, args, signal });
161
190
  } catch (error) {
162
191
  outcome = { ok: false, error: error instanceof Error ? error.message : String(error) };
163
192
  }
@@ -1,12 +1,15 @@
1
1
  /**
2
- * codemode 脚本源码的解析:首行可选的 `// @options: {"max_output_tokens":…}`。
2
+ * codemode 脚本源码的解析:首行可选的 `// @options: {"max_output_tokens":…, "timeout_ms":…}`。
3
3
  *
4
4
  * 选项行与脚本首行共用一行,解析后原样保留为空行,脚本里报错的行号因此与用户写的
5
5
  * 一致。解析失败(空输入、JSON 非法、未知字段、只有选项行没有代码)直接报错。
6
6
  */
7
7
 
8
8
  const OPTIONS_PREFIX = "// @options:";
9
- const SUPPORTED_FIELDS = ["max_output_tokens"] as const;
9
+ const SUPPORTED_FIELDS = ["max_output_tokens", "timeout_ms"] as const;
10
+
11
+ /** `timeout_ms` 的上限:`setTimeout` 能接受的最大延时(毫秒)。 */
12
+ const MAX_TIMEOUT_MS = 2_147_483_647;
10
13
 
11
14
  export const DEFAULT_OUTPUT_TOKENS = 10_000;
12
15
 
@@ -19,6 +22,8 @@ export class CodemodeSourceError extends Error {
19
22
 
20
23
  export interface CodemodeSourceOptions {
21
24
  maxOutputTokens: number;
25
+ /** 本次执行的墙钟预算(毫秒)。不声明时脚本没有时间上限。 */
26
+ timeoutMs: number;
22
27
  }
23
28
 
24
29
  export interface ParsedCodemodeSource {
@@ -45,6 +50,20 @@ function parseOptions(json: string): Partial<CodemodeSourceOptions> {
45
50
  `${OPTIONS_PREFIX} does not support ${JSON.stringify(key)}; supported fields are ${SUPPORTED_FIELDS.map((field) => `\`${field}\``).join(" and ")}`,
46
51
  );
47
52
  }
53
+ if (key === "timeout_ms") {
54
+ if (
55
+ typeof value !== "number" ||
56
+ !Number.isSafeInteger(value) ||
57
+ value <= 0 ||
58
+ value > MAX_TIMEOUT_MS
59
+ ) {
60
+ throw new CodemodeSourceError(
61
+ `${OPTIONS_PREFIX} \`timeout_ms\` must be a positive integer up to ${MAX_TIMEOUT_MS}`,
62
+ );
63
+ }
64
+ options.timeoutMs = value;
65
+ continue;
66
+ }
48
67
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
49
68
  throw new CodemodeSourceError(`${OPTIONS_PREFIX} ${key} must be a positive number`);
50
69
  }
@@ -115,6 +115,10 @@ function buildDescription(tools: readonly CallableTool[]): string {
115
115
  " return: urls, ids and metadata you will not use cost context and buy nothing.",
116
116
  "- `await sleep(seconds)` waits that many seconds (fractions allowed). It is the only way to",
117
117
  ' wait: the sandbox has no timers, and it beats `call("Bash", { command: "sleep 1" })`.',
118
+ "- Nested calls cannot ask for user approval: a call that would need it (writing outside the",
119
+ " workspace, unsandboxed Bash) fails. Make those calls directly instead of from a script.",
120
+ '- `// @options: {"timeout_ms": N}` bounds the whole run in milliseconds; without it the script',
121
+ " has no time limit beyond the user aborting it.",
118
122
  "- `store.set(key, value)`, `store.get(key)` and `store.list()` are a small key/value store that",
119
123
  " persists across codemode calls in this session; `store.set(key, undefined)` removes a key.",
120
124
  "- The sandbox has no file system, network, timers or Node APIs: calling tools is its only",
@@ -294,9 +298,12 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
294
298
  parameters: Type.Object({
295
299
  code: Type.String({
296
300
  description:
297
- 'Raw JavaScript source. Top-level await and return work. May start with a `// @options: {"max_output_tokens": 10000}` line.',
301
+ 'Raw JavaScript source. Top-level await and return work. May start with a `// @options: {"max_output_tokens": 10000, "timeout_ms": 600000}` line.',
298
302
  }),
299
303
  }),
304
+ // 采样约束与 pi 内置 codemode 同形:grammar 只能约束一个自由文本参数,所以
305
+ // options 只能待在脚本里(多参数会让这个工具在支持 grammar 的模型上退化成
306
+ // 单字段 custom tool,额外参数被静默忽略)
300
307
  constrainedSampling: {
301
308
  type: "grammar",
302
309
  variants: { openai_lark: CODEMODE_SOURCE_GRAMMAR },
@@ -306,10 +313,12 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
306
313
  async execute(toolCallId, params, signal, onUpdate, ctx) {
307
314
  let code: string;
308
315
  let maxOutputTokens = DEFAULT_OUTPUT_TOKENS;
316
+ let timeoutMs: number | undefined;
309
317
  try {
310
318
  const parsed = parseCodemodeSource(params.code);
311
319
  code = parsed.code;
312
320
  maxOutputTokens = parsed.options.maxOutputTokens ?? maxOutputTokens;
321
+ timeoutMs = parsed.options.timeoutMs;
313
322
  } catch (error) {
314
323
  const message = error instanceof Error ? error.message : String(error);
315
324
  return {
@@ -335,6 +344,7 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
335
344
  store: readStore(ctx),
336
345
  toolCallId,
337
346
  signal,
347
+ timeoutMs,
338
348
  onOutput: (items) => {
339
349
  const text = textOf(items);
340
350
  if (text === "") {
@@ -364,10 +374,15 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
364
374
  },
365
375
  });
366
376
  },
367
- onCall: async ({ name, args }) => {
377
+ onCall: async ({ name, args, signal: callSignal }) => {
368
378
  if (fs.handles(name)) {
369
379
  try {
370
- const result = await fs.execute(name, args, { ctx, signal });
380
+ // 脚本里弹不了审批:需要授权的操作(工作区外写入)直接失败
381
+ const result = await fs.execute(name, args, {
382
+ ctx,
383
+ signal: callSignal,
384
+ unapprovable: true,
385
+ });
371
386
  if (result.reads !== undefined) {
372
387
  Object.assign(recordedReads, result.reads);
373
388
  }
@@ -382,7 +397,7 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
382
397
  if (!callable.has(name)) {
383
398
  return { ok: false, error: `Tool "${name}" is not available in codemode.` };
384
399
  }
385
- return bus.executeCodemodeTool(name, args, { ctx, signal });
400
+ return bus.executeCodemodeTool(name, args, { ctx, signal: callSignal });
386
401
  },
387
402
  });
388
403
 
@@ -344,7 +344,7 @@ var PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson) {
344
344
  stringify({
345
345
  name: "Error",
346
346
  message:
347
- "The script is waiting on a promise that can never settle: no tool call is pending, and timers do not exist here.",
347
+ "The script is waiting on a promise that can never settle: no tool call and no sleep is pending.",
348
348
  }),
349
349
  );
350
350
  return true;
@@ -55,6 +55,7 @@ export interface StructuredToolDefinition<
55
55
  signal: AbortSignal | undefined,
56
56
  onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
57
57
  ctx: ExtensionContext,
58
+ callOptions?: ToolCallOptions,
58
59
  ): Promise<AgentToolResult<TDetails> & { structuredResult: StructuredResult<Static<TOutput>> }>;
59
60
  }
60
61
 
@@ -111,16 +112,37 @@ export interface CodemodeToolDefinition<
111
112
  ): Promise<StructuredResult<Static<TOutput>>>;
112
113
  }
113
114
 
115
+ /**
116
+ * 工具 `execute` 的额外调用选项:只有本仓库自己的调用路径会传(目前是 codemode 的嵌套
117
+ * 调用),pi 直接调用工具时它永远是 `undefined`。因此在 pi 的 `ToolDefinition` 之外,
118
+ * 总线单独把 `execute` 的签名放宽一个可选参数。
119
+ */
120
+ export interface ToolCallOptions {
121
+ /** 本次调用不可请求用户审批:需要授权的操作直接失败,不弹审批框。 */
122
+ unapprovable?: boolean;
123
+ }
124
+
114
125
  /**
115
126
  * 注册后的定义:`codemodeOnly` 标出未交给 pi 的脚本工具。`structuredSchema` 供 codemode
116
127
  * 渲染脚本侧返回类型;`scripted` 区分「`execute` 直接返回结构化结果」的脚本工具与经配置
117
128
  * 转成 codemode-only 的普通工具(后者仍返回 `AgentToolResult`,结构化结果在
118
129
  * `structuredResult` 里)。
119
130
  */
120
- export type RegisteredToolDefinition = ToolDefinition<TSchema, unknown, unknown> & {
131
+ export type RegisteredToolDefinition = Omit<
132
+ ToolDefinition<TSchema, unknown, unknown>,
133
+ "execute"
134
+ > & {
121
135
  structuredSchema?: TSchema;
122
136
  codemodeOnly?: boolean;
123
137
  scripted?: true;
138
+ execute(
139
+ toolCallId: string,
140
+ params: unknown,
141
+ signal: AbortSignal | undefined,
142
+ onUpdate: AgentToolUpdateCallback<unknown> | undefined,
143
+ ctx: ExtensionContext,
144
+ callOptions?: ToolCallOptions,
145
+ ): Promise<AgentToolResult<unknown>>;
124
146
  };
125
147
 
126
148
  export interface ToolBus {
@@ -416,6 +438,8 @@ export function createToolBus(pi: ExtensionAPI, options: ToolBusOptions = {}): T
416
438
  options.signal,
417
439
  options.onUpdate,
418
440
  options.ctx,
441
+ // codemode 的嵌套调用弹不了审批框:需要授权的操作直接失败
442
+ { unapprovable: true },
419
443
  );
420
444
  if (definition.scripted === true) {
421
445
  return checkScriptedResult(name, definition.structuredSchema, result);
@@ -139,6 +139,8 @@ export interface WriteGuardOptions {
139
139
  policy: RequestPolicy;
140
140
  /** 工具调用的中止信号:透传给审批对话框,工具被取消时对话框一起关掉。 */
141
141
  signal?: AbortSignal;
142
+ /** 本次调用不可请求用户审批(codemode 的嵌套调用):区外写入直接失败,不弹框。 */
143
+ unapprovable?: boolean;
142
144
  }
143
145
 
144
146
  /**
@@ -164,6 +166,13 @@ export async function guardWriteAccess(
164
166
  throw new Error(`user deny ${opts.toolName}: blocked`);
165
167
  }
166
168
 
169
+ // codemode 内不弹审批:那次嵌套调用只经 codemode 暴露,弹不了框,因此直接失败并提示直调
170
+ if (opts.unapprovable === true) {
171
+ throw new Error(
172
+ `${opts.toolName} needs user approval to write outside the workspace, which codemode cannot ask for. Call the tool directly instead of from a script.`,
173
+ );
174
+ }
175
+
167
176
  if (process.platform === "win32") {
168
177
  // Windows 上退化为「只能写工作区」:无沙箱兜底,工作区外写入一律拒绝,
169
178
  // 不提供审批路径。
@@ -95,7 +95,7 @@ export function registerBashTool(bus: ToolBus, pi: ExtensionAPI, runtime: BwrapR
95
95
  },
96
96
  { additionalProperties: false },
97
97
  ),
98
- async execute(id, params, signal, onUpdate, ctx) {
98
+ async execute(id, params, signal, onUpdate, ctx, callOptions) {
99
99
  const timeout = params.timeout ?? BASH_DEFAULT_TIMEOUT_MS;
100
100
  if (!Number.isFinite(timeout) || timeout <= 0 || timeout > BASH_MAX_TIMEOUT_MS) {
101
101
  throw new Error(`timeout must be between 1 and ${BASH_MAX_TIMEOUT_MS} milliseconds`);
@@ -115,6 +115,7 @@ export function registerBashTool(bus: ToolBus, pi: ExtensionAPI, runtime: BwrapR
115
115
  description: params.description,
116
116
  signal,
117
117
  onUpdate,
118
+ unapprovable: callOptions?.unapprovable,
118
119
  });
119
120
  } catch (error) {
120
121
  if (!(error instanceof Error)) {