@trim21/personal-pi-extensions 0.1.553 → 0.1.554
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/package.json +1 -1
- package/src/bwrap/runtime.ts +14 -40
- package/src/claude-code/shell.ts +16 -10
- package/src/opencode/bash.ts +11 -14
package/package.json
CHANGED
package/src/bwrap/runtime.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { type TObject, Type } from "typebox";
|
|
|
17
17
|
|
|
18
18
|
import { type CommandSpec, parseCommand } from "../lib/cli.js";
|
|
19
19
|
import { fenceCodeBlock } from "../lib/markdown.js";
|
|
20
|
-
import { formatDisplayPath
|
|
20
|
+
import { formatDisplayPath } from "../lib/path.js";
|
|
21
21
|
import { type SelectAction, selectMultiple, selectWithOptionalInput } from "../lib/ui.js";
|
|
22
22
|
import { type ApprovalRule, evaluateBashApproval, matchRule } from "./approval-rules.js";
|
|
23
23
|
import { commandPatternsFor } from "./approval-suggest.js";
|
|
@@ -91,7 +91,7 @@ export interface BwrapExecutionRequest {
|
|
|
91
91
|
*/
|
|
92
92
|
export interface BwrapExecutionResult {
|
|
93
93
|
exitCode: number | null;
|
|
94
|
-
/**
|
|
94
|
+
/** 沙箱状态说明(写边界 + 网络层级),失败时由上层工具作为独立信息块附上;未沙箱执行时为 undefined。 */
|
|
95
95
|
sandboxHint: string | undefined;
|
|
96
96
|
/** 截断后的输出(尾部),未截断时为完整输出;空输出为空字符串。 */
|
|
97
97
|
output: string;
|
|
@@ -260,13 +260,8 @@ function notifyMode(
|
|
|
260
260
|
ctx.ui.notify(labels[mode], "info");
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
/**
|
|
264
|
-
|
|
265
|
-
const writable = [...resolved.writablePaths, ...resolved.extraWritablePaths].map((path) =>
|
|
266
|
-
resolveHomePath(path, workspace),
|
|
267
|
-
);
|
|
268
|
-
return [...new Set(writable)];
|
|
269
|
-
}
|
|
263
|
+
/** 沙箱默认写边界:根只读、工作区可写、.git 只读。不展开用户配置的额外可写路径。 */
|
|
264
|
+
const SANDBOX_WRITE_RULES = "/ is read-only, ./ is writable, ./.git/ is read-only";
|
|
270
265
|
|
|
271
266
|
/** 网络层级:关 / 只放行白名单 / 完全放开(白名单域名本身不列出,对判断失败无用)。 */
|
|
272
267
|
function describeNetwork(resolved: ResolvedBwrap): string {
|
|
@@ -276,49 +271,28 @@ function describeNetwork(resolved: ResolvedBwrap): string {
|
|
|
276
271
|
return "network access is unrestricted";
|
|
277
272
|
}
|
|
278
273
|
|
|
279
|
-
export interface SandboxHintInput {
|
|
280
|
-
/** writablePaths 中 "." 的解析基准,也是可写根目录的显示基准。 */
|
|
281
|
-
workspace: string;
|
|
282
|
-
/** 本次命令是否绕过沙箱(Windows、审批通过的全权限、allow-all)。 */
|
|
283
|
-
unsandboxed: boolean;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
274
|
/** 命令没经沙箱时沿用 prompt 里的说法,给出在沙盒外重跑的手段。 */
|
|
287
275
|
const SANDBOX_ESCAPE_HATCH =
|
|
288
276
|
"[Sandbox] If the command needs more than that, use the `dangerouslyDisableSandbox` parameter to request unsandboxed execution; the user must approve this request.";
|
|
289
277
|
|
|
290
278
|
/**
|
|
291
|
-
*
|
|
292
|
-
*
|
|
279
|
+
* 命令失败时作为独立信息块附上的沙箱状态:默认写边界 + 网络层级,以及在沙盒外重跑的手段。
|
|
280
|
+
* 写边界只说沙箱布局(不展开用户配置的可写路径),网络只说层级(不列白名单域名)。
|
|
293
281
|
* 命令没经沙箱(allow-all、审批通过的全权限、Windows)时返回 undefined:
|
|
294
282
|
* 没有沙箱就没什么可提示的。
|
|
295
283
|
*/
|
|
296
|
-
export function describeSandbox(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
): string | undefined {
|
|
300
|
-
if (input.unsandboxed) return undefined;
|
|
301
|
-
const roots = writableRoots(resolved, input.workspace);
|
|
302
|
-
const clauses = [
|
|
303
|
-
roots.length === 0
|
|
304
|
-
? "the filesystem is read-only"
|
|
305
|
-
: `writes are limited to ${roots.map((path) => formatDisplayPath(input.workspace, path)).join(", ")}`,
|
|
306
|
-
// .git 只读保护只在工作区本身可写时才有区分度(readonly 下整个文件系统都不可写)
|
|
307
|
-
...(roots.includes(input.workspace) ? [".git is read-only"] : []),
|
|
308
|
-
describeNetwork(resolved),
|
|
309
|
-
];
|
|
284
|
+
export function describeSandbox(resolved: ResolvedBwrap, unsandboxed: boolean): string | undefined {
|
|
285
|
+
if (unsandboxed) return undefined;
|
|
286
|
+
const writes = resolved.mode === "readonly" ? "the filesystem is read-only" : SANDBOX_WRITE_RULES;
|
|
310
287
|
return [
|
|
311
|
-
`[Sandbox] This command ran in a sandbox: ${
|
|
288
|
+
`[Sandbox] This command ran in a sandbox: ${writes}; ${describeNetwork(resolved)}.`,
|
|
312
289
|
SANDBOX_ESCAPE_HATCH,
|
|
313
290
|
].join("\n");
|
|
314
291
|
}
|
|
315
292
|
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
*/
|
|
320
|
-
export function appendSandboxHint(text: string, hint: string | undefined): string {
|
|
321
|
-
return hint === undefined ? text : `${text.trimEnd()}\n\n${hint}`;
|
|
293
|
+
/** 失败结果里附加的沙箱状态块;未沙箱执行(hint 为 undefined)时没有这一块。 */
|
|
294
|
+
export function sandboxHintBlock(hint: string | undefined): { type: "text"; text: string }[] {
|
|
295
|
+
return hint === undefined ? [] : [{ type: "text", text: hint }];
|
|
322
296
|
}
|
|
323
297
|
|
|
324
298
|
export class BwrapRuntime {
|
|
@@ -450,7 +424,7 @@ export class BwrapRuntime {
|
|
|
450
424
|
this.mihomoPath = runtime.mihomoPath;
|
|
451
425
|
}
|
|
452
426
|
// 命令失败时附带的沙箱状态(写边界 + 网络层级),由上层拼进错误文本
|
|
453
|
-
const sandboxHint = describeSandbox(runtime,
|
|
427
|
+
const sandboxHint = describeSandbox(runtime, local);
|
|
454
428
|
await using output = new BashOutput(request.ctx.sessionManager.getSessionId());
|
|
455
429
|
const { onUpdate } = request;
|
|
456
430
|
|
package/src/claude-code/shell.ts
CHANGED
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
appendSandboxHint,
|
|
15
14
|
BashInterruptedError,
|
|
16
15
|
type BwrapRuntime,
|
|
17
16
|
createBwrapRuntime,
|
|
17
|
+
sandboxHintBlock,
|
|
18
18
|
} from "../bwrap/runtime.js";
|
|
19
19
|
import { resolveWorkdir } from "../lib/path.js";
|
|
20
20
|
|
|
@@ -156,25 +156,31 @@ export function registerShellTools(
|
|
|
156
156
|
const full = text ? `${text}\n\nCommand aborted by user` : "Command aborted by user";
|
|
157
157
|
return { content: [{ type: "text", text: full }], details: undefined };
|
|
158
158
|
}
|
|
159
|
-
const
|
|
159
|
+
const full = text
|
|
160
160
|
? `${text}\n\nCommand timed out after ${timeout} milliseconds`
|
|
161
161
|
: `Command timed out after ${timeout} milliseconds`;
|
|
162
|
-
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: "text", text: full }, ...sandboxHintBlock(error.sandboxHint)],
|
|
164
|
+
details: undefined,
|
|
165
|
+
};
|
|
163
166
|
}
|
|
164
167
|
throw error;
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
// 对齐 Claude Code:非 0
|
|
168
|
-
//
|
|
170
|
+
// 对齐 Claude Code:非 0 退出码都算失败(不做 grep/find 等命令语义化特判)。
|
|
171
|
+
// 失败是命令的正常结果而不是异常:与成功一样 return,文本用完整输出
|
|
172
|
+
// (从落盘文件读取,必要时头尾截断),沙箱状态另行附一块
|
|
169
173
|
if (result.exitCode !== 0 && result.exitCode !== null) {
|
|
170
174
|
const full = result.fullOutputPath
|
|
171
175
|
? await readFile(result.fullOutputPath, "utf8")
|
|
172
176
|
: result.output;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
return {
|
|
178
|
+
content: [
|
|
179
|
+
{ type: "text", text: formatBashError(result.exitCode, full) },
|
|
180
|
+
...sandboxHintBlock(result.sandboxHint),
|
|
181
|
+
],
|
|
182
|
+
details: undefined,
|
|
183
|
+
};
|
|
178
184
|
}
|
|
179
185
|
return formatBashSuccess(result);
|
|
180
186
|
},
|
package/src/opencode/bash.ts
CHANGED
|
@@ -5,10 +5,10 @@ import type { ExtensionAPI, TruncationResult } from "@earendil-works/pi-coding-a
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
appendSandboxHint,
|
|
9
8
|
BashInterruptedError,
|
|
10
9
|
type BwrapRuntime,
|
|
11
10
|
createBwrapRuntime,
|
|
11
|
+
sandboxHintBlock,
|
|
12
12
|
} from "../bwrap/runtime.js";
|
|
13
13
|
import { resolveWorkdir } from "../lib/path.js";
|
|
14
14
|
|
|
@@ -111,18 +111,18 @@ export default function opencodeBash(
|
|
|
111
111
|
error.partial.truncation,
|
|
112
112
|
error.partial.fullOutputPath,
|
|
113
113
|
);
|
|
114
|
-
// 超时附带沙箱状态(可能是网络限制让命令挂住);中断是用户主动取消,与沙箱无关
|
|
115
114
|
const status =
|
|
116
115
|
error.kind === "timeout"
|
|
117
|
-
?
|
|
118
|
-
`Command exceeded timeout of ${timeout} ms. Retry with a larger timeout if the command is expected to take longer.`,
|
|
119
|
-
error.sandboxHint,
|
|
120
|
-
)
|
|
116
|
+
? `Command exceeded timeout of ${timeout} ms. Retry with a larger timeout if the command is expected to take longer.`
|
|
121
117
|
: "Command aborted by user";
|
|
122
118
|
const full = text ? `${text}\n\n${status}` : status;
|
|
123
|
-
// 对齐上游 opencode
|
|
119
|
+
// 对齐上游 opencode:超时与中断都不抛错,输出与状态文本一起返回;
|
|
120
|
+
// 超时可能是沙箱的网络限制导致的,附加沙箱状态(用户中断与沙箱无关)
|
|
124
121
|
return {
|
|
125
|
-
content: [
|
|
122
|
+
content: [
|
|
123
|
+
{ type: "text" as const, text: full },
|
|
124
|
+
...sandboxHintBlock(error.kind === "timeout" ? error.sandboxHint : undefined),
|
|
125
|
+
],
|
|
126
126
|
details: error.kind === "timeout" ? { timeout: true } : {},
|
|
127
127
|
};
|
|
128
128
|
}
|
|
@@ -133,15 +133,12 @@ export default function opencodeBash(
|
|
|
133
133
|
let text = result.output || "(no output)";
|
|
134
134
|
text = appendTruncationNotice(text, result.truncation, result.fullOutputPath);
|
|
135
135
|
const failed = result.exitCode !== 0 && result.exitCode !== null;
|
|
136
|
-
// 失败时附带沙箱状态:命令可能是被沙箱的写边界或网络限制挡住的
|
|
137
|
-
const status = appendSandboxHint(
|
|
138
|
-
`Command exited with code ${result.exitCode}.`,
|
|
139
|
-
failed ? result.sandboxHint : undefined,
|
|
140
|
-
);
|
|
141
136
|
return {
|
|
142
137
|
content: [
|
|
143
138
|
{ type: "text" as const, text },
|
|
144
|
-
{ type: "text" as const, text:
|
|
139
|
+
{ type: "text" as const, text: `Command exited with code ${result.exitCode}.` },
|
|
140
|
+
// 失败可能是被沙箱的写边界或网络限制挡住的,附一块沙箱状态
|
|
141
|
+
...sandboxHintBlock(failed ? result.sandboxHint : undefined),
|
|
145
142
|
],
|
|
146
143
|
details: {
|
|
147
144
|
exitCode: result.exitCode,
|