@hank-warren/pi-auto-permissions 0.5.0 → 0.6.0

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.
Files changed (3) hide show
  1. package/README.md +7 -1
  2. package/index.ts +66 -4
  3. package/package.json +4 -1
package/README.md CHANGED
@@ -143,7 +143,7 @@ The guardian must return one of three decisions:
143
143
 
144
144
  - `approve`: execute the command
145
145
  - `revise`: block it and tell the main agent what to correct
146
- - `ask_user`: open Pi's normal confirmation prompt
146
+ - `ask_user`: open an approval prompt (rendered with [`@hank-warren/pi-permission-selector`](../pi-permission-selector)'s `OptionSelector`): numbered options with `1`–`9` hotkeys, Tab to attach a note that is delivered to the agent as a steering user message, Esc to cancel — which blocks the command
147
147
 
148
148
  ## Conversation context and caching
149
149
 
@@ -264,6 +264,12 @@ $ git commit --dry-run -m "fix auth"
264
264
 
265
265
  Set `ui.enabled` to `false` to hide review state without disabling enforcement.
266
266
 
267
+ ### Herdr pane indicator
268
+
269
+ When the environment variable `HERDR_ENV` is set to exactly `1`, the extension additionally emits a `herdr:blocked` event whenever a command is waiting on your approval, and a matching cleared event once the review resolves. [Herdr](https://herdr.dev), a terminal multiplexer for coding agents, sets this variable for the panes it manages and uses the event to flag the pane that needs attention — useful when a review is blocking in a pane you are not currently looking at. The blocked event carries the gate label so the indicator can name the operation.
270
+
271
+ This is an optional integration and nothing needs to be configured to use it. Outside Herdr the variable is unset, the emit is skipped entirely, and every other feature behaves identically; the extension has no dependency on Herdr being installed.
272
+
267
273
  ## Trusted groups
268
274
 
269
275
  In a trusted project, create `.pi/trusted-ops` to bypass selected rule groups:
package/index.ts CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  type ReviewEvidenceRecord,
38
38
  } from "./review.js";
39
39
  import { mergeOverrideEvidence, type PermissionOverride } from "./override-evidence.js";
40
+ import { OptionSelector } from "@hank-warren/pi-permission-selector/selector.ts";
40
41
 
41
42
  const WIDGET_KEY = "auto-permissions";
42
43
  const PROJECT_CONFIG_DIR_NAME = (PiCodingAgent as { CONFIG_DIR_NAME?: string }).CONFIG_DIR_NAME ?? ".pi";
@@ -653,6 +654,61 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
653
654
  }
654
655
  }
655
656
 
657
+ /**
658
+ * Render an approval prompt with the shared `OptionSelector` through
659
+ * `ctx.ui.custom()` (non-overlay, so it swaps into the editor area exactly
660
+ * where `ctx.ui.select` used to render).
661
+ *
662
+ * Cancellation parity with `ctx.ui.select` — all of these resolve
663
+ * `undefined`, which every caller must treat as deny/cancel, never allow:
664
+ * - Esc (`OptionSelector.onCancel`)
665
+ * - `signal` abort (listener calls `done(undefined)`; pi's `done` is
666
+ * idempotent and performs its own teardown, so nothing else is needed)
667
+ * - a host that reports `hasUI` but cannot render custom components
668
+ *
669
+ * A Tab-typed note is delivered to the agent as a steering user message,
670
+ * matching what the retired `ExtensionSelectorComponent` monkey patch in
671
+ * pi-permission-selector did.
672
+ */
673
+ function promptSelect(
674
+ ctx: ExtensionContext,
675
+ title: string,
676
+ values: string[],
677
+ signal: AbortSignal,
678
+ opts: { allowComment: boolean },
679
+ ): Promise<string | undefined> {
680
+ return ctx.ui.custom<string | undefined>((tui, theme, _keybindings, done) => {
681
+ const onAbort = () => finish(undefined);
682
+ const finish = (result: string | undefined) => {
683
+ signal.removeEventListener("abort", onAbort);
684
+ done(result);
685
+ };
686
+ const selector = new OptionSelector({
687
+ title,
688
+ options: values.map((value) => ({ value, label: value })),
689
+ allowComment: opts.allowComment,
690
+ theme,
691
+ onSelect: (option, comment) => {
692
+ if (comment) {
693
+ try {
694
+ pi.sendUserMessage(comment, { deliverAs: "steer" });
695
+ } catch {
696
+ // Never let note delivery break the approval flow.
697
+ }
698
+ }
699
+ finish(option.value);
700
+ },
701
+ onCancel: () => finish(undefined),
702
+ requestRender: () => tui.requestRender(),
703
+ });
704
+ // `done` before the factory resolves is safe: pi marks the dialog closed
705
+ // and never mounts the component.
706
+ if (signal.aborted) onAbort();
707
+ else signal.addEventListener("abort", onAbort, { once: true });
708
+ return selector;
709
+ });
710
+ }
711
+
656
712
  async function askUser(
657
713
  gate: Gate,
658
714
  command: string,
@@ -695,10 +751,12 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
695
751
  try {
696
752
  let choice: string | undefined;
697
753
  try {
698
- choice = await ctx.ui.select(
754
+ choice = await promptSelect(
755
+ ctx,
699
756
  `${gate.label} — Auto Permissions needs approval\n\n${detail}\n\n${command}`,
700
757
  permissionPromptOptions(config.evaluationLog.enabled),
701
- { signal: promptSignal },
758
+ promptSignal,
759
+ { allowComment: true },
702
760
  );
703
761
  } catch (error) {
704
762
  if (!lifecycleStale() && !reviewCancelled(signal)) throw error;
@@ -879,10 +937,14 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
879
937
  try {
880
938
  let choice: string | undefined;
881
939
  try {
882
- choice = await ctx.ui.select(
940
+ // The override prompt never had Tab-to-comment (the monkey patch only
941
+ // enabled it on titles matching /needs approval/i), so keep parity.
942
+ choice = await promptSelect(
943
+ ctx,
883
944
  `Convention override: ${gate.label}\n\n${params.reason}\n\n${params.command}`,
884
945
  ["Allow for this session", "Keep blocked"],
885
- { signal: promptSignal },
946
+ promptSignal,
947
+ { allowComment: false },
886
948
  );
887
949
  } catch (error) {
888
950
  if (!lifecycleStale() && !reviewCancelled(signal)) throw error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hank-warren/pi-auto-permissions",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Fork of @ogulcancelik/pi-auto-permissions with dialog-answer user evidence: context-aware Bash permissions for Pi with automated guardian review.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -51,6 +51,9 @@
51
51
  "README.md",
52
52
  "LICENSE"
53
53
  ],
54
+ "dependencies": {
55
+ "@hank-warren/pi-permission-selector": "^1.0.0"
56
+ },
54
57
  "peerDependencies": {
55
58
  "@earendil-works/pi-ai": "*",
56
59
  "@earendil-works/pi-coding-agent": "*",