@erichll/pi-auto-review 0.17.0 → 0.18.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0 - 2026-09-11
4
+
5
+ - Coordinated release for `@erichll/pi-sandbox 0.18.0`.
6
+ - Dismiss the above-editor widget eight seconds after an allow or auto-confirm
7
+ so a successful check does not stay on screen until the next review. Denials,
8
+ deferrals, and local-confirmation waits still remain until the next check.
9
+ - Raise the `@gotgenes/pi-permission-system` peer floor to `>=30.0.0` and drop
10
+ the upper bound. 29.x is no longer claimed; 32.x does not change the public
11
+ authorizer API this package uses, and later majors are no longer excluded by
12
+ the range.
13
+
3
14
  ## 0.17.0 - 2026-09-05
4
15
 
5
16
  - Coordinated release for `@erichll/pi-sandbox 0.17.0`; the broker API and
package/README.md CHANGED
@@ -31,8 +31,7 @@ that dependency is a hard prerequisite (see [Install and enable](#install-and-en
31
31
  > **Prerequisite:** pi-auto-review is an authorizer inside
32
32
  > `@gotgenes/pi-permission-system`. Pi does not auto-install peer packages, so
33
33
  > install the permission system separately (once per machine) before this
34
- > extension. This release line supports permission-system 29.3.0 through the
35
- > latest 31.x release:
34
+ > extension. This release line supports permission-system 30.0.0 and later:
36
35
 
37
36
  Node.js 22.13.0 or newer is required. Permission auditing uses Node's built-in
38
37
  `node:sqlite`; it does not require a SQLite CLI, system SQLite library, or npm
@@ -178,9 +177,11 @@ Interactive sessions show the current permission check in a single widget
178
177
  above the editor. Each check first shows its surface, compact target, and the
179
178
  dynamically configured reviewer model, then replaces that content in place
180
179
  with the outcome, target and rationale, model, token usage, duration, and any
181
- extra call count. A new check replaces the previous result; the latest result
182
- remains visible until then and is cleared when the session changes or shuts
183
- down. Concurrent older checks cannot overwrite the most recently started one.
180
+ extra call count. A new check replaces the previous result. Allowed and
181
+ auto-confirmed results dismiss after eight seconds so they do not occupy the
182
+ editor; denials, deferrals, and local-confirmation waits stay until the next
183
+ check, a session change, or shutdown. Concurrent older checks cannot overwrite
184
+ the most recently started one.
184
185
 
185
186
  Every request still has its own model call, verdict, grant, local confirmation,
186
187
  and audit record. No new review-result transcript entries are written. Existing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erichll/pi-auto-review",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Fail-closed, model-backed approval broker for Pi: deterministically hard-denies dangerous operations, reviews dangerous boundaries with a reviewer model, and issues one-shot expiring grants that OS sandbox adapters must consume before retrying.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "peerDependencies": {
50
50
  "@earendil-works/pi-ai": "^0.85.0",
51
51
  "@earendil-works/pi-coding-agent": "^0.85.0",
52
- "@gotgenes/pi-permission-system": ">=29.3.0 <32.0.0"
52
+ "@gotgenes/pi-permission-system": ">=30.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@earendil-works/pi-ai": "^0.85.0",
@@ -3,6 +3,8 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  export const USER_REVIEW_STATUS_KEY = "pi-auto-review";
4
4
  export const USER_REVIEW_ENTRY_TYPE = "pi-auto-review";
5
5
  export const USER_REVIEW_WIDGET_KEY = "pi-auto-review";
6
+ /** How long a successful completed widget stays above the editor. */
7
+ export const USER_REVIEW_SUCCESS_WIDGET_TTL_MS = 8_000;
6
8
 
7
9
  export type UserReviewOutcome =
8
10
  | "allow"
@@ -652,9 +654,14 @@ function setWidget(
652
654
  }
653
655
  }
654
656
 
657
+ function isTransientCompletedOutcome(outcome: UserReviewOutcome): boolean {
658
+ return outcome === "allow" || outcome === "auto_confirm";
659
+ }
660
+
655
661
  /** Owns the single live widget and rejects stale concurrent completions. */
656
662
  export class UserReviewWidgetController {
657
663
  #generation = 0;
664
+ #dismissTimer: ReturnType<typeof setTimeout> | undefined;
658
665
  /** Set while a `ctx.ui` prompt blocks the session (ui_prompt span). */
659
666
  #waiting?: { kind?: unknown; title?: unknown };
660
667
  #current?: {
@@ -664,6 +671,9 @@ export class UserReviewWidgetController {
664
671
  data?: UserReviewWidgetData;
665
672
  notice?: UserReviewNotice;
666
673
  };
674
+ constructor(
675
+ private readonly successTtlMs = USER_REVIEW_SUCCESS_WIDGET_TTL_MS,
676
+ ) {}
667
677
 
668
678
  begin(
669
679
  requestId: string,
@@ -671,6 +681,7 @@ export class UserReviewWidgetController {
671
681
  input: { surface: string; target?: string; model?: string },
672
682
  ): number {
673
683
  const generation = ++this.#generation;
684
+ this.#cancelDismiss();
674
685
  this.#current = { requestId, generation, ctx, data: buildUserReviewingWidgetData(input) };
675
686
  this.#render();
676
687
  return generation;
@@ -715,8 +726,12 @@ export class UserReviewWidgetController {
715
726
  return;
716
727
  }
717
728
  if (!this.#isCurrent(requestId, generation)) return;
729
+ this.#cancelDismiss();
718
730
  this.#current = { requestId, generation, ctx, notice, data };
719
731
  if (!setWidget(ctx, data)) notifyUserReview(ctx, notice);
732
+ if (isTransientCompletedOutcome(data.outcome)) {
733
+ this.#scheduleDismiss(generation, ctx);
734
+ }
720
735
  }
721
736
 
722
737
  permissionDecision(event: unknown): void {
@@ -738,18 +753,36 @@ export class UserReviewWidgetController {
738
753
  type: current.notice?.type ?? data.type,
739
754
  message: data.lines.join("\n"),
740
755
  };
756
+ this.#cancelDismiss();
741
757
  this.#current = { ...current, data, notice };
742
758
  if (!setWidget(current.ctx, data)) notifyUserReview(current.ctx, notice);
743
759
  }
744
760
 
745
761
  clear(ctx?: ExtensionContext): void {
746
762
  ++this.#generation;
763
+ this.#cancelDismiss();
747
764
  this.#waiting = undefined;
748
765
  const current = this.#current;
749
766
  this.#current = undefined;
750
767
  if (current) setWidget(ctx ?? current.ctx, undefined);
751
768
  }
752
769
 
770
+ #cancelDismiss(): void {
771
+ if (this.#dismissTimer === undefined) return;
772
+ clearTimeout(this.#dismissTimer);
773
+ this.#dismissTimer = undefined;
774
+ }
775
+
776
+ #scheduleDismiss(generation: number, ctx: ExtensionContext): void {
777
+ if (this.successTtlMs <= 0) return;
778
+ this.#dismissTimer = setTimeout(() => {
779
+ this.#dismissTimer = undefined;
780
+ if (this.#current?.generation !== generation) return;
781
+ this.clear(ctx);
782
+ }, this.successTtlMs);
783
+ this.#dismissTimer.unref?.();
784
+ }
785
+
753
786
  #isCurrent(requestId: string, generation: number): boolean {
754
787
  return this.#current?.requestId === requestId &&
755
788
  this.#current.generation === generation;