@zeldrisho/pi-gate 0.7.0 → 0.7.1
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 +12 -1
- package/README.md +2 -2
- package/package.json +2 -1
- package/src/index.ts +32 -15
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.1] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Create starter configuration when a session starts rather than when the extension is loaded.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Cancel active approval dialogs and reject queued calls during shutdown, reload, and session replacement.
|
|
19
|
+
|
|
10
20
|
## [0.7.0] - 2026-10-24
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -95,7 +105,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
95
105
|
- support `prompt`, `block`, and `allow` actions, with longest-pattern-wins resolution
|
|
96
106
|
- block rather than auto-approve in non-UI modes
|
|
97
107
|
|
|
98
|
-
[Unreleased]: https://github.com/zeldrisho/pi-packages/compare/pi-gate-v0.7.
|
|
108
|
+
[Unreleased]: https://github.com/zeldrisho/pi-packages/compare/pi-gate-v0.7.1...HEAD
|
|
109
|
+
[0.7.1]: https://github.com/zeldrisho/pi-packages/compare/pi-gate-v0.7.0...pi-gate-v0.7.1
|
|
99
110
|
[0.7.0]: https://github.com/zeldrisho/pi-packages/compare/pi-gate-v0.6.1...pi-gate-v0.7.0
|
|
100
111
|
[0.6.1]: https://github.com/zeldrisho/pi-packages/compare/pi-gate-v0.6.0...pi-gate-v0.6.1
|
|
101
112
|
[0.6.0]: https://github.com/zeldrisho/pi-packages/compare/pi-gate-v0.5.0...pi-gate-v0.6.0
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Use `pi config` to enable or disable this extension interactively. Run `pi list`
|
|
|
15
15
|
|
|
16
16
|
## Configure
|
|
17
17
|
|
|
18
|
-
Edit `~/.pi/agent/gate.json` to choose which commands need approval. The extension creates this file with starter rules
|
|
18
|
+
Edit `~/.pi/agent/gate.json` to choose which commands need approval. The extension creates this file with starter rules when the first session starts. Changes apply before the next gated call, without restarting Pi. Add the optional `$schema` below for editor completion and validation:
|
|
19
19
|
|
|
20
20
|
```json
|
|
21
21
|
{
|
|
@@ -46,7 +46,7 @@ pi-gate is a guard rail, not a sandbox. Complex shell syntax, pipelines, redirec
|
|
|
46
46
|
|
|
47
47
|
The built-in `bash` tool is gated, and `write`/`edit` calls targeting the gate config require approval. Prompt dialogs offer `Allow` and `Deny`; approvals are never remembered. To stop repeated prompts, add a specific `allow` rule to `gate.json`.
|
|
48
48
|
|
|
49
|
-
Approval dialogs are serialized for concurrent tool calls; cancellation, session replacement, or a policy change while a dialog is open denies the pending call.
|
|
49
|
+
Approval dialogs are serialized for concurrent tool calls; cancellation, shutdown, reload, session replacement, or a policy change while a dialog is open denies the pending call. Calls queued before shutdown or session replacement are also denied.
|
|
50
50
|
|
|
51
51
|
Long commands are truncated in approval dialogs, but an approved command executes in full. Denied, dismissed, blocked, and timed-out calls stop the turn; other allowed calls in a parallel batch may still run. In non-interactive modes (`-p`, JSON), commands requiring approval are denied.
|
|
52
52
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeldrisho/pi-gate",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Pi extension that gates bash commands and protects its configuration from tool edits",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"command-blocking",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "^1.4.0",
|
|
36
|
+
"typebox": "^1.4.2",
|
|
36
37
|
"typescript": "^7.0.2",
|
|
37
38
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
|
|
38
39
|
"vite-plus": "1.3.0"
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Matching is case-insensitive and whitespace-normalized. Allow occurrences
|
|
20
20
|
* suppress contained restrictions in the same command; surviving blocks win.
|
|
21
21
|
*
|
|
22
|
-
* On first
|
|
22
|
+
* On first session start, a configuration with the default prompt timeout and starter
|
|
23
23
|
* operation rules is written to `~/.pi/agent/gate.json` when neither file exists.
|
|
24
24
|
*
|
|
25
25
|
* Non-UI modes (print, JSON) never auto-approve. A `prompt` or `block` rule
|
|
@@ -112,14 +112,16 @@ export {
|
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* Gates `bash` calls against user rules and requires approval for protected
|
|
115
|
-
* configuration writes and edits. Attempts to create a starter configuration
|
|
115
|
+
* configuration writes and edits. Attempts to create a starter configuration
|
|
116
|
+
* at session start if absent.
|
|
116
117
|
* Reloads rules before gating and after each prompt; approvals apply per call.
|
|
117
|
-
* Dialogs are serialized, and
|
|
118
|
-
*
|
|
118
|
+
* Dialogs are serialized. Cancellation, shutdown, and session replacement deny
|
|
119
|
+
* active and queued gated calls; policy changes invalidate an active approval.
|
|
120
|
+
* UI failures reject the affected tool call.
|
|
119
121
|
*/
|
|
120
122
|
export default function piGate(pi: ExtensionAPI): void {
|
|
121
|
-
ensureConfig();
|
|
122
123
|
let loadResult = loadConfigResult();
|
|
124
|
+
let session = new AbortController();
|
|
123
125
|
let policy = JSON.stringify(loadResult);
|
|
124
126
|
let revision = 0;
|
|
125
127
|
let pending = Promise.resolve();
|
|
@@ -135,8 +137,16 @@ export default function piGate(pi: ExtensionAPI): void {
|
|
|
135
137
|
}
|
|
136
138
|
};
|
|
137
139
|
|
|
140
|
+
pi.on("session_shutdown", () => {
|
|
141
|
+
session.abort();
|
|
142
|
+
revision += 1;
|
|
143
|
+
});
|
|
144
|
+
|
|
138
145
|
pi.on("session_start", (_event, ctx) => {
|
|
146
|
+
session.abort();
|
|
147
|
+
session = new AbortController();
|
|
139
148
|
revision += 1;
|
|
149
|
+
ensureConfig();
|
|
140
150
|
refreshConfig();
|
|
141
151
|
const path = configPath();
|
|
142
152
|
const ruleCount = Object.keys(loadResult.config.operations).length;
|
|
@@ -160,7 +170,14 @@ export default function piGate(pi: ExtensionAPI): void {
|
|
|
160
170
|
});
|
|
161
171
|
|
|
162
172
|
pi.on("tool_call", (event, ctx) => {
|
|
173
|
+
// Capture ownership before queueing so old calls cannot enter a replacement session.
|
|
174
|
+
const signal = ctx.signal ? AbortSignal.any([ctx.signal, session.signal]) : session.signal;
|
|
175
|
+
|
|
163
176
|
// Pi can dispatch sibling tools concurrently; never overlap approval dialogs.
|
|
177
|
+
/**
|
|
178
|
+
* Returns a terminating block result for denied calls, or undefined to allow;
|
|
179
|
+
* UI errors propagate.
|
|
180
|
+
*/
|
|
164
181
|
const run = async (): Promise<ToolCallEventResult | undefined> => {
|
|
165
182
|
let command: string;
|
|
166
183
|
let configChange = false;
|
|
@@ -181,6 +198,14 @@ export default function piGate(pi: ExtensionAPI): void {
|
|
|
181
198
|
return undefined;
|
|
182
199
|
}
|
|
183
200
|
|
|
201
|
+
if (signal.aborted) {
|
|
202
|
+
return {
|
|
203
|
+
block: true,
|
|
204
|
+
reason: "pi-gate: approval cancelled; command not run.",
|
|
205
|
+
terminate: true,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
184
209
|
refreshConfig();
|
|
185
210
|
|
|
186
211
|
const match = configChange
|
|
@@ -210,14 +235,6 @@ export default function piGate(pi: ExtensionAPI): void {
|
|
|
210
235
|
};
|
|
211
236
|
}
|
|
212
237
|
|
|
213
|
-
if (ctx.signal?.aborted) {
|
|
214
|
-
return {
|
|
215
|
-
block: true,
|
|
216
|
-
reason: "pi-gate: approval cancelled; command not run.",
|
|
217
|
-
terminate: true,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
238
|
const promptRevision = revision;
|
|
222
239
|
|
|
223
240
|
reportHerdrBlocked(pi, ctx, true);
|
|
@@ -234,7 +251,7 @@ export default function piGate(pi: ExtensionAPI): void {
|
|
|
234
251
|
choice = await ctx.ui.select(
|
|
235
252
|
`pi-gate: allow this command?\n\n${promptCommand}\n\nMatched rule: ${rule}${configChange ? "" : "\nMatched command text is wrapped in »…«"}`,
|
|
236
253
|
["Allow", "Deny"],
|
|
237
|
-
{ timeout: timeoutMs, signal
|
|
254
|
+
{ timeout: timeoutMs, signal },
|
|
238
255
|
);
|
|
239
256
|
// Pi returns undefined for both dismissal and timeout; elapsed time separates the timeout case.
|
|
240
257
|
timedOut = choice === undefined && Date.now() - promptStartedAt >= timeoutMs;
|
|
@@ -244,7 +261,7 @@ export default function piGate(pi: ExtensionAPI): void {
|
|
|
244
261
|
|
|
245
262
|
refreshConfig();
|
|
246
263
|
|
|
247
|
-
if (
|
|
264
|
+
if (signal.aborted || revision !== promptRevision) {
|
|
248
265
|
return {
|
|
249
266
|
block: true,
|
|
250
267
|
reason:
|