@trim21/personal-pi-extensions 0.1.581 → 0.1.584
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/LICENSE +21 -0
- package/README.md +1 -1
- package/package.json +2 -1
- package/src/bwrap/core.ts +0 -11
- package/src/bwrap/runtime.ts +9 -36
- package/src/bwrap/sandbox.ts +1 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 trim21
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -251,7 +251,7 @@ opencode 风格的任务列表工具,参数与语义和 opencode 的 [`todowri
|
|
|
251
251
|
- `status`:`pending` | `in_progress` | `completed` | `cancelled`
|
|
252
252
|
- `priority`:`high` | `medium` | `low`
|
|
253
253
|
- **持久化**:列表存进工具结果 `details.todos`,跟随会话分支自动恢复
|
|
254
|
-
- **渲染**:每次调用用完整 markdown
|
|
254
|
+
- **渲染**:每次调用用完整 markdown 列表输出对应的任务
|
|
255
255
|
|
|
256
256
|
与 pi 内置 `todo` 工具(`create`/`update`/`list`/… 单条动作)不同,本工具没有单条增删改动作,模型必须每次都传完整列表。
|
|
257
257
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trim21/personal-pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.584",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package"
|
|
8
8
|
],
|
|
9
|
+
"license": "MIT",
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
11
12
|
"url": "https://github.com/trim21/pi-extensions"
|
package/src/bwrap/core.ts
CHANGED
|
@@ -134,17 +134,6 @@ export function resolveBwrap(config: BwrapConfig): ResolvedBwrap {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
export function resolveHeadlessBwrap(config: BwrapConfig): ResolvedBwrap {
|
|
138
|
-
return resolveBwrap({
|
|
139
|
-
...config,
|
|
140
|
-
mode: "readonly",
|
|
141
|
-
writablePaths: [],
|
|
142
|
-
extraWritablePaths: [],
|
|
143
|
-
denyPaths: [],
|
|
144
|
-
extraArgs: [],
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
137
|
function deepMerge(base: BwrapConfig, overrides: Partial<BwrapConfig>): BwrapConfig {
|
|
149
138
|
return {
|
|
150
139
|
mode: overrides.mode ?? base.mode,
|
package/src/bwrap/runtime.ts
CHANGED
|
@@ -31,24 +31,10 @@ import {
|
|
|
31
31
|
resolveBwrap,
|
|
32
32
|
resolveBwrapPath,
|
|
33
33
|
type ResolvedBwrap,
|
|
34
|
-
resolveHeadlessBwrap,
|
|
35
34
|
} from "./core.js";
|
|
36
35
|
import { dcgSuggestion } from "./dcg-scan.js";
|
|
37
36
|
import { loadSandboxConfig, runInSandbox } from "./sandbox.js";
|
|
38
37
|
|
|
39
|
-
export type EscalationDecision = { kind: "dialog" } | { kind: "deny"; reason: string };
|
|
40
|
-
|
|
41
|
-
export function resolveEscalation(opts: { hasUI: boolean }): EscalationDecision {
|
|
42
|
-
if (!opts.hasUI) {
|
|
43
|
-
return {
|
|
44
|
-
kind: "deny",
|
|
45
|
-
reason:
|
|
46
|
-
"request_full_access requires an interactive session with user approval; no UI is available in this session.",
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return { kind: "dialog" };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
38
|
/** 全权限审批对话框的选项 label(也作为 switch 匹配键与测试引用)。 */
|
|
53
39
|
export const ALLOW_ONCE = "Allow once";
|
|
54
40
|
export const DENY = "Deny";
|
|
@@ -285,8 +271,9 @@ function describeNetwork(resolved: ResolvedBwrap): string {
|
|
|
285
271
|
}
|
|
286
272
|
|
|
287
273
|
/**
|
|
288
|
-
* 用户无理由拒绝非沙盒请求的文案。`/bwrap-deny-request`
|
|
289
|
-
*
|
|
274
|
+
* 用户无理由拒绝非沙盒请求的文案。`/bwrap-deny-request` 生效时与无 UI 会话里
|
|
275
|
+
* 需要审批的请求,都必须与用户在审批框点 Deny 完全一致——模型看到的是一次
|
|
276
|
+
* 普通拒绝,而不是另一套错误语义。
|
|
290
277
|
*/
|
|
291
278
|
const UNSANDBOXED_DENIED = "User denied unsandboxed execution.";
|
|
292
279
|
|
|
@@ -319,7 +306,6 @@ export function sandboxHintBlock(hint: string | undefined): { type: "text"; text
|
|
|
319
306
|
|
|
320
307
|
export class BwrapRuntime {
|
|
321
308
|
private resolved: ResolvedBwrap | undefined;
|
|
322
|
-
private sandboxDisabled = false;
|
|
323
309
|
private bwrapUnavailable = false;
|
|
324
310
|
/** net-allowlist 首次执行时解析一次 mihomo 路径,之后随 runtime 复用,不逐命令扫描 PATH。 */
|
|
325
311
|
private mihomoPath: string | undefined;
|
|
@@ -334,14 +320,7 @@ export class BwrapRuntime {
|
|
|
334
320
|
}
|
|
335
321
|
|
|
336
322
|
setup(pi: ExtensionAPI): void {
|
|
337
|
-
pi.registerFlag("no-bwrap", {
|
|
338
|
-
description: "Disable bwrap sandboxing for bash commands",
|
|
339
|
-
type: "boolean",
|
|
340
|
-
default: false,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
323
|
pi.on("session_start", (_event, ctx) => {
|
|
344
|
-
this.sandboxDisabled = pi.getFlag("no-bwrap") === true && ctx.hasUI;
|
|
345
324
|
this.resolved = undefined;
|
|
346
325
|
this.bwrapUnavailable = false;
|
|
347
326
|
this.policy.setDenyRequests(false);
|
|
@@ -358,7 +337,7 @@ export class BwrapRuntime {
|
|
|
358
337
|
} catch (error) {
|
|
359
338
|
// Fail closed: a missing bwrap binary must not silently degrade to an
|
|
360
339
|
// unsandboxed allow-all session. Commands are refused until the user
|
|
361
|
-
// explicitly opts out via
|
|
340
|
+
// explicitly opts out via the bwrap-allow-all command.
|
|
362
341
|
this.bwrapUnavailable = true;
|
|
363
342
|
this.resolved = undefined;
|
|
364
343
|
ctx.ui.setStatus("bwrap", ctx.ui.theme.fg("error", "bwrap: unavailable"));
|
|
@@ -386,9 +365,7 @@ export class BwrapRuntime {
|
|
|
386
365
|
? isWindows
|
|
387
366
|
? "Every bash command requires user approval before it runs."
|
|
388
367
|
: `Current bwrap mode: **${runtime.mode}**. The bwrap runtime selects sandboxing and, when requested, user approval for unsandboxed execution.`
|
|
389
|
-
:
|
|
390
|
-
? "This headless session cannot approve commands: bash commands are refused."
|
|
391
|
-
: "This headless session is forced into bwrap readonly mode. Unsandboxed execution cannot be approved.";
|
|
368
|
+
: `Current bwrap mode: **${runtime.mode}**. This headless session cannot ask for approval: commands that require user approval are denied.`;
|
|
392
369
|
const unavailableText =
|
|
393
370
|
!isWindows && this.bwrapUnavailable
|
|
394
371
|
? " bwrap is unavailable (binary not found): bash commands are refused unless the user explicitly approves unsandboxed execution."
|
|
@@ -409,13 +386,11 @@ export class BwrapRuntime {
|
|
|
409
386
|
|
|
410
387
|
setMode(cwd: string, mode: BwrapMode): ResolvedBwrap {
|
|
411
388
|
this.resolved = loadSandboxConfig({ workspace: cwd, mode });
|
|
412
|
-
this.sandboxDisabled = false;
|
|
413
389
|
return this.resolved;
|
|
414
390
|
}
|
|
415
391
|
|
|
416
392
|
reset(): void {
|
|
417
393
|
this.resolved = undefined;
|
|
418
|
-
this.sandboxDisabled = false;
|
|
419
394
|
this.bwrapUnavailable = false;
|
|
420
395
|
}
|
|
421
396
|
|
|
@@ -432,7 +407,7 @@ export class BwrapRuntime {
|
|
|
432
407
|
) {
|
|
433
408
|
throw new Error(
|
|
434
409
|
"bwrap (bubblewrap) not found; refusing to execute commands without sandboxing. " +
|
|
435
|
-
"Install bubblewrap and restart the session, or
|
|
410
|
+
"Install bubblewrap and restart the session, or disable the sandbox explicitly with /bwrap-allow-all.",
|
|
436
411
|
);
|
|
437
412
|
}
|
|
438
413
|
// workspace 恒为 session 工作区;cwd 只是本次命令的进程执行目录,
|
|
@@ -568,10 +543,8 @@ export class BwrapRuntime {
|
|
|
568
543
|
};
|
|
569
544
|
}
|
|
570
545
|
|
|
571
|
-
private resolve(ctx: Pick<ExtensionContext, "cwd"
|
|
546
|
+
private resolve(ctx: Pick<ExtensionContext, "cwd">): ResolvedBwrap {
|
|
572
547
|
const config = loadBwrapConfig(ctx.cwd);
|
|
573
|
-
if (!ctx.hasUI) return resolveHeadlessBwrap(config);
|
|
574
|
-
if (this.sandboxDisabled) return resolveBwrap({ ...config, mode: "allow-all" });
|
|
575
548
|
if (!this.resolved) this.resolved = resolveBwrap(config);
|
|
576
549
|
return this.resolved;
|
|
577
550
|
}
|
|
@@ -582,8 +555,8 @@ export class BwrapRuntime {
|
|
|
582
555
|
reason: string | undefined,
|
|
583
556
|
execCwd: string,
|
|
584
557
|
): Promise<void> {
|
|
585
|
-
|
|
586
|
-
if (
|
|
558
|
+
// hasUI 判定推迟到审批时刻:无 UI 会话弹不了审批框,按用户点 Deny 的标准文案拒绝
|
|
559
|
+
if (!ctx.hasUI) throw new Error(UNSANDBOXED_DENIED);
|
|
587
560
|
const decision = await this.approveFullAccessUI(ctx, command, reason, execCwd);
|
|
588
561
|
// 关闭对话框 = 中断并拒绝,不循环重问
|
|
589
562
|
if (decision === undefined) {
|
package/src/bwrap/sandbox.ts
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
resolveBwrap,
|
|
29
29
|
resolveBwrapPath,
|
|
30
30
|
type ResolvedBwrap,
|
|
31
|
-
resolveHeadlessBwrap,
|
|
32
31
|
} from "./core.js";
|
|
33
32
|
import type { NetworkStack } from "./network-stack.js";
|
|
34
33
|
|
|
@@ -39,8 +38,6 @@ export interface SandboxConfigInput {
|
|
|
39
38
|
configPath?: string;
|
|
40
39
|
/** 覆盖配置中的 mode(等价于会话内切 /bwrap 模式)。 */
|
|
41
40
|
mode?: BwrapMode;
|
|
42
|
-
/** 无 UI 会话策略:无论配置如何都强制 readonly。 */
|
|
43
|
-
headless?: boolean;
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
export interface SandboxRunOptions {
|
|
@@ -97,9 +94,7 @@ export function loadSandboxConfig(input: SandboxConfigInput): ResolvedBwrap {
|
|
|
97
94
|
const config: BwrapConfig = loadBwrapConfig(workspace, paths);
|
|
98
95
|
const configured: BwrapConfig =
|
|
99
96
|
input.mode === undefined ? config : { ...config, mode: input.mode };
|
|
100
|
-
|
|
101
|
-
input.headless === true ? resolveHeadlessBwrap(configured) : resolveBwrap(configured);
|
|
102
|
-
return resolveSandboxPaths(resolved, workspace);
|
|
97
|
+
return resolveSandboxPaths(resolveBwrap(configured), workspace);
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
/**
|