f343923014aa829e2922a21032b142fe27b0592d
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/AGENTS.md b/AGENTS.md
2index ba8f5e1ee05cb821153a06cac1cfc87cbe5d7d88..0831b5a8f8f035e0cbd5a5ae4dfa120afcd957b4 100644
3--- a/AGENTS.md
4+++ b/AGENTS.md
5@@ -15,7 +15,6 @@ Security policy engine for Pi. Evaluates tool calls, primarily Bash commands, ag
6 - `src/normalizer.ts` — Request normalization and cache-key generation
7 - `src/cache.ts` — JSONL decision cache
8 - `src/logger.ts` — JSONL decision logging
9-- `src/notify.ts` — Native Linux and macOS desktop notifications
10 - `src/types.ts` — Shared decision types
11
12 ## Commands
13diff --git a/README.md b/README.md
14index bee627fd1a1be73b1303f2ac073290f929671a35..73ba64a10206f89813a2989d34401da7eecc4b6f 100644
15--- a/README.md
16+++ b/README.md
17@@ -8,8 +8,6 @@ A [Pi](https://pi.dev) extension that evaluates tool calls through a three-stage
18
19 An `allow` decision lets Pi run the tool. A `deny` decision blocks it. An `ask` decision opens a confirmation dialog in interactive Pi and blocks the call when no UI is available.
20
21-The extension also sends a desktop notification when a Pi session settles.
22-
23 ## Install
24
25 Install the checked-out package globally:
26@@ -64,9 +62,9 @@ The extension handles Pi's `tool_call` event and evaluates each tool call before
27
28diff --git a/opencode-logo.png b/opencode-logo.png
29deleted file mode 100644
30index cf868c8e8711cf9e79638e17fedb2ae483047b74..0000000000000000000000000000000000000000
31Binary files a/opencode-logo.png and /dev/null differ
32diff --git a/package.json b/package.json
33index dfbd20a12ac26ddcfbbf25c00cadf672ed05e3a0..806761936dbf709fb4816c435df41c50c7b474e6 100644
34--- a/package.json
35+++ b/package.json
36@@ -3,15 +3,19 @@
37 "version": "3.0.0",
38 "type": "module",
39 "main": "src/index.ts",
40- "keywords": ["pi-package"],
41+ "keywords": [
42+ "pi-package"
43+ ],
44 "pi": {
45- "extensions": ["./src/index.ts"]
46+ "extensions": [
47+ "./src/index.ts"
48+ ]
49 },
50 "peerDependencies": {
51 "@earendil-works/pi-coding-agent": "*"
52 },
53 "devDependencies": {
54- "@earendil-works/pi-coding-agent": "^0.82.1",
55+ "@earendil-works/pi-coding-agent": "^0.84.2",
56 "@eslint/js": "^10.0.1",
57 "bun-types": "latest",
58 "eslint": "^10.1.0",
59diff --git a/src/index.ts b/src/index.ts
60index aeb09d709dde1023bab23c6b2be1eccf40d1b9d9..ea587b919a641c955f8ab9bab4c18b477548a479 100644
61--- a/src/index.ts
62+++ b/src/index.ts
63@@ -1,5 +1,4 @@
64 import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"
65-import { basename } from "node:path"
66 import { captureProviderCredentials } from "./api"
67 import { lookupCache, writeCache } from "./cache"
68 import { checkDeterministic } from "./deterministic"
69@@ -7,7 +6,6 @@ import { evaluateWithLLM } from "./llm"
70 import { checkStaticPermission } from "./static-permissions"
71 import { logDecision } from "./logger"
72 import { cacheKey, normalizeRequest } from "./normalizer"
73-import { notify } from "./notify"
74 import type { Decision } from "./types"
75
76 type ToolInput = Record<string, unknown>
77@@ -140,16 +138,17 @@ export default function (pi: ExtensionAPI) {
78 }
79
80 const summary = inputSummary(event.toolName, input)
81- const approved = await ctx.ui.confirm(
82- "Policy approval required",
83- `${event.toolName}: ${summary}\n\n${decision.reason}`,
84- )
85+ pi.events.emit("herdr:blocked", { active: true, label: "Policy approval required" })
86+ let approved: boolean
87+ try {
88+ approved = await ctx.ui.confirm(
89+ "Policy approval required",
90+ `${event.toolName}: ${summary}\n\n${decision.reason}`,
91+ )
92+ } finally {
93+ pi.events.emit("herdr:blocked", { active: false })
94+ }
95 if (approved) return undefined
96 return { block: true, reason: `Blocked by user: ${decision.reason}` }
97 })
98-
99- pi.on("agent_settled", async (_event, ctx) => {
100- const projectName = basename(ctx.cwd)
101- notify(projectName ? `Pi (${projectName})` : "Pi", "Session complete")
102- })
103 }
104diff --git a/src/notify.ts b/src/notify.ts
105deleted file mode 100644
106index 81e8c861714d1b6d5007b588d0dff03e5fbedbc9..0000000000000000000000000000000000000000
107--- a/src/notify.ts
108+++ /dev/null
109@@ -1,23 +0,0 @@
110-import { execFile } from "child_process"
111-const IS_MAC = process.platform === "darwin"
112-
113-function notifyLinux(title: string, message: string, timeout: number): void {
114- const args = ["--app-name", "pi", "--expire-time", String(timeout * 1000), "--", title, message]
115-
116- execFile("notify-send", args, () => {})
117-}
118-
119-function escapeAppleScript(s: string): string {
120- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
121-}
122-
123-function notifyMac(title: string, message: string): void {
124- const t = escapeAppleScript(title)
125- const m = escapeAppleScript(message)
126- execFile("osascript", ["-e", `display notification "${m}" with title "${t}"`], () => {})
127-}
128-
129-export function notify(title: string, message: string, timeout = 10): void {
130- if (IS_MAC) notifyMac(title, message)
131- else notifyLinux(title, message, timeout)
132-}
133diff --git a/test/extension.test.ts b/test/extension.test.ts
134index b2b22d9c707e5d60e11a6260cb92c23ea4bd7ef2..e84da230cbb4822950af4a1a48183ccd7f76a579 100644
135--- a/test/extension.test.ts
136+++ b/test/extension.test.ts
137@@ -20,13 +20,19 @@ afterEach(() => {
138 })
139
140 type ToolCallHandler = (event: { toolName: string; input: Record<string, unknown> }, ctx: any) => Promise<unknown>
141+type EmittedEvent = { name: string; data: unknown }
142
143-function loadToolCallHandler(): ToolCallHandler {
144+function loadToolCallHandler(emittedEvents: EmittedEvent[] = []): ToolCallHandler {
145 let handler: ToolCallHandler | undefined
146 PolicyEngine({
147 on(event: string, callback: ToolCallHandler) {
148 if (event === "tool_call") handler = callback
149 },
150+ events: {
151+ emit(name: string, data: unknown) {
152+ emittedEvents.push({ name, data })
153+ },
154+ },
155 } as any)
156 if (!handler) throw new Error("tool_call handler was not registered")
157 return handler
158@@ -65,7 +71,8 @@ describe("Pi policy extension", () => {
159 })
160
161 test("allows a user-approved command", async () => {
162- const handler = loadToolCallHandler()
163+ const emittedEvents: EmittedEvent[] = []
164+ const handler = loadToolCallHandler(emittedEvents)
165 let prompted = false
166 const confirm = async () => {
167 prompted = true
168@@ -74,5 +81,9 @@ describe("Pi policy extension", () => {
169 await expect(handler({ toolName: "bash", input: { command: "sudo apt install foo" } }, context(true, confirm)))
170 .resolves.toBeUndefined()
171 expect(prompted).toBe(true)
172+ expect(emittedEvents).toEqual([
173+ { name: "herdr:blocked", data: { active: true, label: "Policy approval required" } },
174+ { name: "herdr:blocked", data: { active: false } },
175+ ])
176 })
177 })