@komarspn/pi-permission-system 16.0.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 +2234 -0
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/config/config.example.json +39 -0
- package/package.json +82 -0
- package/schemas/permissions.schema.json +158 -0
- package/src/active-agent.ts +72 -0
- package/src/async-cache.ts +21 -0
- package/src/bash-arity.ts +210 -0
- package/src/builtin-tool-input-formatters.ts +82 -0
- package/src/canonicalize-path.ts +30 -0
- package/src/common.ts +121 -0
- package/src/config-loader.ts +432 -0
- package/src/config-modal.ts +259 -0
- package/src/config-paths.ts +47 -0
- package/src/config-reporter.ts +34 -0
- package/src/config-store.ts +222 -0
- package/src/decision-audit.ts +75 -0
- package/src/decision-reporter.ts +41 -0
- package/src/denial-messages.ts +232 -0
- package/src/expand-home.ts +28 -0
- package/src/extension-config.ts +79 -0
- package/src/extension-paths.ts +66 -0
- package/src/forwarded-permissions/io.ts +404 -0
- package/src/forwarded-permissions/permission-forwarder.ts +580 -0
- package/src/forwarding-manager.ts +74 -0
- package/src/gate-prompter.ts +12 -0
- package/src/handlers/before-agent-start.ts +94 -0
- package/src/handlers/gates/bash-command.ts +75 -0
- package/src/handlers/gates/bash-external-directory.ts +127 -0
- package/src/handlers/gates/bash-path-extractor.ts +15 -0
- package/src/handlers/gates/bash-path.ts +152 -0
- package/src/handlers/gates/bash-program.ts +1143 -0
- package/src/handlers/gates/bash-token-classification.ts +105 -0
- package/src/handlers/gates/candidate-check.ts +32 -0
- package/src/handlers/gates/descriptor.ts +81 -0
- package/src/handlers/gates/external-directory-messages.ts +20 -0
- package/src/handlers/gates/external-directory.ts +133 -0
- package/src/handlers/gates/helpers.ts +76 -0
- package/src/handlers/gates/path.ts +91 -0
- package/src/handlers/gates/runner.ts +186 -0
- package/src/handlers/gates/skill-input-gate-pipeline.ts +104 -0
- package/src/handlers/gates/skill-input.ts +46 -0
- package/src/handlers/gates/skill-read.ts +87 -0
- package/src/handlers/gates/tool-call-gate-pipeline.ts +129 -0
- package/src/handlers/gates/tool.ts +102 -0
- package/src/handlers/gates/types.ts +13 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/lifecycle.ts +95 -0
- package/src/handlers/permission-gate-handler.ts +190 -0
- package/src/handlers/tool-call-boundary.ts +91 -0
- package/src/index.ts +225 -0
- package/src/input-normalizer.ts +157 -0
- package/src/logging.ts +113 -0
- package/src/mcp-targets.ts +170 -0
- package/src/node-modules-discovery.ts +76 -0
- package/src/normalize.ts +43 -0
- package/src/path-utils.ts +355 -0
- package/src/pattern-suggest.ts +132 -0
- package/src/permission-dialog.ts +138 -0
- package/src/permission-event-rpc.ts +223 -0
- package/src/permission-events.ts +266 -0
- package/src/permission-forwarding.ts +188 -0
- package/src/permission-gate.ts +94 -0
- package/src/permission-manager.ts +392 -0
- package/src/permission-merge.ts +32 -0
- package/src/permission-prompter.ts +142 -0
- package/src/permission-prompts.ts +93 -0
- package/src/permission-resolver.ts +109 -0
- package/src/permission-session.ts +189 -0
- package/src/permission-ui-prompt.ts +127 -0
- package/src/permissions-service.ts +63 -0
- package/src/persistent-approval-recorder.ts +139 -0
- package/src/policy-loader.ts +350 -0
- package/src/prompting-gateway.ts +104 -0
- package/src/rule.ts +188 -0
- package/src/scope-merge.ts +72 -0
- package/src/service-lifecycle.ts +49 -0
- package/src/service.ts +163 -0
- package/src/session-approval-recorder.ts +6 -0
- package/src/session-approval.ts +43 -0
- package/src/session-logger.ts +91 -0
- package/src/session-rules.ts +79 -0
- package/src/skill-prompt-sanitizer.ts +292 -0
- package/src/status.ts +35 -0
- package/src/subagent-context.ts +104 -0
- package/src/subagent-lifecycle-events.ts +72 -0
- package/src/subagent-registry.ts +105 -0
- package/src/synthesize.ts +92 -0
- package/src/system-prompt-sanitizer.ts +274 -0
- package/src/tool-access-extractor-registry.ts +68 -0
- package/src/tool-input-formatter-registry.ts +67 -0
- package/src/tool-input-preview.ts +34 -0
- package/src/tool-input-prompt-formatters.ts +63 -0
- package/src/tool-preview-formatter.ts +207 -0
- package/src/tool-registry.ts +148 -0
- package/src/types.ts +64 -0
- package/src/wildcard-matcher.ts +120 -0
- package/src/yolo-mode.ts +30 -0
- package/test/active-agent.test.ts +155 -0
- package/test/async-cache.test.ts +48 -0
- package/test/bash-arity.test.ts +144 -0
- package/test/bash-external-directory.test.ts +956 -0
- package/test/builtin-tool-input-formatters.test.ts +109 -0
- package/test/canonicalize-path.test.ts +93 -0
- package/test/common.test.ts +287 -0
- package/test/composition-root.test.ts +603 -0
- package/test/config-loader.test.ts +740 -0
- package/test/config-modal.test.ts +320 -0
- package/test/config-paths.test.ts +83 -0
- package/test/config-pipeline.test.ts +90 -0
- package/test/config-reporter.test.ts +147 -0
- package/test/config-store.test.ts +466 -0
- package/test/decision-audit.test.ts +72 -0
- package/test/decision-reporter.test.ts +112 -0
- package/test/denial-messages.test.ts +656 -0
- package/test/detect-permissive-bash-fallback.test.ts +56 -0
- package/test/expand-home.test.ts +93 -0
- package/test/extension-config.test.ts +129 -0
- package/test/extension-paths.test.ts +108 -0
- package/test/forwarded-permissions/io.test.ts +251 -0
- package/test/forwarding-manager.test.ts +194 -0
- package/test/handlers/before-agent-start.test.ts +317 -0
- package/test/handlers/external-directory-integration.test.ts +623 -0
- package/test/handlers/external-directory-session-dedup.test.ts +430 -0
- package/test/handlers/external-directory-symlink-acceptance.test.ts +149 -0
- package/test/handlers/gates/bash-command-metamorphic.test.ts +83 -0
- package/test/handlers/gates/bash-command.test.ts +191 -0
- package/test/handlers/gates/bash-external-directory.test.ts +269 -0
- package/test/handlers/gates/bash-path.test.ts +337 -0
- package/test/handlers/gates/bash-program.test.ts +410 -0
- package/test/handlers/gates/bash-token-classification.test.ts +241 -0
- package/test/handlers/gates/candidate-check.test.ts +52 -0
- package/test/handlers/gates/external-directory-messages.test.ts +61 -0
- package/test/handlers/gates/external-directory.test.ts +259 -0
- package/test/handlers/gates/helpers.test.ts +177 -0
- package/test/handlers/gates/path.test.ts +294 -0
- package/test/handlers/gates/runner.test.ts +447 -0
- package/test/handlers/gates/skill-input-gate-pipeline.test.ts +176 -0
- package/test/handlers/gates/skill-input.test.ts +131 -0
- package/test/handlers/gates/skill-read.test.ts +158 -0
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +252 -0
- package/test/handlers/gates/tool.test.ts +223 -0
- package/test/handlers/input-events.test.ts +168 -0
- package/test/handlers/input.test.ts +199 -0
- package/test/handlers/lifecycle.test.ts +221 -0
- package/test/handlers/tool-call-boundary.test.ts +145 -0
- package/test/handlers/tool-call-events.test.ts +277 -0
- package/test/handlers/tool-call.test.ts +395 -0
- package/test/handlers/validate-requested-tool.test.ts +92 -0
- package/test/helpers/gate-fixtures.ts +323 -0
- package/test/helpers/handler-fixtures.ts +335 -0
- package/test/helpers/make-fake-pi.ts +100 -0
- package/test/helpers/manager-harness.ts +112 -0
- package/test/helpers/session-fixtures.ts +204 -0
- package/test/input-normalizer.test.ts +367 -0
- package/test/logging.test.ts +51 -0
- package/test/mcp-targets.test.ts +233 -0
- package/test/node-modules-discovery.test.ts +97 -0
- package/test/normalize.test.ts +247 -0
- package/test/path-utils.test.ts +650 -0
- package/test/pattern-suggest.test.ts +248 -0
- package/test/permission-dialog.test.ts +241 -0
- package/test/permission-event-rpc.test.ts +541 -0
- package/test/permission-events.test.ts +402 -0
- package/test/permission-forwarder.test.ts +369 -0
- package/test/permission-forwarding.test.ts +315 -0
- package/test/permission-gate.test.ts +305 -0
- package/test/permission-manager-unified.test.ts +3368 -0
- package/test/permission-merge.test.ts +61 -0
- package/test/permission-prompter.test.ts +518 -0
- package/test/permission-prompts.test.ts +363 -0
- package/test/permission-resolver.test.ts +265 -0
- package/test/permission-session.test.ts +363 -0
- package/test/permission-ui-prompt.test.ts +146 -0
- package/test/permissions-service.test.ts +177 -0
- package/test/persistent-approval-recorder.test.ts +133 -0
- package/test/pi-infrastructure-read.test.ts +369 -0
- package/test/policy-loader.test.ts +561 -0
- package/test/prompting-gateway.test.ts +230 -0
- package/test/rule.test.ts +604 -0
- package/test/scope-merge.test.ts +116 -0
- package/test/service-lifecycle.test.ts +163 -0
- package/test/service.test.ts +308 -0
- package/test/session-approval.test.ts +75 -0
- package/test/session-logger.test.ts +200 -0
- package/test/session-rules.test.ts +304 -0
- package/test/session-start.test.ts +112 -0
- package/test/skill-prompt-sanitizer.test.ts +374 -0
- package/test/status.test.ts +10 -0
- package/test/subagent-context.test.ts +326 -0
- package/test/subagent-lifecycle-events.test.ts +132 -0
- package/test/subagent-registry.test.ts +145 -0
- package/test/synthesize.test.ts +300 -0
- package/test/system-prompt-sanitizer.test.ts +382 -0
- package/test/tool-access-extractor-registry.test.ts +77 -0
- package/test/tool-input-formatter-registry.test.ts +75 -0
- package/test/tool-input-preview.test.ts +129 -0
- package/test/tool-input-prompt-formatters.test.ts +115 -0
- package/test/tool-preview-formatter.test.ts +458 -0
- package/test/tool-registry.test.ts +197 -0
- package/test/wildcard-matcher.test.ts +424 -0
- package/test/yolo-mode.test.ts +188 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for PromptingGateway.
|
|
3
|
+
*
|
|
4
|
+
* The gateway owns the stored ExtensionContext and is the sole implementation
|
|
5
|
+
* of the GatePrompter role. These tests exercise canConfirm() across all
|
|
6
|
+
* policy permutations and verify the prompt/reject contract for promptPermission().
|
|
7
|
+
*/
|
|
8
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
|
|
11
|
+
import type { ConfigReader } from "#src/config-store";
|
|
12
|
+
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
13
|
+
import type { PermissionPromptDecision } from "#src/permission-dialog";
|
|
14
|
+
import type {
|
|
15
|
+
PermissionPrompterApi,
|
|
16
|
+
PromptPermissionDetails,
|
|
17
|
+
} from "#src/permission-prompter";
|
|
18
|
+
import {
|
|
19
|
+
PromptingGateway,
|
|
20
|
+
type PromptingGatewayDeps,
|
|
21
|
+
} from "#src/prompting-gateway";
|
|
22
|
+
|
|
23
|
+
// ── Test helpers ──────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
26
|
+
return {
|
|
27
|
+
cwd: "/test/project",
|
|
28
|
+
hasUI: true,
|
|
29
|
+
ui: {
|
|
30
|
+
setStatus: vi.fn(),
|
|
31
|
+
notify: vi.fn(),
|
|
32
|
+
select: vi.fn(),
|
|
33
|
+
input: vi.fn(),
|
|
34
|
+
},
|
|
35
|
+
sessionManager: {
|
|
36
|
+
getEntries: vi.fn().mockReturnValue([]),
|
|
37
|
+
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
38
|
+
getSessionId: vi.fn().mockReturnValue(null),
|
|
39
|
+
addEntry: vi.fn(),
|
|
40
|
+
},
|
|
41
|
+
...overrides,
|
|
42
|
+
} as unknown as ExtensionContext;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeConfigReader(
|
|
46
|
+
overrides: Partial<typeof DEFAULT_EXTENSION_CONFIG> = {},
|
|
47
|
+
): ConfigReader {
|
|
48
|
+
return {
|
|
49
|
+
current: vi
|
|
50
|
+
.fn<() => typeof DEFAULT_EXTENSION_CONFIG>()
|
|
51
|
+
.mockReturnValue({ ...DEFAULT_EXTENSION_CONFIG, ...overrides }),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function makePrompterApi(): PermissionPrompterApi & {
|
|
56
|
+
prompt: ReturnType<typeof vi.fn>;
|
|
57
|
+
} {
|
|
58
|
+
return {
|
|
59
|
+
prompt: vi
|
|
60
|
+
.fn<PermissionPrompterApi["prompt"]>()
|
|
61
|
+
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function makeDetails(): PromptPermissionDetails {
|
|
66
|
+
return {
|
|
67
|
+
requestId: "req-1",
|
|
68
|
+
source: "tool_call",
|
|
69
|
+
agentName: null,
|
|
70
|
+
message: "Allow this?",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeDeps(
|
|
75
|
+
overrides: Partial<PromptingGatewayDeps> = {},
|
|
76
|
+
): PromptingGatewayDeps {
|
|
77
|
+
return {
|
|
78
|
+
config: overrides.config ?? makeConfigReader(),
|
|
79
|
+
subagentSessionsDir:
|
|
80
|
+
overrides.subagentSessionsDir ?? "/test/agent/subagent-sessions",
|
|
81
|
+
registry: overrides.registry,
|
|
82
|
+
prompter: overrides.prompter ?? makePrompterApi(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Tests ─────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
describe("PromptingGateway", () => {
|
|
89
|
+
describe("canConfirm", () => {
|
|
90
|
+
it("returns false before activate", () => {
|
|
91
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
92
|
+
expect(gateway.canConfirm()).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns true after activate when context has UI", () => {
|
|
96
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
97
|
+
gateway.activate(makeCtx({ hasUI: true }));
|
|
98
|
+
expect(gateway.canConfirm()).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns false when context has no UI, is not a subagent, and yolo mode is off", () => {
|
|
102
|
+
const gateway = new PromptingGateway(
|
|
103
|
+
makeDeps({ config: makeConfigReader({ yoloMode: false }) }),
|
|
104
|
+
);
|
|
105
|
+
gateway.activate(makeCtx({ hasUI: false }));
|
|
106
|
+
expect(gateway.canConfirm()).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns true when yolo mode is enabled (no UI, not subagent)", () => {
|
|
110
|
+
const gateway = new PromptingGateway(
|
|
111
|
+
makeDeps({ config: makeConfigReader({ yoloMode: true }) }),
|
|
112
|
+
);
|
|
113
|
+
gateway.activate(makeCtx({ hasUI: false }));
|
|
114
|
+
expect(gateway.canConfirm()).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns true when running as a subagent (env hint)", () => {
|
|
118
|
+
vi.stubEnv("PI_IS_SUBAGENT", "1");
|
|
119
|
+
const gateway = new PromptingGateway(
|
|
120
|
+
makeDeps({ config: makeConfigReader({ yoloMode: false }) }),
|
|
121
|
+
);
|
|
122
|
+
gateway.activate(makeCtx({ hasUI: false }));
|
|
123
|
+
expect(gateway.canConfirm()).toBe(true);
|
|
124
|
+
vi.unstubAllEnvs();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("returns false after deactivate", () => {
|
|
128
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
129
|
+
gateway.activate(makeCtx({ hasUI: true }));
|
|
130
|
+
gateway.deactivate();
|
|
131
|
+
expect(gateway.canConfirm()).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("returns true after re-activate following deactivate", () => {
|
|
135
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
136
|
+
gateway.activate(makeCtx({ hasUI: true }));
|
|
137
|
+
gateway.deactivate();
|
|
138
|
+
gateway.activate(makeCtx({ hasUI: true }));
|
|
139
|
+
expect(gateway.canConfirm()).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("prompt", () => {
|
|
144
|
+
it("rejects before activate", async () => {
|
|
145
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
146
|
+
await expect(gateway.prompt(makeDetails())).rejects.toThrow(
|
|
147
|
+
"prompt called before the session was activated",
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("delegates to deps.prompter.prompt with the stored context", async () => {
|
|
152
|
+
const prompter = makePrompterApi();
|
|
153
|
+
const gateway = new PromptingGateway(makeDeps({ prompter }));
|
|
154
|
+
const ctx = makeCtx();
|
|
155
|
+
gateway.activate(ctx);
|
|
156
|
+
const details = makeDetails();
|
|
157
|
+
|
|
158
|
+
const result = await gateway.prompt(details);
|
|
159
|
+
|
|
160
|
+
expect(prompter.prompt).toHaveBeenCalledWith(ctx, details);
|
|
161
|
+
expect(result).toEqual({ approved: true, state: "approved" });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("uses the most recently activated context", async () => {
|
|
165
|
+
const prompter = makePrompterApi();
|
|
166
|
+
const gateway = new PromptingGateway(makeDeps({ prompter }));
|
|
167
|
+
const firstCtx = makeCtx({ cwd: "/first" });
|
|
168
|
+
const secondCtx = makeCtx({ cwd: "/second" });
|
|
169
|
+
|
|
170
|
+
gateway.activate(firstCtx);
|
|
171
|
+
gateway.activate(secondCtx);
|
|
172
|
+
|
|
173
|
+
await gateway.prompt(makeDetails());
|
|
174
|
+
|
|
175
|
+
expect(prompter.prompt).toHaveBeenCalledWith(
|
|
176
|
+
secondCtx,
|
|
177
|
+
expect.anything(),
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("rejects after deactivate", async () => {
|
|
182
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
183
|
+
gateway.activate(makeCtx());
|
|
184
|
+
gateway.deactivate();
|
|
185
|
+
await expect(gateway.prompt(makeDetails())).rejects.toThrow(
|
|
186
|
+
"prompt called before the session was activated",
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("returns the prompter decision", async () => {
|
|
191
|
+
const decision: PermissionPromptDecision = {
|
|
192
|
+
approved: false,
|
|
193
|
+
state: "denied",
|
|
194
|
+
denialReason: "user declined",
|
|
195
|
+
};
|
|
196
|
+
const prompter = makePrompterApi();
|
|
197
|
+
prompter.prompt.mockResolvedValue(decision);
|
|
198
|
+
const gateway = new PromptingGateway(makeDeps({ prompter }));
|
|
199
|
+
gateway.activate(makeCtx());
|
|
200
|
+
|
|
201
|
+
const result = await gateway.prompt(makeDetails());
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual(decision);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("lifecycle", () => {
|
|
208
|
+
afterEach(() => {
|
|
209
|
+
vi.unstubAllEnvs();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("activate then deactivate clears the stored context", () => {
|
|
213
|
+
const gateway = new PromptingGateway(makeDeps());
|
|
214
|
+
gateway.activate(makeCtx());
|
|
215
|
+
gateway.deactivate();
|
|
216
|
+
expect(gateway.canConfirm()).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("multiple activate calls update the stored context", () => {
|
|
220
|
+
const prompter = makePrompterApi();
|
|
221
|
+
const gateway = new PromptingGateway(makeDeps({ prompter }));
|
|
222
|
+
const ctx2 = makeCtx({ cwd: "/new" });
|
|
223
|
+
gateway.activate(makeCtx({ cwd: "/old" }));
|
|
224
|
+
gateway.activate(ctx2);
|
|
225
|
+
|
|
226
|
+
// canConfirm still works (context set)
|
|
227
|
+
expect(gateway.canConfirm()).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|