@trim21/personal-pi-extensions 0.1.522 → 0.1.524

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.522",
3
+ "version": "0.1.524",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
package/src/aft/bridge.ts CHANGED
@@ -59,7 +59,7 @@ export async function createAftState(
59
59
  }
60
60
 
61
61
  /** semantic_search 等待索引构建完成的上限(与 Rust 侧 AFT_WAIT_FOR_SEMANTIC_READY_MS 一致)。 */
62
- export const SEMANTIC_INDEX_WAIT_TIMEOUT_MS = 600_000;
62
+ export const SEMANTIC_INDEX_WAIT_TIMEOUT_MS = 3_600_000;
63
63
 
64
64
  /**
65
65
  * 调用图存储未就绪时查询侧的内联等待窗口。Rust 侧默认 0(纯异步:立即返回
package/src/bwrap/core.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type ChildProcess, spawn } from "node:child_process";
2
2
  import { constants, type Dirent, existsSync, readFileSync } from "node:fs";
3
- import { access as fsAccess, readdir, stat } from "node:fs/promises";
3
+ import { access as fsAccess, readdir, realpath, stat } from "node:fs/promises";
4
4
  import { delimiter, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
@@ -337,20 +337,34 @@ export async function findGitDirs(root: string): Promise<string[]> {
337
337
  return found;
338
338
  }
339
339
 
340
+ /**
341
+ * bwrap 创建挂载点时不跟随目标路径中的 symlink 组件(防 symlink 逃逸),
342
+ * 含绝对 symlink 的配置路径会以 "Can't mkdir parents ... No such file or directory" 失败。
343
+ * 已存在的路径先解析成真实路径(沙箱内 ro-bind 的 / 下同样可见,symlink 语义不变);
344
+ * 不存在的路径保持原样,交给 --*-try 的跳过语义处理。
345
+ */
346
+ async function realpathOrSelf(path: string): Promise<string> {
347
+ try {
348
+ return await realpath(path);
349
+ } catch {
350
+ return path;
351
+ }
352
+ }
353
+
340
354
  export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Promise<string[]> {
341
355
  const args = ["--new-session", "--die-with-parent", "--unshare-user", "--unshare-pid"];
342
356
  // --*-bind-try:配置的路径不存在时忽略该项而不是让整条命令失败
343
357
  for (const path of resolved.writablePaths) {
344
- const absolutePath = resolveBwrapPath(path, cwd);
358
+ const absolutePath = await realpathOrSelf(resolveBwrapPath(path, cwd));
345
359
  args.push("--bind-try", absolutePath, absolutePath);
346
360
  }
347
361
  for (const path of resolved.extraWritablePaths) {
348
- const absolutePath = resolveBwrapPath(path, cwd);
362
+ const absolutePath = await realpathOrSelf(resolveBwrapPath(path, cwd));
349
363
  args.push("--bind-try", absolutePath, absolutePath);
350
364
  }
351
365
  // denyPaths:以 / 结尾的条目视为目录(挂空 tmpfs),否则视为文件(--ro-bind-try /dev/null 覆盖)
352
366
  for (const path of resolved.denyPaths) {
353
- const target = resolveBwrapPath(path, cwd);
367
+ const target = await realpathOrSelf(resolveBwrapPath(path, cwd));
354
368
  if (path.endsWith("/")) {
355
369
  args.push("--tmpfs", target);
356
370
  } else {
@@ -360,13 +374,13 @@ export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Prom
360
374
  if (!resolved.network) args.push("--unshare-net");
361
375
  // --ro-bind-try:目录不存在(或已被删除)时自动忽略
362
376
  for (const name of PROTECTED_DIRS) {
363
- const absolutePath = join(cwd, name);
377
+ const absolutePath = await realpathOrSelf(join(cwd, name));
364
378
  args.push("--ro-bind-try", absolutePath, absolutePath);
365
379
  }
366
380
  // 工作区下所有 .git 一律只读:可写 bind 之上的覆盖绑定,防止命令篡改仓库元数据。
367
381
  // 根目录本身是 git 仓库时只保护根 .git(递归扫描有成本,绝大多数情况根即唯一仓库);
368
382
  // 根不是 git 仓库时才递归扫描嵌套仓库(如 monorepo 子仓库)。
369
- const rootGit = join(cwd, ".git");
383
+ const rootGit = await realpathOrSelf(join(cwd, ".git"));
370
384
  let gitDirs: string[];
371
385
  try {
372
386
  await stat(rootGit);
@@ -375,7 +389,8 @@ export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Prom
375
389
  gitDirs = await findGitDirs(cwd);
376
390
  }
377
391
  for (const gitDir of gitDirs) {
378
- args.push("--ro-bind-try", gitDir, gitDir);
392
+ const realGitDir = await realpathOrSelf(gitDir);
393
+ args.push("--ro-bind-try", realGitDir, realGitDir);
379
394
  }
380
395
  args.push(...resolved.extraArgs);
381
396
  return args;