@gotgenes/pi-permission-system 7.3.0 → 7.3.2
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 +19 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/extension-config.ts +1 -1
- package/src/handlers/before-agent-start.ts +11 -12
- package/src/handlers/gates/bash-path-extractor.ts +0 -8
- package/src/handlers/lifecycle.ts +12 -15
- package/src/handlers/permission-gate-handler.ts +18 -20
- package/src/permission-dialog.ts +0 -6
- package/src/permission-manager.ts +1 -1
- package/src/permission-merge.ts +1 -1
- package/src/skill-prompt-sanitizer.ts +0 -27
- package/src/types.ts +0 -11
- package/test/handlers/before-agent-start.test.ts +0 -1
- package/test/handlers/gates/bash-external-directory.test.ts +17 -12
- package/test/handlers/gates/skill-read.test.ts +0 -2
- package/test/handlers/input-events.test.ts +0 -1
- package/test/handlers/input.test.ts +0 -1
- package/test/handlers/tool-call-events.test.ts +1 -1
- package/test/handlers/tool-call.test.ts +1 -1
- package/test/permission-events.test.ts +1 -1
- package/test/permission-session.test.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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
|
+
## [7.3.2](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.3.1...pi-permission-system-v7.3.2) (2026-05-27)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* replace \n with <br/> in Mermaid node labels ([3312a45](https://github.com/gotgenes/pi-packages/commit/3312a4559100cf9ae923f67819653b5a99fceb12))
|
|
14
|
+
|
|
15
|
+
## [7.3.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.3.0...pi-permission-system-v7.3.1) (2026-05-26)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* resolve pre-existing lint errors in pi-autoformat and pi-permission-system ([68fd516](https://github.com/gotgenes/pi-packages/commit/68fd516e33ddbb9a5e37ef19e949ee9ecdc37252))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
* update subagent integration docs for native permission bridge ([#101](https://github.com/gotgenes/pi-packages/issues/101)) ([0bd456b](https://github.com/gotgenes/pi-packages/commit/0bd456befa8ea6918e74f4393d844868795edc77))
|
|
26
|
+
|
|
8
27
|
## [7.3.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.2.0...pi-permission-system-v7.3.0) (2026-05-25)
|
|
9
28
|
|
|
10
29
|
|
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin
|
|
|
20
20
|
- **Protects sensitive file patterns** — cross-cutting `path` rules deny `.env`, `~/.ssh/*`, etc. across all tools and bash at once
|
|
21
21
|
- **Guards external paths** — prompts before file tools or bash commands reach outside `cwd`
|
|
22
22
|
- **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
|
|
23
|
+
- **Native [`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) integration** — in-process child sessions register with the permission system automatically, enabling per-agent policy enforcement and `ask`-state forwarding to the parent UI without configuration
|
|
23
24
|
|
|
24
25
|
## Install
|
|
25
26
|
|
package/package.json
CHANGED
package/src/extension-config.ts
CHANGED
|
@@ -50,11 +50,10 @@ export class AgentPrepHandler {
|
|
|
50
50
|
event: BeforeAgentStartPayload,
|
|
51
51
|
ctx: ExtensionContext,
|
|
52
52
|
): Promise<BeforeAgentStartEventResult> {
|
|
53
|
-
|
|
54
|
-
session.
|
|
55
|
-
session.refreshConfig(ctx);
|
|
53
|
+
this.session.activate(ctx);
|
|
54
|
+
this.session.refreshConfig(ctx);
|
|
56
55
|
|
|
57
|
-
const agentName = session.resolveAgentName(ctx, event.systemPrompt);
|
|
56
|
+
const agentName = this.session.resolveAgentName(ctx, event.systemPrompt);
|
|
58
57
|
const allTools = this.toolRegistry.getAll();
|
|
59
58
|
const allowedTools: string[] = [];
|
|
60
59
|
|
|
@@ -65,7 +64,7 @@ export class AgentPrepHandler {
|
|
|
65
64
|
}
|
|
66
65
|
if (
|
|
67
66
|
shouldExposeTool(toolName, agentName, (t, a) =>
|
|
68
|
-
session.getToolPermission(t, a),
|
|
67
|
+
this.session.getToolPermission(t, a),
|
|
69
68
|
)
|
|
70
69
|
) {
|
|
71
70
|
allowedTools.push(toolName);
|
|
@@ -73,24 +72,24 @@ export class AgentPrepHandler {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
|
|
76
|
-
if (session.shouldUpdateActiveTools(activeToolsCacheKey)) {
|
|
75
|
+
if (this.session.shouldUpdateActiveTools(activeToolsCacheKey)) {
|
|
77
76
|
this.toolRegistry.setActive(allowedTools);
|
|
78
|
-
session.commitActiveToolsCacheKey(activeToolsCacheKey);
|
|
77
|
+
this.session.commitActiveToolsCacheKey(activeToolsCacheKey);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
|
|
82
81
|
agentName,
|
|
83
82
|
cwd: ctx.cwd,
|
|
84
|
-
permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined),
|
|
83
|
+
permissionStamp: this.session.getPolicyCacheStamp(agentName ?? undefined),
|
|
85
84
|
systemPrompt: event.systemPrompt,
|
|
86
85
|
allowedToolNames: allowedTools,
|
|
87
86
|
});
|
|
88
87
|
|
|
89
|
-
if (!session.shouldUpdatePromptState(promptStateCacheKey)) {
|
|
88
|
+
if (!this.session.shouldUpdatePromptState(promptStateCacheKey)) {
|
|
90
89
|
return {};
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
session.commitPromptStateCacheKey(promptStateCacheKey);
|
|
92
|
+
this.session.commitPromptStateCacheKey(promptStateCacheKey);
|
|
94
93
|
|
|
95
94
|
const toolPromptResult = sanitizeAvailableToolsSection(
|
|
96
95
|
event.systemPrompt,
|
|
@@ -98,11 +97,11 @@ export class AgentPrepHandler {
|
|
|
98
97
|
);
|
|
99
98
|
const skillPromptResult = resolveSkillPromptEntries(
|
|
100
99
|
toolPromptResult.prompt,
|
|
101
|
-
session,
|
|
100
|
+
this.session,
|
|
102
101
|
agentName,
|
|
103
102
|
ctx.cwd,
|
|
104
103
|
);
|
|
105
|
-
session.setActiveSkillEntries(skillPromptResult.entries);
|
|
104
|
+
this.session.setActiveSkillEntries(skillPromptResult.entries);
|
|
106
105
|
|
|
107
106
|
if (skillPromptResult.prompt !== event.systemPrompt) {
|
|
108
107
|
return { systemPrompt: skillPromptResult.prompt };
|
|
@@ -48,14 +48,6 @@ function getParser(): Promise<TSParser> {
|
|
|
48
48
|
return parserPromise;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
* Reset the cached parser promise. Only used by tests to avoid
|
|
53
|
-
* cross-test pollution or to inject a mock parser.
|
|
54
|
-
*/
|
|
55
|
-
function resetParserForTesting(): void {
|
|
56
|
-
parserPromise = null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
51
|
// ── AST walker ─────────────────────────────────────────────────────────────
|
|
60
52
|
|
|
61
53
|
/**
|
|
@@ -30,19 +30,18 @@ export class SessionLifecycleHandler {
|
|
|
30
30
|
event: SessionStartPayload,
|
|
31
31
|
ctx: ExtensionContext,
|
|
32
32
|
): Promise<void> {
|
|
33
|
-
|
|
34
|
-
session.
|
|
35
|
-
session.
|
|
36
|
-
session.logResolvedConfigPaths();
|
|
33
|
+
this.session.refreshConfig(ctx);
|
|
34
|
+
this.session.resetForNewSession(ctx);
|
|
35
|
+
this.session.logResolvedConfigPaths();
|
|
37
36
|
|
|
38
|
-
const agentName = session.resolveAgentName(ctx);
|
|
39
|
-
const policyIssues = session.getConfigIssues(agentName ?? undefined);
|
|
37
|
+
const agentName = this.session.resolveAgentName(ctx);
|
|
38
|
+
const policyIssues = this.session.getConfigIssues(agentName ?? undefined);
|
|
40
39
|
for (const issue of policyIssues) {
|
|
41
|
-
session.logger.warn(issue);
|
|
40
|
+
this.session.logger.warn(issue);
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
if (event.reason === "reload") {
|
|
45
|
-
session.logger.debug("lifecycle.reload", {
|
|
44
|
+
this.session.logger.debug("lifecycle.reload", {
|
|
46
45
|
triggeredBy: "session_start",
|
|
47
46
|
reason: event.reason,
|
|
48
47
|
cwd: ctx.cwd,
|
|
@@ -56,23 +55,21 @@ export class SessionLifecycleHandler {
|
|
|
56
55
|
return Promise.resolve();
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
session.reload
|
|
61
|
-
session.logger.debug("lifecycle.reload", {
|
|
58
|
+
this.session.reload();
|
|
59
|
+
this.session.logger.debug("lifecycle.reload", {
|
|
62
60
|
triggeredBy: "resources_discover",
|
|
63
61
|
reason: event.reason,
|
|
64
|
-
cwd: session.getRuntimeContext()?.cwd ?? null,
|
|
62
|
+
cwd: this.session.getRuntimeContext()?.cwd ?? null,
|
|
65
63
|
});
|
|
66
64
|
return Promise.resolve();
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
handleSessionShutdown(): Promise<void> {
|
|
70
|
-
const
|
|
71
|
-
const ctx = session.getRuntimeContext();
|
|
68
|
+
const ctx = this.session.getRuntimeContext();
|
|
72
69
|
if (ctx) {
|
|
73
70
|
ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
|
|
74
71
|
}
|
|
75
|
-
session.shutdown();
|
|
72
|
+
this.session.shutdown();
|
|
76
73
|
this.cleanupRpc();
|
|
77
74
|
return Promise.resolve();
|
|
78
75
|
}
|
|
@@ -56,10 +56,9 @@ export class PermissionGateHandler {
|
|
|
56
56
|
event: unknown,
|
|
57
57
|
ctx: ExtensionContext,
|
|
58
58
|
): Promise<{ block?: true; reason?: string }> {
|
|
59
|
-
|
|
60
|
-
session.activate(ctx);
|
|
59
|
+
this.session.activate(ctx);
|
|
61
60
|
|
|
62
|
-
const agentName = session.resolveAgentName(ctx);
|
|
61
|
+
const agentName = this.session.resolveAgentName(ctx);
|
|
63
62
|
const toolName = getToolNameFromValue(event);
|
|
64
63
|
|
|
65
64
|
if (!toolName) {
|
|
@@ -99,22 +98,22 @@ export class PermissionGateHandler {
|
|
|
99
98
|
};
|
|
100
99
|
|
|
101
100
|
// ── Shared gate adapter closures ─────────────────────────────────────
|
|
102
|
-
const canConfirm = () => session.canPrompt(ctx);
|
|
101
|
+
const canConfirm = () => this.session.canPrompt(ctx);
|
|
103
102
|
const promptPermission = (details: PromptPermissionDetails) =>
|
|
104
|
-
session.prompt(ctx, details);
|
|
103
|
+
this.session.prompt(ctx, details);
|
|
105
104
|
const emitDecision: GateRunnerDeps["emitDecision"] = (e) =>
|
|
106
105
|
emitDecisionEvent(this.events, e);
|
|
107
106
|
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger.review is a plain function closure; no this-binding issue
|
|
108
|
-
const writeReviewLog = session.logger.review;
|
|
107
|
+
const writeReviewLog = this.session.logger.review;
|
|
109
108
|
const checkPermission: GateRunnerDeps["checkPermission"] = (
|
|
110
109
|
surface,
|
|
111
110
|
input,
|
|
112
111
|
agent,
|
|
113
112
|
sessionRules,
|
|
114
|
-
) => session.checkPermission(surface, input, agent, sessionRules);
|
|
115
|
-
const getSessionRuleset = () => session.getSessionRuleset();
|
|
113
|
+
) => this.session.checkPermission(surface, input, agent, sessionRules);
|
|
114
|
+
const getSessionRuleset = () => this.session.getSessionRuleset();
|
|
116
115
|
const approveSessionRule = (surface: string, pattern: string) =>
|
|
117
|
-
session.approveSessionRule(surface, pattern);
|
|
116
|
+
this.session.approveSessionRule(surface, pattern);
|
|
118
117
|
|
|
119
118
|
// ── Shared runner deps (built once, reused for all gates) ────────────
|
|
120
119
|
const runnerDeps: GateRunnerDeps = {
|
|
@@ -129,7 +128,7 @@ export class PermissionGateHandler {
|
|
|
129
128
|
|
|
130
129
|
// ── Skill-read gate (descriptor + runner) ───────────────────────────────
|
|
131
130
|
const skillDescriptor = describeSkillReadGate(tcc, () =>
|
|
132
|
-
session.getActiveSkillEntries(),
|
|
131
|
+
this.session.getActiveSkillEntries(),
|
|
133
132
|
);
|
|
134
133
|
if (skillDescriptor) {
|
|
135
134
|
const skillResult = await runGateCheck(
|
|
@@ -165,8 +164,8 @@ export class PermissionGateHandler {
|
|
|
165
164
|
|
|
166
165
|
// ── External-directory gate (descriptor + runner) ────────────────────────
|
|
167
166
|
const infraDirs = [
|
|
168
|
-
...session.getInfrastructureDirs(),
|
|
169
|
-
...session.getInfrastructureReadPaths(),
|
|
167
|
+
...this.session.getInfrastructureDirs(),
|
|
168
|
+
...this.session.getInfrastructureReadPaths(),
|
|
170
169
|
];
|
|
171
170
|
const extDirDesc = describeExternalDirectoryGate(tcc, infraDirs);
|
|
172
171
|
if (extDirDesc) {
|
|
@@ -264,16 +263,15 @@ export class PermissionGateHandler {
|
|
|
264
263
|
event: InputPayload,
|
|
265
264
|
ctx: ExtensionContext,
|
|
266
265
|
): Promise<InputEventResult> {
|
|
267
|
-
|
|
268
|
-
session.activate(ctx);
|
|
266
|
+
this.session.activate(ctx);
|
|
269
267
|
|
|
270
268
|
const skillName = extractSkillNameFromInput(event.text);
|
|
271
269
|
if (!skillName) {
|
|
272
270
|
return { action: "continue" };
|
|
273
271
|
}
|
|
274
272
|
|
|
275
|
-
const agentName = session.resolveAgentName(ctx);
|
|
276
|
-
const check = session.checkPermission(
|
|
273
|
+
const agentName = this.session.resolveAgentName(ctx);
|
|
274
|
+
const check = this.session.checkPermission(
|
|
277
275
|
"skill",
|
|
278
276
|
{ name: skillName },
|
|
279
277
|
agentName ?? undefined,
|
|
@@ -290,14 +288,14 @@ export class PermissionGateHandler {
|
|
|
290
288
|
skillName,
|
|
291
289
|
agentName ?? undefined,
|
|
292
290
|
);
|
|
293
|
-
const skillInputCanConfirm = session.canPrompt(ctx);
|
|
291
|
+
const skillInputCanConfirm = this.session.canPrompt(ctx);
|
|
294
292
|
let skillInputAutoApproved = false;
|
|
295
293
|
const skillInputGate = await applyPermissionGate({
|
|
296
294
|
state: check.state,
|
|
297
295
|
canConfirm: skillInputCanConfirm,
|
|
298
296
|
promptForApproval: async () => {
|
|
299
|
-
const decision = await session.prompt(ctx, {
|
|
300
|
-
requestId: session.createPermissionRequestId("skill-input"),
|
|
297
|
+
const decision = await this.session.prompt(ctx, {
|
|
298
|
+
requestId: this.session.createPermissionRequestId("skill-input"),
|
|
301
299
|
source: "skill_input",
|
|
302
300
|
agentName,
|
|
303
301
|
message: skillInputMessage,
|
|
@@ -307,7 +305,7 @@ export class PermissionGateHandler {
|
|
|
307
305
|
return decision;
|
|
308
306
|
},
|
|
309
307
|
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger.review is a plain function closure; no this-binding issue
|
|
310
|
-
writeLog: session.logger.review,
|
|
308
|
+
writeLog: this.session.logger.review,
|
|
311
309
|
logContext: {
|
|
312
310
|
source: "skill_input",
|
|
313
311
|
skillName,
|
package/src/permission-dialog.ts
CHANGED
|
@@ -25,12 +25,6 @@ const APPROVE_OPTION = "Yes";
|
|
|
25
25
|
const APPROVE_FOR_SESSION_OPTION = "Yes, for this session";
|
|
26
26
|
const DENY_OPTION = "No";
|
|
27
27
|
const DENY_WITH_REASON_OPTION = "No, provide reason";
|
|
28
|
-
const PERMISSION_DECISION_OPTIONS = [
|
|
29
|
-
APPROVE_OPTION,
|
|
30
|
-
APPROVE_FOR_SESSION_OPTION,
|
|
31
|
-
DENY_OPTION,
|
|
32
|
-
DENY_WITH_REASON_OPTION,
|
|
33
|
-
] as const;
|
|
34
28
|
|
|
35
29
|
export function normalizePermissionDenialReason(
|
|
36
30
|
value: unknown,
|
|
@@ -120,7 +120,7 @@ export class PermissionManager {
|
|
|
120
120
|
// existing patterns from lower scopes keep their earlier origin.
|
|
121
121
|
if (!origins.has(surface)) origins.set(surface, new Map());
|
|
122
122
|
for (const pattern of Object.keys(value)) {
|
|
123
|
-
origins.get(surface)
|
|
123
|
+
origins.get(surface)?.set(pattern, scopeName);
|
|
124
124
|
}
|
|
125
125
|
} else {
|
|
126
126
|
// Full replacement: this scope takes over the entire surface entry.
|
package/src/permission-merge.ts
CHANGED
|
@@ -92,33 +92,6 @@ function parseSkillEntries(sectionBody: string): ParsedSkillPromptEntry[] {
|
|
|
92
92
|
return entries;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function parseSkillPromptSection(prompt: string): SkillPromptSection | null {
|
|
96
|
-
const start = prompt.indexOf(AVAILABLE_SKILLS_OPEN_TAG);
|
|
97
|
-
if (start === -1) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const closeStart = prompt.indexOf(
|
|
102
|
-
AVAILABLE_SKILLS_CLOSE_TAG,
|
|
103
|
-
start + AVAILABLE_SKILLS_OPEN_TAG.length,
|
|
104
|
-
);
|
|
105
|
-
if (closeStart === -1) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const end = closeStart + AVAILABLE_SKILLS_CLOSE_TAG.length;
|
|
110
|
-
const sectionBody = prompt.slice(
|
|
111
|
-
start + AVAILABLE_SKILLS_OPEN_TAG.length,
|
|
112
|
-
closeStart,
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
start,
|
|
117
|
-
end,
|
|
118
|
-
entries: parseSkillEntries(sectionBody),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
95
|
export function parseAllSkillPromptSections(
|
|
123
96
|
prompt: string,
|
|
124
97
|
): SkillPromptSection[] {
|
package/src/types.ts
CHANGED
|
@@ -14,17 +14,6 @@ export type FlatPermissionConfig = Record<
|
|
|
14
14
|
PermissionState | Record<string, PermissionState>
|
|
15
15
|
>;
|
|
16
16
|
|
|
17
|
-
type BuiltInToolName =
|
|
18
|
-
| "bash"
|
|
19
|
-
| "read"
|
|
20
|
-
| "write"
|
|
21
|
-
| "edit"
|
|
22
|
-
| "grep"
|
|
23
|
-
| "find"
|
|
24
|
-
| "ls";
|
|
25
|
-
|
|
26
|
-
type SpecialPermissionName = "external_directory";
|
|
27
|
-
|
|
28
17
|
/**
|
|
29
18
|
* Per-scope permission config shape after loading and validation.
|
|
30
19
|
* Holds only the flat permission map — all policy is expressed there.
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "#src/handlers/before-agent-start";
|
|
8
8
|
import type { PermissionSession } from "#src/permission-session";
|
|
9
9
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
10
|
-
import type { PermissionState } from "#src/types";
|
|
11
10
|
|
|
12
11
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
13
12
|
vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
@@ -101,12 +101,15 @@ describe("describeBashExternalDirectoryGate", () => {
|
|
|
101
101
|
it("uses config-level checkPermission for the policy state", async () => {
|
|
102
102
|
const checkPermission = vi
|
|
103
103
|
.fn()
|
|
104
|
-
.mockImplementation(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
.mockImplementation(
|
|
105
|
+
(_surface: string, input: Record<string, unknown>) => {
|
|
106
|
+
// Path-specific check returns session for coverage filtering
|
|
107
|
+
if (input.path)
|
|
108
|
+
return makeCheckResult("allow", { source: "special" });
|
|
109
|
+
// Config-level check (no path) returns deny
|
|
110
|
+
return makeCheckResult("deny");
|
|
111
|
+
},
|
|
112
|
+
);
|
|
110
113
|
const result = await describeBashExternalDirectoryGate(
|
|
111
114
|
makeTcc(),
|
|
112
115
|
checkPermission,
|
|
@@ -172,12 +175,14 @@ describe("describeBashExternalDirectoryGate", () => {
|
|
|
172
175
|
it("only includes uncovered paths when some are session-covered", async () => {
|
|
173
176
|
const checkPermission = vi
|
|
174
177
|
.fn()
|
|
175
|
-
.mockImplementation(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
.mockImplementation(
|
|
179
|
+
(_surface: string, input: Record<string, unknown>) => {
|
|
180
|
+
if (input.path === "/outside/a.ts") {
|
|
181
|
+
return makeCheckResult("allow", { source: "session" });
|
|
182
|
+
}
|
|
183
|
+
return makeCheckResult("ask");
|
|
184
|
+
},
|
|
185
|
+
);
|
|
181
186
|
const result = await describeBashExternalDirectoryGate(
|
|
182
187
|
makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
|
|
183
188
|
checkPermission,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import type { GateDescriptor } from "#src/handlers/gates/descriptor";
|
|
4
2
|
import { describeSkillReadGate } from "#src/handlers/gates/skill-read";
|
|
5
3
|
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
6
4
|
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
@@ -9,7 +9,6 @@ import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
|
9
9
|
import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
|
|
10
10
|
import type { PermissionSession } from "#src/permission-session";
|
|
11
11
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
12
|
-
import type { PermissionState } from "#src/types";
|
|
13
12
|
|
|
14
13
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
15
14
|
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "#src/handlers/permission-gate-handler";
|
|
8
8
|
import type { PermissionSession } from "#src/permission-session";
|
|
9
9
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
10
|
-
import type { PermissionState } from "#src/types";
|
|
11
10
|
|
|
12
11
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
13
12
|
|
|
@@ -10,7 +10,7 @@ import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
|
10
10
|
import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
|
|
11
11
|
import type { PermissionSession } from "#src/permission-session";
|
|
12
12
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
13
|
-
import type { PermissionCheckResult
|
|
13
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
14
14
|
|
|
15
15
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
16
16
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "#src/handlers/permission-gate-handler";
|
|
8
8
|
import type { PermissionSession } from "#src/permission-session";
|
|
9
9
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
10
|
-
import type { PermissionCheckResult
|
|
10
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
11
11
|
|
|
12
12
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
13
13
|
vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
@@ -264,7 +264,7 @@ describe("piPermissionSystemExtension ready event wiring", () => {
|
|
|
264
264
|
mkdirSync(join(baseDir, "agents"), { recursive: true });
|
|
265
265
|
writeFileSync(
|
|
266
266
|
globalConfigPath,
|
|
267
|
-
JSON.stringify({ permission: { "*": "ask" } })
|
|
267
|
+
`${JSON.stringify({ permission: { "*": "ask" } })}\n`,
|
|
268
268
|
"utf8",
|
|
269
269
|
);
|
|
270
270
|
process.env.PI_CODING_AGENT_DIR = baseDir;
|
|
@@ -38,7 +38,6 @@ import {
|
|
|
38
38
|
} from "#src/permission-session";
|
|
39
39
|
import type { SessionLogger } from "#src/session-logger";
|
|
40
40
|
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
41
|
-
import type { PermissionCheckResult } from "#src/types";
|
|
42
41
|
|
|
43
42
|
function makeSkillEntry(
|
|
44
43
|
name: string,
|