@hank-warren/pi-auto-permissions 0.5.1 → 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 +1 -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
 
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.1",
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": "*",