@gotgenes/pi-permission-system 5.10.0 → 5.11.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 +27 -0
- package/package.json +5 -5
- package/src/active-agent.ts +1 -1
- package/src/config-modal.ts +2 -2
- package/src/forwarded-permissions/polling.ts +1 -1
- package/src/forwarding-manager.ts +2 -2
- package/src/handlers/before-agent-start.ts +74 -61
- package/src/handlers/gates/descriptor.ts +1 -1
- package/src/handlers/index.ts +6 -15
- package/src/handlers/lifecycle.ts +55 -43
- package/src/handlers/permission-gate-handler.ts +346 -0
- package/src/index.ts +34 -39
- package/src/permission-event-rpc.ts +1 -1
- package/src/permission-prompter.ts +24 -7
- package/src/permission-session.ts +30 -1
- package/src/policy-loader.ts +1 -1
- package/src/runtime.ts +1 -1
- package/src/session-logger.ts +1 -1
- package/src/status.ts +1 -1
- package/src/subagent-context.ts +1 -1
- package/src/tool-registry.ts +6 -0
- package/tests/active-agent.test.ts +1 -1
- package/tests/config-modal.test.ts +2 -2
- package/tests/forwarding-manager.test.ts +1 -1
- package/tests/handlers/before-agent-start.test.ts +73 -93
- package/tests/handlers/gates/skill-read.test.ts +2 -2
- package/tests/handlers/input-events.test.ts +71 -64
- package/tests/handlers/input.test.ts +86 -84
- package/tests/handlers/lifecycle.test.ts +61 -73
- package/tests/handlers/tool-call-events.test.ts +129 -123
- package/tests/handlers/tool-call.test.ts +87 -61
- package/tests/permission-event-rpc.test.ts +1 -1
- package/tests/permission-prompter.test.ts +2 -2
- package/tests/permission-session.test.ts +62 -1
- package/tests/runtime.test.ts +1 -1
- package/tests/subagent-context.test.ts +1 -1
- package/src/handlers/input.ts +0 -126
- package/src/handlers/tool-call.ts +0 -203
- package/src/handlers/types.ts +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.11.1](https://github.com/gotgenes/pi-permission-system/compare/v5.11.0...v5.11.1) (2026-05-08)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* **retro:** add retro notes for issue [#130](https://github.com/gotgenes/pi-permission-system/issues/130) ([e2ed7cb](https://github.com/gotgenes/pi-permission-system/commit/e2ed7cbbabe2dabf5689704c14b59fc662c1e7d4))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Miscellaneous Chores
|
|
17
|
+
|
|
18
|
+
* migrate @mariozechner/* deps to @earendil-works/* ([8908be1](https://github.com/gotgenes/pi-permission-system/commit/8908be17624a60ff3272b9e0e0a720a239de2de5))
|
|
19
|
+
|
|
20
|
+
## [5.11.0](https://github.com/gotgenes/pi-permission-system/compare/v5.10.0...v5.11.0) (2026-05-08)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* add ToolRegistry interface ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([5e886fd](https://github.com/gotgenes/pi-permission-system/commit/5e886fd4bc67ddac56a7f2b7b445f6f172e60668))
|
|
26
|
+
* PermissionSession absorbs prompting methods ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([4ae81e6](https://github.com/gotgenes/pi-permission-system/commit/4ae81e6e32164926202633ec7831a4f3db69fc70))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
|
|
31
|
+
* plan handler classes to replace HandlerDeps ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([e8bc1a4](https://github.com/gotgenes/pi-permission-system/commit/e8bc1a40596aa6a032b367e642d19a03ca622394))
|
|
32
|
+
* **retro:** add retro notes for issue [#129](https://github.com/gotgenes/pi-permission-system/issues/129) ([23c29a2](https://github.com/gotgenes/pi-permission-system/commit/23c29a2148f6647565e6997d9c56341b43e74118))
|
|
33
|
+
* update architecture for handler classes ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([02d02b6](https://github.com/gotgenes/pi-permission-system/commit/02d02b647ec366eee3dc572afbaf436b1264b052))
|
|
34
|
+
|
|
8
35
|
## [5.10.0](https://github.com/gotgenes/pi-permission-system/compare/v5.9.0...v5.10.0) (2026-05-08)
|
|
9
36
|
|
|
10
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-permission-system",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
]
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
51
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
52
|
+
"@earendil-works/pi-tui": "*"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@biomejs/biome": "^2.4.13",
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
57
|
+
"@earendil-works/pi-tui": "^0.74.0",
|
|
58
58
|
"@types/node": "^25.6.0",
|
|
59
59
|
"markdownlint-cli2": "^0.22.1",
|
|
60
60
|
"typescript": "6.0.3",
|
package/src/active-agent.ts
CHANGED
package/src/config-modal.ts
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
type ExtensionAPI,
|
|
3
3
|
type ExtensionCommandContext,
|
|
4
4
|
getSettingsListTheme,
|
|
5
|
-
} from "@
|
|
6
|
-
import { type SettingItem, SettingsList } from "@
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { type SettingItem, SettingsList } from "@earendil-works/pi-tui";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_EXTENSION_CONFIG,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import type { PermissionForwardingDeps } from "./forwarded-permissions/polling";
|
|
4
4
|
import { processForwardedPermissionRequests } from "./forwarded-permissions/polling";
|
|
@@ -6,7 +6,7 @@ import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding"
|
|
|
6
6
|
import { isSubagentExecutionContext } from "./subagent-context";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Narrow interface for the forwarding lifecycle used by `
|
|
9
|
+
* Narrow interface for the forwarding lifecycle used by `PermissionSession`.
|
|
10
10
|
* `ForwardingManager` satisfies it; tests can provide a plain object mock.
|
|
11
11
|
*/
|
|
12
12
|
export interface ForwardingController {
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BeforeAgentStartEventResult,
|
|
3
3
|
ExtensionContext,
|
|
4
|
-
} from "@
|
|
5
|
-
|
|
6
|
-
/** Minimal subset of BeforeAgentStartEvent used by this handler. */
|
|
7
|
-
interface BeforeAgentStartPayload {
|
|
8
|
-
systemPrompt: string;
|
|
9
|
-
}
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
5
|
|
|
11
6
|
import {
|
|
12
7
|
createActiveToolsCacheKey,
|
|
13
8
|
createBeforeAgentStartPromptStateKey,
|
|
14
9
|
} from "../before-agent-start-cache";
|
|
10
|
+
import type { PermissionSession } from "../permission-session";
|
|
15
11
|
import { resolveSkillPromptEntries } from "../skill-prompt-sanitizer";
|
|
16
12
|
import { sanitizeAvailableToolsSection } from "../system-prompt-sanitizer";
|
|
17
|
-
import { getToolNameFromValue } from "../tool-registry";
|
|
13
|
+
import { getToolNameFromValue, type ToolRegistry } from "../tool-registry";
|
|
18
14
|
import type { PermissionState } from "../types";
|
|
19
|
-
|
|
15
|
+
|
|
16
|
+
/** Minimal subset of BeforeAgentStartEvent used by this handler. */
|
|
17
|
+
interface BeforeAgentStartPayload {
|
|
18
|
+
systemPrompt: string;
|
|
19
|
+
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Pure helper: returns true when the tool should be exposed to the agent.
|
|
@@ -32,68 +32,81 @@ export function shouldExposeTool(
|
|
|
32
32
|
return toolPermission !== "deny";
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Handles the `before_agent_start` event: tool filtering + prompt sanitization.
|
|
37
|
+
*
|
|
38
|
+
* Constructor deps:
|
|
39
|
+
* - `session` — encapsulates all mutable session state
|
|
40
|
+
* - `toolRegistry` — Pi tool API subset (getAll + setActive)
|
|
41
|
+
*/
|
|
42
|
+
export class AgentPrepHandler {
|
|
43
|
+
constructor(
|
|
44
|
+
private readonly session: PermissionSession,
|
|
45
|
+
private readonly toolRegistry: ToolRegistry,
|
|
46
|
+
) {}
|
|
47
|
+
|
|
48
|
+
async handle(
|
|
49
|
+
event: BeforeAgentStartPayload,
|
|
50
|
+
ctx: ExtensionContext,
|
|
51
|
+
): Promise<BeforeAgentStartEventResult> {
|
|
52
|
+
const { session } = this;
|
|
53
|
+
session.activate(ctx);
|
|
54
|
+
session.refreshConfig(ctx);
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
const agentName = session.resolveAgentName(ctx, event.systemPrompt);
|
|
57
|
+
const allTools = this.toolRegistry.getAll();
|
|
58
|
+
const allowedTools: string[] = [];
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
for (const tool of allTools) {
|
|
61
|
+
const toolName = getToolNameFromValue(tool);
|
|
62
|
+
if (!toolName) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (
|
|
66
|
+
shouldExposeTool(toolName, agentName, (t, a) =>
|
|
67
|
+
session.getToolPermission(t, a),
|
|
68
|
+
)
|
|
69
|
+
) {
|
|
70
|
+
allowedTools.push(toolName);
|
|
71
|
+
}
|
|
52
72
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
allowedTools.push(toolName);
|
|
73
|
+
|
|
74
|
+
const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
|
|
75
|
+
if (session.shouldUpdateActiveTools(activeToolsCacheKey)) {
|
|
76
|
+
this.toolRegistry.setActive(allowedTools);
|
|
77
|
+
session.commitActiveToolsCacheKey(activeToolsCacheKey);
|
|
59
78
|
}
|
|
60
|
-
}
|
|
61
79
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
|
|
81
|
+
agentName,
|
|
82
|
+
cwd: ctx.cwd,
|
|
83
|
+
permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined),
|
|
84
|
+
systemPrompt: event.systemPrompt,
|
|
85
|
+
allowedToolNames: allowedTools,
|
|
86
|
+
});
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined),
|
|
72
|
-
systemPrompt: event.systemPrompt,
|
|
73
|
-
allowedToolNames: allowedTools,
|
|
74
|
-
});
|
|
88
|
+
if (!session.shouldUpdatePromptState(promptStateCacheKey)) {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
75
91
|
|
|
76
|
-
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
92
|
+
session.commitPromptStateCacheKey(promptStateCacheKey);
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
const toolPromptResult = sanitizeAvailableToolsSection(
|
|
95
|
+
event.systemPrompt,
|
|
96
|
+
allowedTools,
|
|
97
|
+
);
|
|
98
|
+
const skillPromptResult = resolveSkillPromptEntries(
|
|
99
|
+
toolPromptResult.prompt,
|
|
100
|
+
session,
|
|
101
|
+
agentName,
|
|
102
|
+
ctx.cwd,
|
|
103
|
+
);
|
|
104
|
+
session.setActiveSkillEntries(skillPromptResult.entries);
|
|
81
105
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
);
|
|
86
|
-
const skillPromptResult = resolveSkillPromptEntries(
|
|
87
|
-
toolPromptResult.prompt,
|
|
88
|
-
session,
|
|
89
|
-
agentName,
|
|
90
|
-
ctx.cwd,
|
|
91
|
-
);
|
|
92
|
-
session.setActiveSkillEntries(skillPromptResult.entries);
|
|
106
|
+
if (skillPromptResult.prompt !== event.systemPrompt) {
|
|
107
|
+
return { systemPrompt: skillPromptResult.prompt };
|
|
108
|
+
}
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
return { systemPrompt: skillPromptResult.prompt };
|
|
110
|
+
return {};
|
|
96
111
|
}
|
|
97
|
-
|
|
98
|
-
return {};
|
|
99
112
|
}
|
|
@@ -3,9 +3,9 @@ import type {
|
|
|
3
3
|
PermissionDecisionEvent,
|
|
4
4
|
PermissionDecisionResolution,
|
|
5
5
|
} from "../../permission-events";
|
|
6
|
+
import type { PromptPermissionDetails } from "../../permission-prompter";
|
|
6
7
|
import type { Rule } from "../../rule";
|
|
7
8
|
import type { PermissionCheckResult, PermissionState } from "../../types";
|
|
8
|
-
import type { PromptPermissionDetails } from "../types";
|
|
9
9
|
|
|
10
10
|
// ── Descriptor types ───────────────────────────────────────────────────────
|
|
11
11
|
|
package/src/handlers/index.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
+
export { AgentPrepHandler, shouldExposeTool } from "./before-agent-start";
|
|
2
|
+
export { SessionLifecycleHandler } from "./lifecycle";
|
|
1
3
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
handleResourcesDiscover,
|
|
8
|
-
handleSessionShutdown,
|
|
9
|
-
handleSessionStart,
|
|
10
|
-
} from "./lifecycle";
|
|
11
|
-
export { getEventInput, handleToolCall } from "./tool-call";
|
|
12
|
-
export type {
|
|
13
|
-
HandlerDeps,
|
|
14
|
-
PermissionReviewSource,
|
|
15
|
-
PromptPermissionDetails,
|
|
16
|
-
} from "./types";
|
|
4
|
+
extractSkillNameFromInput,
|
|
5
|
+
getEventInput,
|
|
6
|
+
PermissionGateHandler,
|
|
7
|
+
} from "./permission-gate-handler";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import type { PermissionSession } from "../permission-session";
|
|
3
4
|
import { PERMISSION_SYSTEM_STATUS_KEY } from "../status";
|
|
4
|
-
import type { HandlerDeps } from "./types";
|
|
5
5
|
|
|
6
6
|
/** Minimal subset of SessionStartEvent used by this handler. */
|
|
7
7
|
interface SessionStartPayload {
|
|
@@ -13,54 +13,66 @@ interface ResourcesDiscoverPayload {
|
|
|
13
13
|
reason: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Handles session lifecycle events: start, reload, and shutdown.
|
|
18
|
+
*
|
|
19
|
+
* Constructor deps:
|
|
20
|
+
* - `session` — encapsulates all mutable session state
|
|
21
|
+
* - `cleanupRpc` — unsubscribes RPC handlers on shutdown
|
|
22
|
+
*/
|
|
23
|
+
export class SessionLifecycleHandler {
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly session: PermissionSession,
|
|
26
|
+
private readonly cleanupRpc: () => void,
|
|
27
|
+
) {}
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
async handleSessionStart(
|
|
30
|
+
event: SessionStartPayload,
|
|
31
|
+
ctx: ExtensionContext,
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const { session } = this;
|
|
34
|
+
session.refreshConfig(ctx);
|
|
35
|
+
session.resetForNewSession(ctx);
|
|
36
|
+
session.logResolvedConfigPaths();
|
|
37
|
+
|
|
38
|
+
const agentName = session.resolveAgentName(ctx);
|
|
39
|
+
const policyIssues = session.getConfigIssues(agentName);
|
|
40
|
+
for (const issue of policyIssues) {
|
|
41
|
+
session.logger.warn(issue);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (event.reason === "reload") {
|
|
45
|
+
session.logger.debug("lifecycle.reload", {
|
|
46
|
+
triggeredBy: "session_start",
|
|
47
|
+
reason: event.reason,
|
|
48
|
+
cwd: ctx.cwd,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
30
51
|
}
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
async handleResourcesDiscover(
|
|
54
|
+
event: ResourcesDiscoverPayload,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
if (event.reason !== "reload") {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { session } = this;
|
|
61
|
+
session.reload();
|
|
33
62
|
session.logger.debug("lifecycle.reload", {
|
|
34
|
-
triggeredBy: "
|
|
63
|
+
triggeredBy: "resources_discover",
|
|
35
64
|
reason: event.reason,
|
|
36
|
-
cwd:
|
|
65
|
+
cwd: session.getRuntimeContext()?.cwd ?? null,
|
|
37
66
|
});
|
|
38
67
|
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function handleResourcesDiscover(
|
|
42
|
-
deps: HandlerDeps,
|
|
43
|
-
event: ResourcesDiscoverPayload,
|
|
44
|
-
): Promise<void> {
|
|
45
|
-
if (event.reason !== "reload") {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const { session } = deps;
|
|
50
|
-
session.reload();
|
|
51
|
-
session.logger.debug("lifecycle.reload", {
|
|
52
|
-
triggeredBy: "resources_discover",
|
|
53
|
-
reason: event.reason,
|
|
54
|
-
cwd: session.getRuntimeContext()?.cwd ?? null,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
68
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
async handleSessionShutdown(): Promise<void> {
|
|
70
|
+
const { session } = this;
|
|
71
|
+
const ctx = session.getRuntimeContext();
|
|
72
|
+
if (ctx) {
|
|
73
|
+
ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
|
|
74
|
+
}
|
|
75
|
+
session.shutdown();
|
|
76
|
+
this.cleanupRpc();
|
|
63
77
|
}
|
|
64
|
-
session.shutdown();
|
|
65
|
-
deps.stopPermissionRpcHandlers();
|
|
66
78
|
}
|