@trim21/personal-pi-extensions 0.0.429 → 0.0.430

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.0.429",
3
+ "version": "0.0.430",
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/bwrap/core.ts CHANGED
@@ -370,7 +370,7 @@ function killChild(child: ChildProcess): void {
370
370
  }
371
371
  }
372
372
 
373
- /** 为 net-allowlist 模式创建常驻网络栈;非该模式返回 undefined。 */
373
+ /** 为 net-allowlist 模式创建网络栈;非该模式返回 undefined。每次命令现建现停。 */
374
374
  export async function createNetworkStack(
375
375
  resolved: ResolvedBwrap,
376
376
  ): Promise<NetworkStack | undefined> {
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import { generateSingboxConfig } from "./singbox-config.js";
8
8
 
9
9
  export interface NetworkStackOptions {
10
- /** 允许直连的域名 / IP:port 列表(session 启动时固定,reload 才会重新加载)。 */
10
+ /** 允许直连的域名 / IP:port 列表(每次命令从配置重新读取)。 */
11
11
  readonly allowlist: readonly string[];
12
12
  /** 真实 DNS 服务器列表,按顺序 fallback。 */
13
13
  readonly dnsServers: readonly string[];
@@ -132,7 +132,7 @@ const stackFinalizer = new FinalizationRegistry<NetworkStackState>((state) => {
132
132
  });
133
133
 
134
134
  /**
135
- * 启动常驻的 per-session 网络栈:holder 进程持有 netns(内部跑 sing-box 的
135
+ * 启动网络栈:holder 进程持有 netns(内部跑 sing-box 的
136
136
  * TUN + fakeip + deny-by-default),slirp4netns 提供 egress。返回前
137
137
  * netns/sing-box/slirp4netns 均已就绪,命令通过 nsenter 进入该 netns 执行。
138
138
  */
@@ -38,7 +38,6 @@ import {
38
38
  resolveHeadlessBwrap,
39
39
  } from "./core.js";
40
40
  import { dcgSuggestion } from "./dcg-scan.js";
41
- import { type NetworkStack } from "./network-stack.js";
42
41
 
43
42
  export type EscalationDecision = { kind: "dialog" } | { kind: "deny"; reason: string };
44
43
 
@@ -197,6 +196,11 @@ class BashOutput {
197
196
  });
198
197
  }
199
198
 
199
+ // eslint-disable-next-line unicorn/no-nonstandard-builtin-properties -- ES2024 标准(Node 24 原生支持)
200
+ async [Symbol.asyncDispose](): Promise<void> {
201
+ await this.close();
202
+ }
203
+
200
204
  /** 尾部文本(截断结果的候选,未截断时即完整输出)。 */
201
205
  tailText(): string {
202
206
  return Buffer.concat(this.tail, this.tailBytes).toString("utf8");
@@ -242,7 +246,6 @@ export class BwrapRuntime {
242
246
  private resolved: ResolvedBwrap | undefined;
243
247
  private sandboxDisabled = false;
244
248
  private bwrapUnavailable = false;
245
- private networkStack: NetworkStack | undefined;
246
249
 
247
250
  setup(pi: ExtensionAPI): void {
248
251
  pi.registerFlag("no-bwrap", {
@@ -315,8 +318,6 @@ export class BwrapRuntime {
315
318
  setMode(cwd: string, mode: BwrapMode): ResolvedBwrap {
316
319
  this.resolved = resolveBwrap({ ...loadBwrapConfig(cwd), mode });
317
320
  this.sandboxDisabled = false;
318
- void this.networkStack?.stop();
319
- this.networkStack = undefined;
320
321
  return this.resolved;
321
322
  }
322
323
 
@@ -324,8 +325,6 @@ export class BwrapRuntime {
324
325
  this.resolved = undefined;
325
326
  this.sandboxDisabled = false;
326
327
  this.bwrapUnavailable = false;
327
- void this.networkStack?.stop();
328
- this.networkStack = undefined;
329
328
  }
330
329
 
331
330
  async execute(request: BwrapExecutionRequest): Promise<BwrapExecutionResult> {
@@ -361,11 +360,23 @@ export class BwrapRuntime {
361
360
  await this.approveFullAccess(request.ctx, request.command, request.description, execCwd);
362
361
  }
363
362
  }
364
- const operations =
365
- isWindows || needsApproval || !runtime.bwrapEnabled
366
- ? createLocalBashOperations()
367
- : createBwrapBashOperations(runtime, workspace, await this.getNetworkStack(runtime));
368
- const output = new BashOutput(request.ctx.sessionManager.getSessionId());
363
+ const local = isWindows || needsApproval || !runtime.bwrapEnabled;
364
+ // 每次命令现建网络栈(启动约 140ms),作用域结束自动停栈;allowlist 变化即时生效
365
+ const networkStack = local ? undefined : await createNetworkStack(runtime);
366
+ await using _stack = {
367
+ // eslint-disable-next-line unicorn/no-nonstandard-builtin-properties -- ES2024 标准(Node 24 原生支持)
368
+ async [Symbol.asyncDispose]() {
369
+ try {
370
+ await networkStack?.stop();
371
+ } catch {
372
+ // best-effort:停栈失败(进程已退出/目录删除失败)不掩盖命令结果
373
+ }
374
+ },
375
+ };
376
+ const operations = local
377
+ ? createLocalBashOperations()
378
+ : createBwrapBashOperations(runtime, workspace, networkStack);
379
+ await using output = new BashOutput(request.ctx.sessionManager.getSessionId());
369
380
  const { onUpdate } = request;
370
381
 
371
382
  // 流式进度:节流推送尾部快照(对齐 pi 内置 bash 的实时输出体验)
@@ -442,7 +453,6 @@ export class BwrapRuntime {
442
453
  } finally {
443
454
  if (updateTimer) clearTimeout(updateTimer);
444
455
  if (onUpdate && dirty) emitUpdate();
445
- await output.close();
446
456
  }
447
457
  }
448
458
 
@@ -454,21 +464,6 @@ export class BwrapRuntime {
454
464
  return this.resolved;
455
465
  }
456
466
 
457
- /** net-allowlist 模式的常驻网络栈:惰性创建,session 期间复用(allowlist 固定)。 */
458
- private async getNetworkStack(runtime: ResolvedBwrap): Promise<NetworkStack | undefined> {
459
- if (runtime.mode !== "net-allowlist" || runtime.networkAllowlist.length === 0) {
460
- if (this.networkStack) {
461
- void this.networkStack.stop();
462
- this.networkStack = undefined;
463
- }
464
- return undefined;
465
- }
466
- if (!this.networkStack) {
467
- this.networkStack = await createNetworkStack(runtime);
468
- }
469
- return this.networkStack;
470
- }
471
-
472
467
  private async approveFullAccess(
473
468
  ctx: ExtensionContext,
474
469
  command: string,
@@ -744,8 +739,6 @@ export class BwrapRuntime {
744
739
  private reload(ctx: ExtensionCommandContext): void {
745
740
  this.resolved = undefined;
746
741
  this.bwrapUnavailable = false;
747
- void this.networkStack?.stop();
748
- this.networkStack = undefined;
749
742
  const runtime = this.resolve(ctx);
750
743
  ctx.ui.setStatus("bwrap", ctx.ui.theme.fg("accent", `bwrap: ${runtime.mode}`));
751
744
  ctx.ui.notify(`bwrap config reloaded (mode: ${runtime.mode})`, "info");