@trim21/personal-pi-extensions 0.0.399 → 0.0.400
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 +3 -1
- package/package.json +1 -1
- package/src/bwrap/core.ts +81 -8
package/README.md
CHANGED
|
@@ -52,7 +52,9 @@ bash 工具(opencode 风格 `bash`、Claude Code 风格 `Bash`)注册了 `da
|
|
|
52
52
|
|
|
53
53
|
### 保护目录
|
|
54
54
|
|
|
55
|
-
`.
|
|
55
|
+
`.pi`、`.agent` 即使在 `workspace-write` 模式下也始终只读;`.git` 同样只读:工作区根是 git 仓库时保护根 `.git`,根不是 git 仓库时才递归扫描嵌套仓库(monorepo 子仓库,跳过 `node_modules`、`.venv` 等包目录)。
|
|
56
|
+
|
|
57
|
+
可写与保护路径均使用 bwrap 的 `--*-bind-try` 变体:路径不存在时自动忽略该项,而不是让整条命令失败。
|
|
56
58
|
|
|
57
59
|
### 运行时命令
|
|
58
60
|
|
package/package.json
CHANGED
package/src/bwrap/core.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
-
import { closeSync, constants, existsSync, openSync, readFileSync } from "node:fs";
|
|
3
|
-
import { access as fsAccess } from "node:fs/promises";
|
|
2
|
+
import { closeSync, constants, type Dirent, existsSync, openSync, readFileSync } from "node:fs";
|
|
3
|
+
import { access as fsAccess, readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { delimiter, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ import { Value } from "typebox/value";
|
|
|
12
12
|
import { expandHome } from "../lib/path.js";
|
|
13
13
|
import { type ApprovalRule } from "./approval-rules.js";
|
|
14
14
|
|
|
15
|
-
const PROTECTED_DIRS = [".
|
|
15
|
+
const PROTECTED_DIRS = [".pi", ".agent"];
|
|
16
16
|
|
|
17
17
|
export const BWRAP_MODES = ["allow-all", "workspace-write", "allow-net", "readonly"] as const;
|
|
18
18
|
|
|
@@ -190,23 +190,96 @@ export function findBwrap(override?: string): string {
|
|
|
190
190
|
return findDefaultBwrap();
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
/** 扫描 .git 时跳过的目录:包/依赖/构建产物,嵌套 git 仓库几乎不会出现在这里。 */
|
|
194
|
+
const GIT_DIR_SCAN_SKIP = new Set([
|
|
195
|
+
"node_modules",
|
|
196
|
+
".venv",
|
|
197
|
+
"venv",
|
|
198
|
+
".venvs",
|
|
199
|
+
"dist",
|
|
200
|
+
"build",
|
|
201
|
+
"target",
|
|
202
|
+
"__pycache__",
|
|
203
|
+
".next",
|
|
204
|
+
".cache",
|
|
205
|
+
".turbo",
|
|
206
|
+
".pytest_cache",
|
|
207
|
+
".mypy_cache",
|
|
208
|
+
".hg",
|
|
209
|
+
".svn",
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
/** 最多收集的 .git 数量与扫描深度,防止异常大的工作区拖慢每条命令。 */
|
|
213
|
+
const MAX_GIT_DIRS = 64;
|
|
214
|
+
const MAX_GIT_SCAN_DEPTH = 24;
|
|
215
|
+
|
|
216
|
+
class ScanLimitError extends Error {}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 递归扫描工作区,收集所有 `.git` 目录的绝对路径(monorepo / 嵌套仓库)。
|
|
220
|
+
* 达到数量上限立即停止整棵遍历;跳过 symlink(防循环)与无法读取的目录。
|
|
221
|
+
*/
|
|
222
|
+
export async function findGitDirs(root: string): Promise<string[]> {
|
|
223
|
+
const found: string[] = [];
|
|
224
|
+
const walk = async (dir: string, depth: number): Promise<void> => {
|
|
225
|
+
if (depth > MAX_GIT_SCAN_DEPTH) return;
|
|
226
|
+
let entries: Dirent[];
|
|
227
|
+
try {
|
|
228
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
229
|
+
} catch {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
for (const entry of entries) {
|
|
233
|
+
if (found.length >= MAX_GIT_DIRS) throw new ScanLimitError();
|
|
234
|
+
if (entry.name === ".git" && entry.isDirectory()) {
|
|
235
|
+
found.push(join(dir, ".git"));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!entry.isDirectory() || GIT_DIR_SCAN_SKIP.has(entry.name)) continue;
|
|
239
|
+
await walk(join(dir, entry.name), depth + 1);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
try {
|
|
243
|
+
await walk(root, 0);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (!(error instanceof ScanLimitError)) throw error;
|
|
246
|
+
}
|
|
247
|
+
return found;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Promise<string[]> {
|
|
194
251
|
const args = ["--new-session", "--die-with-parent", "--unshare-user", "--unshare-pid"];
|
|
252
|
+
// --*-bind-try:配置的路径不存在时忽略该项而不是让整条命令失败
|
|
195
253
|
for (const path of resolved.writablePaths) {
|
|
196
254
|
const absolutePath = resolveBwrapPath(path, cwd);
|
|
197
|
-
args.push("--bind", absolutePath, absolutePath);
|
|
255
|
+
args.push("--bind-try", absolutePath, absolutePath);
|
|
198
256
|
}
|
|
199
257
|
for (const path of resolved.extraWritablePaths) {
|
|
200
258
|
const absolutePath = resolveBwrapPath(path, cwd);
|
|
201
|
-
args.push("--bind", absolutePath, absolutePath);
|
|
259
|
+
args.push("--bind-try", absolutePath, absolutePath);
|
|
202
260
|
}
|
|
203
261
|
for (const path of resolved.tmpfsPaths) {
|
|
204
262
|
args.push("--tmpfs", resolveBwrapPath(path, cwd));
|
|
205
263
|
}
|
|
206
264
|
if (!resolved.network) args.push("--unshare-net");
|
|
265
|
+
// --ro-bind-try:目录不存在(或已被删除)时自动忽略
|
|
207
266
|
for (const name of PROTECTED_DIRS) {
|
|
208
267
|
const absolutePath = join(cwd, name);
|
|
209
|
-
|
|
268
|
+
args.push("--ro-bind-try", absolutePath, absolutePath);
|
|
269
|
+
}
|
|
270
|
+
// 工作区下所有 .git 一律只读:可写 bind 之上的覆盖绑定,防止命令篡改仓库元数据。
|
|
271
|
+
// 根目录本身是 git 仓库时只保护根 .git(递归扫描有成本,绝大多数情况根即唯一仓库);
|
|
272
|
+
// 根不是 git 仓库时才递归扫描嵌套仓库(如 monorepo 子仓库)。
|
|
273
|
+
const rootGit = join(cwd, ".git");
|
|
274
|
+
let gitDirs: string[];
|
|
275
|
+
try {
|
|
276
|
+
await stat(rootGit);
|
|
277
|
+
gitDirs = [rootGit];
|
|
278
|
+
} catch {
|
|
279
|
+
gitDirs = await findGitDirs(cwd);
|
|
280
|
+
}
|
|
281
|
+
for (const gitDir of gitDirs) {
|
|
282
|
+
args.push("--ro-bind-try", gitDir, gitDir);
|
|
210
283
|
}
|
|
211
284
|
args.push(...resolved.extraArgs);
|
|
212
285
|
return args;
|
|
@@ -265,7 +338,7 @@ export function createBwrapBashOperations(
|
|
|
265
338
|
"--ro-bind",
|
|
266
339
|
"/",
|
|
267
340
|
"/",
|
|
268
|
-
...buildBwrapArgs(resolved, workspace),
|
|
341
|
+
...(await buildBwrapArgs(resolved, workspace)),
|
|
269
342
|
"--dev",
|
|
270
343
|
"/dev",
|
|
271
344
|
"--proc",
|