@gotgenes/pi-permission-system 10.1.0 → 10.2.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 +7 -0
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/permission-manager.ts +69 -3
- package/src/permission-session.ts +6 -18
- package/src/runtime.ts +1 -37
- package/test/handlers/external-directory-integration.test.ts +81 -176
- package/test/handlers/gates/bash-path.test.ts +26 -44
- package/test/handlers/gates/runner.test.ts +27 -119
- package/test/handlers/tool-call.test.ts +44 -153
- package/test/helpers/gate-fixtures.ts +66 -2
- package/test/helpers/handler-fixtures.ts +83 -2
- package/test/permission-manager-unified.test.ts +159 -1
- package/test/permission-session.test.ts +67 -94
- package/test/runtime.test.ts +2 -82
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ 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
|
+
## [10.2.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.1.0...pi-permission-system-v10.2.0) (2026-06-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add PermissionManager.configureForCwd and agentDir option ([5a2d363](https://github.com/gotgenes/pi-packages/commit/5a2d3634a0b8466a5d6aa8baa170a9bf53e068fb))
|
|
14
|
+
|
|
8
15
|
## [10.1.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.0.0...pi-permission-system-v10.1.0) (2026-06-03)
|
|
9
16
|
|
|
10
17
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { SkillInputGatePipeline } from "./handlers/gates/skill-input-gate-pipeli
|
|
|
18
18
|
import { ToolCallGatePipeline } from "./handlers/gates/tool-call-gate-pipeline";
|
|
19
19
|
import { requestPermissionDecisionFromUi } from "./permission-dialog";
|
|
20
20
|
import { registerPermissionRpcHandlers } from "./permission-event-rpc";
|
|
21
|
+
import { PermissionManager } from "./permission-manager";
|
|
21
22
|
import { PermissionPrompter } from "./permission-prompter";
|
|
22
23
|
import { PermissionSession } from "./permission-session";
|
|
23
24
|
import { LocalPermissionsService } from "./permissions-service";
|
|
@@ -69,6 +70,8 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
69
70
|
|
|
70
71
|
refreshExtensionConfig(runtime);
|
|
71
72
|
|
|
73
|
+
const sessionManager = new PermissionManager({ agentDir: runtime.agentDir });
|
|
74
|
+
|
|
72
75
|
const session = new PermissionSession(
|
|
73
76
|
runtime,
|
|
74
77
|
createSessionLogger(runtime),
|
|
@@ -77,6 +80,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
77
80
|
forwarder,
|
|
78
81
|
subagentRegistry,
|
|
79
82
|
),
|
|
83
|
+
sessionManager,
|
|
80
84
|
{
|
|
81
85
|
refreshExtensionConfig: (ctx) => refreshExtensionConfig(runtime, ctx),
|
|
82
86
|
logResolvedConfigPaths: () => logResolvedConfigPaths(runtime),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { isPermissionState } from "./common";
|
|
3
|
+
import { getGlobalConfigPath, getProjectConfigPath } from "./config-paths";
|
|
2
4
|
import { normalizeInput } from "./input-normalizer";
|
|
3
5
|
import { normalizeFlatConfig } from "./normalize";
|
|
4
6
|
import {
|
|
@@ -48,19 +50,66 @@ type ResolvedPermissions = {
|
|
|
48
50
|
composedRules: Ruleset;
|
|
49
51
|
};
|
|
50
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Narrow interface for session-scoped permission checking.
|
|
55
|
+
* `PermissionSession` depends on this — not the full concrete class — so
|
|
56
|
+
* test mocks can satisfy it without an `as unknown as PermissionManager` cast.
|
|
57
|
+
*/
|
|
58
|
+
export interface ScopedPermissionManager {
|
|
59
|
+
configureForCwd(cwd: string | undefined | null): void;
|
|
60
|
+
checkPermission(
|
|
61
|
+
toolName: string,
|
|
62
|
+
input: unknown,
|
|
63
|
+
agentName?: string,
|
|
64
|
+
sessionRules?: Ruleset,
|
|
65
|
+
): PermissionCheckResult;
|
|
66
|
+
getToolPermission(toolName: string, agentName?: string): PermissionState;
|
|
67
|
+
getConfigIssues(agentName?: string): string[];
|
|
68
|
+
getPolicyCacheStamp(agentName?: string): string;
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
export interface PermissionManagerOptions extends PolicyLoaderOptions {
|
|
52
72
|
policyLoader?: PolicyLoader;
|
|
73
|
+
/**
|
|
74
|
+
* Pi agent directory. When provided, the manager derives all loader paths
|
|
75
|
+
* from this value and supports {@link PermissionManager.configureForCwd}.
|
|
76
|
+
*/
|
|
77
|
+
agentDir?: string;
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
export class PermissionManager {
|
|
56
|
-
private readonly
|
|
80
|
+
export class PermissionManager implements ScopedPermissionManager {
|
|
81
|
+
private readonly agentDir: string | undefined;
|
|
82
|
+
private loader: PolicyLoader;
|
|
57
83
|
private readonly resolvedPermissionsCache = new Map<
|
|
58
84
|
string,
|
|
59
85
|
FileCacheEntry<ResolvedPermissions>
|
|
60
86
|
>();
|
|
61
87
|
|
|
62
88
|
constructor(options: PermissionManagerOptions = {}) {
|
|
63
|
-
this.
|
|
89
|
+
this.agentDir = options.agentDir;
|
|
90
|
+
this.loader =
|
|
91
|
+
options.policyLoader ??
|
|
92
|
+
new FilePolicyLoader(
|
|
93
|
+
options.agentDir !== undefined
|
|
94
|
+
? derivePolicyLoaderOptions(options.agentDir, undefined)
|
|
95
|
+
: options,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Rebuild the policy loader for a new working directory and clear the
|
|
101
|
+
* resolved-permissions cache.
|
|
102
|
+
*
|
|
103
|
+
* When `agentDir` was not provided at construction (e.g. test managers
|
|
104
|
+
* built with explicit paths), only the cache is cleared.
|
|
105
|
+
*/
|
|
106
|
+
configureForCwd(cwd: string | undefined | null): void {
|
|
107
|
+
if (this.agentDir !== undefined) {
|
|
108
|
+
this.loader = new FilePolicyLoader(
|
|
109
|
+
derivePolicyLoaderOptions(this.agentDir, cwd),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
this.resolvedPermissionsCache.clear();
|
|
64
113
|
}
|
|
65
114
|
|
|
66
115
|
getConfigIssues(agentName?: string): string[] {
|
|
@@ -219,6 +268,23 @@ export class PermissionManager {
|
|
|
219
268
|
}
|
|
220
269
|
}
|
|
221
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Derive `PolicyLoaderOptions` from an agentDir + an optional cwd.
|
|
273
|
+
* Setting agentsDir explicitly from agentDir removes the hidden
|
|
274
|
+
* `getAgentDir()` env-read that FilePolicyLoader's default would perform.
|
|
275
|
+
*/
|
|
276
|
+
function derivePolicyLoaderOptions(
|
|
277
|
+
agentDir: string,
|
|
278
|
+
cwd: string | undefined | null,
|
|
279
|
+
): PolicyLoaderOptions {
|
|
280
|
+
return {
|
|
281
|
+
globalConfigPath: getGlobalConfigPath(agentDir),
|
|
282
|
+
agentsDir: join(agentDir, "agents"),
|
|
283
|
+
projectGlobalConfigPath: cwd ? getProjectConfigPath(cwd) : undefined,
|
|
284
|
+
projectAgentsDir: cwd ? join(cwd, ".pi", "agent", "agents") : undefined,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
222
288
|
/**
|
|
223
289
|
* Map a matched rule + tool name to the correct PermissionCheckResult.source.
|
|
224
290
|
*
|
|
@@ -11,11 +11,10 @@ import type { ForwardingController } from "./forwarding-manager";
|
|
|
11
11
|
import type { GateHandlerSession } from "./gate-handler-session";
|
|
12
12
|
import type { GatePrompter } from "./gate-prompter";
|
|
13
13
|
import type { PermissionPromptDecision } from "./permission-dialog";
|
|
14
|
-
import type {
|
|
14
|
+
import type { ScopedPermissionManager } from "./permission-manager";
|
|
15
15
|
import type { PromptPermissionDetails } from "./permission-prompter";
|
|
16
16
|
import type { PermissionResolver } from "./permission-resolver";
|
|
17
17
|
import type { Rule } from "./rule";
|
|
18
|
-
import { createPermissionManagerForCwd } from "./runtime";
|
|
19
18
|
import type { SessionApproval } from "./session-approval";
|
|
20
19
|
import type { SessionApprovalRecorder } from "./session-approval-recorder";
|
|
21
20
|
import type { SessionLifecycleSession } from "./session-lifecycle-session";
|
|
@@ -74,7 +73,6 @@ export class PermissionSession
|
|
|
74
73
|
SessionLifecycleSession
|
|
75
74
|
{
|
|
76
75
|
private context: ExtensionContext | null = null;
|
|
77
|
-
private permissionManager: PermissionManager;
|
|
78
76
|
private readonly sessionRules = new SessionRules();
|
|
79
77
|
private skillEntries: SkillPromptEntry[] = [];
|
|
80
78
|
private knownAgentName: string | null = null;
|
|
@@ -85,13 +83,9 @@ export class PermissionSession
|
|
|
85
83
|
private readonly paths: ExtensionPaths,
|
|
86
84
|
readonly logger: SessionLogger,
|
|
87
85
|
private readonly forwarding: ForwardingController,
|
|
86
|
+
private readonly permissionManager: ScopedPermissionManager,
|
|
88
87
|
private readonly runtimeDeps: PermissionSessionRuntimeDeps,
|
|
89
|
-
) {
|
|
90
|
-
this.permissionManager = createPermissionManagerForCwd(
|
|
91
|
-
paths.agentDir,
|
|
92
|
-
undefined,
|
|
93
|
-
);
|
|
94
|
-
}
|
|
88
|
+
) {}
|
|
95
89
|
|
|
96
90
|
// ── Context lifecycle ──────────────────────────────────────────────────
|
|
97
91
|
|
|
@@ -173,14 +167,11 @@ export class PermissionSession
|
|
|
173
167
|
/**
|
|
174
168
|
* Reset all mutable state for a new session.
|
|
175
169
|
*
|
|
176
|
-
*
|
|
170
|
+
* Configures the injected PermissionManager for `ctx.cwd`, clears caches,
|
|
177
171
|
* skill entries, and activates the new context.
|
|
178
172
|
*/
|
|
179
173
|
resetForNewSession(ctx: ExtensionContext): void {
|
|
180
|
-
this.permissionManager
|
|
181
|
-
this.paths.agentDir,
|
|
182
|
-
ctx.cwd,
|
|
183
|
-
);
|
|
174
|
+
this.permissionManager.configureForCwd(ctx.cwd);
|
|
184
175
|
this.skillEntries = [];
|
|
185
176
|
this.toolsCacheKey = null;
|
|
186
177
|
this.promptCacheKey = null;
|
|
@@ -204,10 +195,7 @@ export class PermissionSession
|
|
|
204
195
|
* Used on config reload (e.g. `resources_discover` with reason "reload").
|
|
205
196
|
*/
|
|
206
197
|
reload(): void {
|
|
207
|
-
this.permissionManager
|
|
208
|
-
this.paths.agentDir,
|
|
209
|
-
this.context?.cwd,
|
|
210
|
-
);
|
|
198
|
+
this.permissionManager.configureForCwd(this.context?.cwd);
|
|
211
199
|
this.skillEntries = [];
|
|
212
200
|
this.toolsCacheKey = null;
|
|
213
201
|
this.promptCacheKey = null;
|
package/src/runtime.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
getLegacyExtensionConfigPath,
|
|
20
20
|
getLegacyGlobalPolicyPath,
|
|
21
21
|
getLegacyProjectPolicyPath,
|
|
22
|
-
getProjectConfigPath,
|
|
23
22
|
REVIEW_LOG_FILENAME,
|
|
24
23
|
} from "./config-paths";
|
|
25
24
|
import { buildResolvedConfigLogEntry } from "./config-reporter";
|
|
@@ -77,41 +76,6 @@ export interface ExtensionRuntime extends ExtensionPaths, SessionState {
|
|
|
77
76
|
writeReviewLog(event: string, details?: Record<string, unknown>): void;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
// ── Pure helpers ───────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Derive Pi project-level config and agents paths from a working directory.
|
|
84
|
-
* Returns null when cwd is absent (headless / global-only config).
|
|
85
|
-
*/
|
|
86
|
-
export function derivePiProjectPaths(cwd: string | undefined | null): {
|
|
87
|
-
projectGlobalConfigPath: string;
|
|
88
|
-
projectAgentsDir: string;
|
|
89
|
-
} | null {
|
|
90
|
-
if (!cwd) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
projectGlobalConfigPath: getProjectConfigPath(cwd),
|
|
95
|
-
projectAgentsDir: join(cwd, ".pi", "agent", "agents"),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create a new PermissionManager scoped to a working directory's config hierarchy.
|
|
101
|
-
* Pass `cwd` as null/undefined to use global config only.
|
|
102
|
-
*/
|
|
103
|
-
export function createPermissionManagerForCwd(
|
|
104
|
-
agentDir: string,
|
|
105
|
-
cwd: string | undefined | null,
|
|
106
|
-
): PermissionManager {
|
|
107
|
-
const projectPaths = derivePiProjectPaths(cwd);
|
|
108
|
-
return new PermissionManager({
|
|
109
|
-
globalConfigPath: getGlobalConfigPath(agentDir),
|
|
110
|
-
projectGlobalConfigPath: projectPaths?.projectGlobalConfigPath,
|
|
111
|
-
projectAgentsDir: projectPaths?.projectAgentsDir,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
79
|
/**
|
|
116
80
|
* Reload merged config from disk into the runtime.
|
|
117
81
|
* If `ctx` is provided, updates `runtime.runtimeContext` first.
|
|
@@ -261,7 +225,7 @@ export function createExtensionRuntime(options?: {
|
|
|
261
225
|
...paths,
|
|
262
226
|
config: { ...DEFAULT_EXTENSION_CONFIG },
|
|
263
227
|
runtimeContext: null,
|
|
264
|
-
permissionManager:
|
|
228
|
+
permissionManager: new PermissionManager({ agentDir }),
|
|
265
229
|
activeSkillEntries: [],
|
|
266
230
|
lastKnownActiveAgentName: null,
|
|
267
231
|
lastActiveToolsCacheKey: null,
|