@hank-warren/pi-auto-permissions 0.1.1 → 0.1.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/NOTICE.md +1 -1
- package/README.md +10 -2
- package/evaluation-log.ts +40 -3
- package/index.ts +25 -9
- package/package.json +1 -1
package/NOTICE.md
CHANGED
|
@@ -4,4 +4,4 @@ This package is a fork of [`@ogulcancelik/pi-auto-permissions`](https://github.c
|
|
|
4
4
|
|
|
5
5
|
The original work is Copyright (c) 2026 Can Celik and is used under the MIT License included in [`LICENSE`](LICENSE).
|
|
6
6
|
|
|
7
|
-
Fork-specific changes (the `reviewEvidence.userAnswerTools` dialog-answer evidence feature, proposed upstream in [ogulcancelik/pi-extensions#24](https://github.com/ogulcancelik/pi-extensions/issues/24) / [PR #25](https://github.com/ogulcancelik/pi-extensions/pull/25)
|
|
7
|
+
Fork-specific changes are Copyright (c) 2026 Hank Warren and are released under the same MIT License. They include the `reviewEvidence.userAnswerTools` dialog-answer evidence feature, proposed upstream in [ogulcancelik/pi-extensions#24](https://github.com/ogulcancelik/pi-extensions/issues/24) / [PR #25](https://github.com/ogulcancelik/pi-extensions/pull/25), and the prompted-review evaluation log with explicit user feedback.
|
package/README.md
CHANGED
|
@@ -185,7 +185,7 @@ The allowlist matches what you wrote: a bare name such as `ask_user_question` ma
|
|
|
185
185
|
|
|
186
186
|
## Prompted-review evaluation log
|
|
187
187
|
|
|
188
|
-
Auto Permissions can append a private JSONL regression record whenever the guardian asks for confirmation and the user
|
|
188
|
+
Auto Permissions can append a private JSONL regression record whenever the guardian asks for confirmation and the user gives explicit feedback:
|
|
189
189
|
|
|
190
190
|
```json
|
|
191
191
|
{
|
|
@@ -198,7 +198,15 @@ Auto Permissions can append a private JSONL regression record whenever the guard
|
|
|
198
198
|
|
|
199
199
|
The path defaults to `review-evals.jsonl` beside the Auto Permissions config and resolves relative to that config. The file is created with mode `0600`.
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
When logging is enabled, prompted reviews offer three choices:
|
|
202
|
+
|
|
203
|
+
- **Allow — asking was unnecessary** executes the command and records `userChoice: "allow_unnecessary"` with `expectedDecision: "approve"`.
|
|
204
|
+
- **Block** remains the second choice, blocks the command, and records `userChoice: "block"` with `expectedDecision: "ask_user"`.
|
|
205
|
+
- **Allow — asking was appropriate** executes the command and records `userChoice: "allow_appropriate"` with `expectedDecision: "ask_user"`.
|
|
206
|
+
|
|
207
|
+
When logging is disabled, the prompt retains the normal **Allow** and **Block** choices. Prompts from Pi or other extensions are unchanged.
|
|
208
|
+
|
|
209
|
+
Each version 2 record contains the collected user request, exact command, compact reviewer evidence, guardian reason, gate and session metadata, raw user choice, and both labels used for evaluation. The guardian's `actualDecision` is `ask_user`. Automatic-review failures are identified separately with `decisionSource: "review_failure"`. Existing version 1 records can remain in the same JSONL file.
|
|
202
210
|
|
|
203
211
|
Logging is disabled by default. Records can contain sensitive conversation text and shell commands, so keep the file private and out of repositories. Cancelled or interrupted prompts are not labeled or logged.
|
|
204
212
|
|
package/evaluation-log.ts
CHANGED
|
@@ -2,8 +2,31 @@ import { chmodSync, mkdirSync, appendFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import type { PermissionDecision, ReviewEvidenceRecord } from "./review.js";
|
|
4
4
|
|
|
5
|
+
export const PROMPT_FEEDBACK_OPTIONS = {
|
|
6
|
+
allowUnnecessary: "Allow — asking was unnecessary",
|
|
7
|
+
allowAppropriate: "Allow — asking was appropriate",
|
|
8
|
+
block: "Block",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type PromptEvaluationUserChoice = "allow_unnecessary" | "allow_appropriate" | "block";
|
|
12
|
+
|
|
13
|
+
export interface PromptChoiceClassification {
|
|
14
|
+
allowsExecution: boolean;
|
|
15
|
+
userChoice?: PromptEvaluationUserChoice;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function permissionPromptOptions(evaluationLoggingEnabled: boolean): string[] {
|
|
19
|
+
return evaluationLoggingEnabled
|
|
20
|
+
? [
|
|
21
|
+
PROMPT_FEEDBACK_OPTIONS.allowUnnecessary,
|
|
22
|
+
PROMPT_FEEDBACK_OPTIONS.block,
|
|
23
|
+
PROMPT_FEEDBACK_OPTIONS.allowAppropriate,
|
|
24
|
+
]
|
|
25
|
+
: ["Allow", "Block"];
|
|
26
|
+
}
|
|
27
|
+
|
|
5
28
|
export interface PromptEvaluationRecord {
|
|
6
|
-
version:
|
|
29
|
+
version: 2;
|
|
7
30
|
timestamp: string;
|
|
8
31
|
sessionId: string;
|
|
9
32
|
cwd: string;
|
|
@@ -18,14 +41,28 @@ export interface PromptEvaluationRecord {
|
|
|
18
41
|
actualDecision: "ask_user";
|
|
19
42
|
actualReason: string;
|
|
20
43
|
decisionSource: "guardian" | "review_failure";
|
|
21
|
-
userChoice:
|
|
44
|
+
userChoice: PromptEvaluationUserChoice;
|
|
22
45
|
expectedDecision: Extract<PermissionDecision, "approve" | "ask_user">;
|
|
23
46
|
}
|
|
24
47
|
|
|
48
|
+
export function classifyPromptChoice(choice: string | undefined): PromptChoiceClassification | undefined {
|
|
49
|
+
if (choice === "Allow") return { allowsExecution: true };
|
|
50
|
+
if (choice === PROMPT_FEEDBACK_OPTIONS.allowUnnecessary) {
|
|
51
|
+
return { allowsExecution: true, userChoice: "allow_unnecessary" };
|
|
52
|
+
}
|
|
53
|
+
if (choice === PROMPT_FEEDBACK_OPTIONS.allowAppropriate) {
|
|
54
|
+
return { allowsExecution: true, userChoice: "allow_appropriate" };
|
|
55
|
+
}
|
|
56
|
+
if (choice === PROMPT_FEEDBACK_OPTIONS.block) {
|
|
57
|
+
return { allowsExecution: false, userChoice: "block" };
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
25
62
|
export function expectedDecisionForChoice(
|
|
26
63
|
choice: PromptEvaluationRecord["userChoice"],
|
|
27
64
|
): PromptEvaluationRecord["expectedDecision"] {
|
|
28
|
-
return choice === "
|
|
65
|
+
return choice === "allow_unnecessary" ? "approve" : "ask_user";
|
|
29
66
|
}
|
|
30
67
|
|
|
31
68
|
export function appendPromptEvaluation(path: string, record: PromptEvaluationRecord): void {
|
package/index.ts
CHANGED
|
@@ -8,7 +8,13 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { loadAutoPermissionsConfig, type AutoPermissionsConfig } from "./config.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
appendPromptEvaluation,
|
|
13
|
+
classifyPromptChoice,
|
|
14
|
+
expectedDecisionForChoice,
|
|
15
|
+
permissionPromptOptions,
|
|
16
|
+
type PromptEvaluationUserChoice,
|
|
17
|
+
} from "./evaluation-log.js";
|
|
12
18
|
import { findGates, type Gate } from "./gates.js";
|
|
13
19
|
import {
|
|
14
20
|
buildReviewEnvelope,
|
|
@@ -530,7 +536,7 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
|
|
|
530
536
|
target: ReviewTarget,
|
|
531
537
|
relevantContext: ReviewEvidenceRecord[],
|
|
532
538
|
decisionSource: "guardian" | "review_failure",
|
|
533
|
-
userChoice:
|
|
539
|
+
userChoice: PromptEvaluationUserChoice,
|
|
534
540
|
): void {
|
|
535
541
|
if (!config.evaluationLog.enabled) return;
|
|
536
542
|
try {
|
|
@@ -539,7 +545,7 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
|
|
|
539
545
|
.map((record) => record.text)
|
|
540
546
|
.join("\n");
|
|
541
547
|
appendPromptEvaluation(config.evaluationLog.path, {
|
|
542
|
-
version:
|
|
548
|
+
version: 2,
|
|
543
549
|
timestamp: new Date().toISOString(),
|
|
544
550
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
545
551
|
cwd: ctx.cwd,
|
|
@@ -602,7 +608,7 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
|
|
|
602
608
|
try {
|
|
603
609
|
choice = await ctx.ui.select(
|
|
604
610
|
`${gate.label} — Auto Permissions needs approval\n\n${detail}\n\n${command}`,
|
|
605
|
-
|
|
611
|
+
permissionPromptOptions(config.evaluationLog.enabled),
|
|
606
612
|
{ signal: promptSignal },
|
|
607
613
|
);
|
|
608
614
|
} catch (error) {
|
|
@@ -616,14 +622,24 @@ export default function autoPermissionsExtension(pi: ExtensionAPI) {
|
|
|
616
622
|
if (sessionActive) clearReviewDisplay(ctx, target);
|
|
617
623
|
return cancelled();
|
|
618
624
|
}
|
|
619
|
-
|
|
620
|
-
|
|
625
|
+
const classification = classifyPromptChoice(choice);
|
|
626
|
+
if (config.evaluationLog.enabled && classification?.userChoice) {
|
|
627
|
+
logPromptEvaluation(
|
|
628
|
+
gate,
|
|
629
|
+
command,
|
|
630
|
+
detail,
|
|
631
|
+
config,
|
|
632
|
+
ctx,
|
|
633
|
+
target,
|
|
634
|
+
evaluationContext,
|
|
635
|
+
decisionSource,
|
|
636
|
+
classification.userChoice,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
if (classification?.allowsExecution) {
|
|
621
640
|
showReviewDisplay(ctx, config, gate, command, target, "approved", "approved by user", true);
|
|
622
641
|
return undefined;
|
|
623
642
|
}
|
|
624
|
-
if (choice === "Block") {
|
|
625
|
-
logPromptEvaluation(gate, command, detail, config, ctx, target, evaluationContext, decisionSource, "block");
|
|
626
|
-
}
|
|
627
643
|
showReviewDisplay(ctx, config, gate, command, target, "blocked", "blocked by user", true);
|
|
628
644
|
return { block: true, reason: "Blocked by user" };
|
|
629
645
|
} finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-auto-permissions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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": [
|