@gotgenes/pi-permission-system 10.3.1 → 10.5.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 +24 -0
- package/package.json +1 -1
- package/src/config-modal.ts +10 -8
- package/src/config-store.ts +6 -11
- package/src/forwarded-permissions/io.ts +16 -22
- package/src/forwarded-permissions/permission-forwarder.ts +16 -19
- package/src/gate-prompter.ts +1 -3
- package/src/handlers/gates/bash-command.ts +2 -2
- package/src/handlers/gates/bash-external-directory.ts +2 -2
- package/src/handlers/gates/bash-path.ts +2 -2
- package/src/handlers/gates/path.ts +2 -2
- package/src/handlers/gates/runner.ts +3 -3
- package/src/handlers/gates/tool-call-gate-pipeline.ts +10 -9
- package/src/index.ts +27 -41
- package/src/permission-event-rpc.ts +19 -15
- package/src/permission-prompter.ts +4 -3
- package/src/permission-resolver.ts +69 -2
- package/src/permission-session.ts +7 -83
- package/src/prompting-gateway.ts +104 -0
- package/src/session-logger.ts +17 -3
- package/test/config-modal.test.ts +13 -7
- package/test/config-store.test.ts +7 -9
- package/test/forwarded-permissions/io.test.ts +23 -26
- package/test/handlers/external-directory-integration.test.ts +45 -32
- package/test/handlers/external-directory-session-dedup.test.ts +47 -57
- package/test/handlers/gates/bash-external-directory.test.ts +2 -2
- package/test/handlers/gates/bash-path.test.ts +2 -2
- package/test/handlers/gates/runner.test.ts +10 -16
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +30 -21
- package/test/handlers/input-events.test.ts +19 -4
- package/test/handlers/input.test.ts +29 -13
- package/test/handlers/tool-call-events.test.ts +23 -5
- package/test/helpers/gate-fixtures.ts +11 -15
- package/test/helpers/handler-fixtures.ts +31 -50
- package/test/permission-event-rpc.test.ts +30 -28
- package/test/permission-forwarder.test.ts +6 -5
- package/test/permission-prompter.test.ts +28 -28
- package/test/permission-resolver.test.ts +194 -0
- package/test/permission-session.test.ts +27 -180
- package/test/prompting-gateway.test.ts +230 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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.5.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.4.0...pi-permission-system-v10.5.0) (2026-06-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add PermissionResolver class and route gate runner through it ([#340](https://github.com/gotgenes/pi-packages/issues/340)) ([4133601](https://github.com/gotgenes/pi-packages/commit/41336018d495f85b30b7b77fadb5912870f0dedd))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* suppress fallow unused-class-member for pre-Step-8 resolver methods ([#340](https://github.com/gotgenes/pi-packages/issues/340)) ([fd65626](https://github.com/gotgenes/pi-packages/commit/fd65626ae867457edeb829ea28d0ab94fe51dea6))
|
|
19
|
+
|
|
20
|
+
## [10.4.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.3.1...pi-permission-system-v10.4.0) (2026-06-07)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* add context-owning PromptingGateway ([1885be2](https://github.com/gotgenes/pi-packages/commit/1885be28fb797eb5ed67a7a30d51e58fa73e3ff0))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
* mark Phase 4 Step 6 complete; drop unused beforeEach import ([217057a](https://github.com/gotgenes/pi-packages/commit/217057ab5f8a1d3290322b442e267287b31635cf))
|
|
31
|
+
|
|
8
32
|
## [10.3.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.3.0...pi-permission-system-v10.3.1) (2026-06-06)
|
|
9
33
|
|
|
10
34
|
|
package/package.json
CHANGED
package/src/config-modal.ts
CHANGED
|
@@ -14,9 +14,12 @@ import type { Ruleset } from "./rule";
|
|
|
14
14
|
|
|
15
15
|
interface PermissionSystemConfigController {
|
|
16
16
|
config: CommandConfigStore;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/** Precomputed global config file path. */
|
|
18
|
+
configPath: string;
|
|
19
|
+
/** Returns the composed config-layer ruleset for origin display. */
|
|
20
|
+
permissionManager: { getComposedConfigRules(agentName?: string): Ruleset };
|
|
21
|
+
/** Provides the active agent name for scoped rule lookup. */
|
|
22
|
+
session: { readonly lastKnownActiveAgentName: string | null };
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
const ON_OFF = ["on", "off"];
|
|
@@ -203,7 +206,9 @@ function handleArgs(
|
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
if (normalized === "show") {
|
|
206
|
-
const rules = controller.
|
|
209
|
+
const rules = controller.permissionManager.getComposedConfigRules(
|
|
210
|
+
controller.session.lastKnownActiveAgentName ?? undefined,
|
|
211
|
+
);
|
|
207
212
|
ctx.ui.notify(
|
|
208
213
|
`permission-system: ${summarizeConfig(controller.config.current(), rules)}`,
|
|
209
214
|
"info",
|
|
@@ -212,10 +217,7 @@ function handleArgs(
|
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
if (normalized === "path") {
|
|
215
|
-
ctx.ui.notify(
|
|
216
|
-
`permission-system config: ${controller.getConfigPath()}`,
|
|
217
|
-
"info",
|
|
218
|
-
);
|
|
220
|
+
ctx.ui.notify(`permission-system config: ${controller.configPath}`, "info");
|
|
219
221
|
return true;
|
|
220
222
|
}
|
|
221
223
|
|
package/src/config-store.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type PermissionSystemExtensionConfig,
|
|
27
27
|
} from "./extension-config";
|
|
28
28
|
import type { ResolvedPolicyPaths } from "./policy-loader";
|
|
29
|
+
import type { DebugReviewLogger } from "./session-logger";
|
|
29
30
|
import { syncPermissionSystemStatus } from "./status";
|
|
30
31
|
|
|
31
32
|
/** Read-only view of the current config — for consumers that only read. */
|
|
@@ -57,12 +58,6 @@ export interface CommandConfigStore extends ConfigReader {
|
|
|
57
58
|
): void;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
/** Narrow logging sink — replaced by an injected logger in Step 3 (#336). */
|
|
61
|
-
export interface ConfigStoreLogger {
|
|
62
|
-
writeDebugLog(event: string, details?: Record<string, unknown>): void;
|
|
63
|
-
writeReviewLog(event: string, details?: Record<string, unknown>): void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
61
|
/** Narrow view of the manager's resolved policy paths (for `logResolvedPaths`). */
|
|
67
62
|
export interface ResolvedPolicyPathProvider {
|
|
68
63
|
getResolvedPolicyPaths(): ResolvedPolicyPaths;
|
|
@@ -71,7 +66,7 @@ export interface ResolvedPolicyPathProvider {
|
|
|
71
66
|
export interface ConfigStoreDeps {
|
|
72
67
|
agentDir: string;
|
|
73
68
|
policyPaths: ResolvedPolicyPathProvider;
|
|
74
|
-
logger:
|
|
69
|
+
logger: DebugReviewLogger;
|
|
75
70
|
}
|
|
76
71
|
|
|
77
72
|
/**
|
|
@@ -127,7 +122,7 @@ export class ConfigStore implements SessionConfigStore, CommandConfigStore {
|
|
|
127
122
|
this.lastConfigWarning = null;
|
|
128
123
|
}
|
|
129
124
|
|
|
130
|
-
this.deps.logger.
|
|
125
|
+
this.deps.logger.debug("config.loaded", {
|
|
131
126
|
warning: warning ?? null,
|
|
132
127
|
debugLog: runtimeConfig.debugLog,
|
|
133
128
|
permissionReviewLog: runtimeConfig.permissionReviewLog,
|
|
@@ -183,7 +178,7 @@ export class ConfigStore implements SessionConfigStore, CommandConfigStore {
|
|
|
183
178
|
syncPermissionSystemStatus(ctx, normalized);
|
|
184
179
|
this.lastConfigWarning = null;
|
|
185
180
|
|
|
186
|
-
this.deps.logger.
|
|
181
|
+
this.deps.logger.debug("config.saved", {
|
|
187
182
|
debugLog: normalized.debugLog,
|
|
188
183
|
permissionReviewLog: normalized.permissionReviewLog,
|
|
189
184
|
yoloMode: normalized.yoloMode,
|
|
@@ -215,11 +210,11 @@ export class ConfigStore implements SessionConfigStore, CommandConfigStore {
|
|
|
215
210
|
legacyProjectPolicyDetected,
|
|
216
211
|
legacyExtensionConfigDetected,
|
|
217
212
|
});
|
|
218
|
-
this.deps.logger.
|
|
213
|
+
this.deps.logger.review(
|
|
219
214
|
"config.resolved",
|
|
220
215
|
entry as unknown as Record<string, unknown>,
|
|
221
216
|
);
|
|
222
|
-
this.deps.logger.
|
|
217
|
+
this.deps.logger.debug(
|
|
223
218
|
"config.resolved",
|
|
224
219
|
entry as unknown as Record<string, unknown>,
|
|
225
220
|
);
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type ForwardedPermissionResponse,
|
|
18
18
|
type PermissionForwardingLocation,
|
|
19
19
|
} from "#src/permission-forwarding";
|
|
20
|
+
import type { DebugReviewLogger } from "#src/session-logger";
|
|
20
21
|
|
|
21
22
|
/** Valid `permissions:ui_prompt` source values, for tolerant request reads. */
|
|
22
23
|
const UI_PROMPT_SOURCES = [
|
|
@@ -41,13 +42,6 @@ function asNullableDisplayString(value: unknown): string | null | undefined {
|
|
|
41
42
|
return undefined;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
type LogFn = (event: string, details: Record<string, unknown>) => void;
|
|
45
|
-
|
|
46
|
-
export interface ForwardedPermissionLogger {
|
|
47
|
-
writeReviewLog: LogFn;
|
|
48
|
-
writeDebugLog: LogFn;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
45
|
export function formatUnknownErrorMessage(error: unknown): string {
|
|
52
46
|
if (error instanceof Error && error.message) {
|
|
53
47
|
return error.message;
|
|
@@ -69,7 +63,7 @@ export function isErrnoCode(error: unknown, code: string): boolean {
|
|
|
69
63
|
* Pass `null` for `logger` to silently no-op (e.g. in unit tests without IO).
|
|
70
64
|
*/
|
|
71
65
|
export function logPermissionForwardingWarning(
|
|
72
|
-
logger:
|
|
66
|
+
logger: DebugReviewLogger | null,
|
|
73
67
|
message: string,
|
|
74
68
|
error?: unknown,
|
|
75
69
|
): void {
|
|
@@ -78,8 +72,8 @@ export function logPermissionForwardingWarning(
|
|
|
78
72
|
? { message }
|
|
79
73
|
: { message, error: formatUnknownErrorMessage(error) };
|
|
80
74
|
|
|
81
|
-
logger?.
|
|
82
|
-
logger?.
|
|
75
|
+
logger?.review("permission_forwarding.warning", details);
|
|
76
|
+
logger?.debug("permission_forwarding.warning", details);
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
/**
|
|
@@ -87,7 +81,7 @@ export function logPermissionForwardingWarning(
|
|
|
87
81
|
* Pass `null` for `logger` to silently no-op (e.g. in unit tests without IO).
|
|
88
82
|
*/
|
|
89
83
|
export function logPermissionForwardingError(
|
|
90
|
-
logger:
|
|
84
|
+
logger: DebugReviewLogger | null,
|
|
91
85
|
message: string,
|
|
92
86
|
error?: unknown,
|
|
93
87
|
): void {
|
|
@@ -96,12 +90,12 @@ export function logPermissionForwardingError(
|
|
|
96
90
|
? { message }
|
|
97
91
|
: { message, error: formatUnknownErrorMessage(error) };
|
|
98
92
|
|
|
99
|
-
logger?.
|
|
100
|
-
logger?.
|
|
93
|
+
logger?.review("permission_forwarding.error", details);
|
|
94
|
+
logger?.debug("permission_forwarding.error", details);
|
|
101
95
|
}
|
|
102
96
|
|
|
103
97
|
export function ensureDirectoryExists(
|
|
104
|
-
logger:
|
|
98
|
+
logger: DebugReviewLogger | null,
|
|
105
99
|
path: string,
|
|
106
100
|
description: string,
|
|
107
101
|
): boolean {
|
|
@@ -126,7 +120,7 @@ export function getPermissionForwardingLocationForSession(
|
|
|
126
120
|
}
|
|
127
121
|
|
|
128
122
|
export function ensurePermissionForwardingLocation(
|
|
129
|
-
logger:
|
|
123
|
+
logger: DebugReviewLogger | null,
|
|
130
124
|
forwardingDir: string,
|
|
131
125
|
sessionId: string,
|
|
132
126
|
): PermissionForwardingLocation | null {
|
|
@@ -182,7 +176,7 @@ export function getExistingPermissionForwardingLocation(
|
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
export function tryRemoveDirectoryIfEmpty(
|
|
185
|
-
logger:
|
|
179
|
+
logger: DebugReviewLogger | null,
|
|
186
180
|
path: string,
|
|
187
181
|
description: string,
|
|
188
182
|
): void {
|
|
@@ -222,7 +216,7 @@ export function tryRemoveDirectoryIfEmpty(
|
|
|
222
216
|
}
|
|
223
217
|
|
|
224
218
|
export function cleanupPermissionForwardingLocationIfEmpty(
|
|
225
|
-
logger:
|
|
219
|
+
logger: DebugReviewLogger | null,
|
|
226
220
|
location: PermissionForwardingLocation,
|
|
227
221
|
): void {
|
|
228
222
|
tryRemoveDirectoryIfEmpty(
|
|
@@ -243,7 +237,7 @@ export function cleanupPermissionForwardingLocationIfEmpty(
|
|
|
243
237
|
}
|
|
244
238
|
|
|
245
239
|
export function safeDeleteFile(
|
|
246
|
-
logger:
|
|
240
|
+
logger: DebugReviewLogger | null,
|
|
247
241
|
filePath: string,
|
|
248
242
|
description: string,
|
|
249
243
|
): void {
|
|
@@ -263,7 +257,7 @@ export function safeDeleteFile(
|
|
|
263
257
|
}
|
|
264
258
|
|
|
265
259
|
export function writeJsonFileAtomic(
|
|
266
|
-
logger:
|
|
260
|
+
logger: DebugReviewLogger | null,
|
|
267
261
|
filePath: string,
|
|
268
262
|
value: unknown,
|
|
269
263
|
): void {
|
|
@@ -279,7 +273,7 @@ export function writeJsonFileAtomic(
|
|
|
279
273
|
}
|
|
280
274
|
|
|
281
275
|
export function readForwardedPermissionRequest(
|
|
282
|
-
logger:
|
|
276
|
+
logger: DebugReviewLogger | null,
|
|
283
277
|
filePath: string,
|
|
284
278
|
): ForwardedPermissionRequest | null {
|
|
285
279
|
try {
|
|
@@ -326,7 +320,7 @@ export function readForwardedPermissionRequest(
|
|
|
326
320
|
}
|
|
327
321
|
|
|
328
322
|
export function readForwardedPermissionResponse(
|
|
329
|
-
logger:
|
|
323
|
+
logger: DebugReviewLogger | null,
|
|
330
324
|
filePath: string,
|
|
331
325
|
): ForwardedPermissionResponse | null {
|
|
332
326
|
try {
|
|
@@ -370,7 +364,7 @@ export function readForwardedPermissionResponse(
|
|
|
370
364
|
}
|
|
371
365
|
|
|
372
366
|
export function listRequestFiles(
|
|
373
|
-
logger:
|
|
367
|
+
logger: DebugReviewLogger | null,
|
|
374
368
|
requestsDir: string,
|
|
375
369
|
): string[] {
|
|
376
370
|
try {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getActiveAgentNameFromSystemPrompt,
|
|
8
8
|
} from "#src/active-agent";
|
|
9
9
|
import { toRecord } from "#src/common";
|
|
10
|
+
import type { ConfigReader } from "#src/config-store";
|
|
10
11
|
import type {
|
|
11
12
|
PermissionPromptDecision,
|
|
12
13
|
RequestPermissionOptions,
|
|
@@ -27,13 +28,14 @@ import {
|
|
|
27
28
|
SUBAGENT_PARENT_SESSION_ENV_CANDIDATES,
|
|
28
29
|
} from "#src/permission-forwarding";
|
|
29
30
|
import { buildForwardedUiPrompt } from "#src/permission-ui-prompt";
|
|
31
|
+
import type { DebugReviewLogger } from "#src/session-logger";
|
|
30
32
|
import { isSubagentExecutionContext } from "#src/subagent-context";
|
|
31
33
|
import type { SubagentSessionRegistry } from "#src/subagent-registry";
|
|
34
|
+
import { shouldAutoApprovePermissionState } from "#src/yolo-mode";
|
|
32
35
|
|
|
33
36
|
import {
|
|
34
37
|
cleanupPermissionForwardingLocationIfEmpty,
|
|
35
38
|
ensurePermissionForwardingLocation,
|
|
36
|
-
type ForwardedPermissionLogger,
|
|
37
39
|
getExistingPermissionForwardingLocation,
|
|
38
40
|
listRequestFiles,
|
|
39
41
|
logPermissionForwardingError,
|
|
@@ -59,15 +61,15 @@ export interface PermissionForwarderDeps {
|
|
|
59
61
|
registry?: SubagentSessionRegistry;
|
|
60
62
|
/** Event bus used for UI prompt broadcasts. */
|
|
61
63
|
events?: PermissionEventBus;
|
|
62
|
-
logger:
|
|
63
|
-
writeReviewLog: (event: string, details: Record<string, unknown>) => void;
|
|
64
|
+
logger: DebugReviewLogger;
|
|
64
65
|
requestPermissionDecisionFromUi: (
|
|
65
66
|
ui: ExtensionContext["ui"],
|
|
66
67
|
title: string,
|
|
67
68
|
message: string,
|
|
68
69
|
options?: RequestPermissionOptions,
|
|
69
70
|
) => Promise<PermissionPromptDecision>;
|
|
70
|
-
|
|
71
|
+
/** Read current config for yolo-mode auto-approve check (called at prompt time). */
|
|
72
|
+
config: ConfigReader;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
// ── Module-private helpers ────────────────────────────────────────────────
|
|
@@ -165,18 +167,14 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
165
167
|
private readonly subagentSessionsDir: string;
|
|
166
168
|
private readonly registry: SubagentSessionRegistry | undefined;
|
|
167
169
|
private readonly events: PermissionEventBus | undefined;
|
|
168
|
-
private readonly logger:
|
|
169
|
-
private readonly writeReviewLog: (
|
|
170
|
-
event: string,
|
|
171
|
-
details: Record<string, unknown>,
|
|
172
|
-
) => void;
|
|
170
|
+
private readonly logger: DebugReviewLogger;
|
|
173
171
|
private readonly requestPermissionDecisionFromUi: (
|
|
174
172
|
ui: ExtensionContext["ui"],
|
|
175
173
|
title: string,
|
|
176
174
|
message: string,
|
|
177
175
|
options?: RequestPermissionOptions,
|
|
178
176
|
) => Promise<PermissionPromptDecision>;
|
|
179
|
-
private readonly
|
|
177
|
+
private readonly config: ConfigReader;
|
|
180
178
|
|
|
181
179
|
constructor(deps: PermissionForwarderDeps) {
|
|
182
180
|
this.forwardingDir = deps.forwardingDir;
|
|
@@ -184,9 +182,8 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
184
182
|
this.registry = deps.registry;
|
|
185
183
|
this.events = deps.events;
|
|
186
184
|
this.logger = deps.logger;
|
|
187
|
-
this.writeReviewLog = deps.writeReviewLog;
|
|
188
185
|
this.requestPermissionDecisionFromUi = deps.requestPermissionDecisionFromUi;
|
|
189
|
-
this.
|
|
186
|
+
this.config = deps.config;
|
|
190
187
|
}
|
|
191
188
|
|
|
192
189
|
// ── Public seam methods ────────────────────────────────────────────────
|
|
@@ -319,7 +316,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
319
316
|
const requestPath = join(location.requestsDir, `${request.id}.json`);
|
|
320
317
|
const responsePath = join(location.responsesDir, `${request.id}.json`);
|
|
321
318
|
|
|
322
|
-
this.
|
|
319
|
+
this.logger.review("forwarded_permission.request_created", {
|
|
323
320
|
requestId: request.id,
|
|
324
321
|
requesterAgentName: request.requesterAgentName,
|
|
325
322
|
requesterSessionId: request.requesterSessionId,
|
|
@@ -391,7 +388,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
391
388
|
this.logger,
|
|
392
389
|
responsePath,
|
|
393
390
|
);
|
|
394
|
-
this.
|
|
391
|
+
this.logger.review("forwarded_permission.response_received", {
|
|
395
392
|
requestId,
|
|
396
393
|
approved: response?.approved ?? null,
|
|
397
394
|
state: response?.state ?? null,
|
|
@@ -421,7 +418,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
421
418
|
this.logger,
|
|
422
419
|
`Timed out waiting for forwarded permission response '${responsePath}'`,
|
|
423
420
|
);
|
|
424
|
-
this.
|
|
421
|
+
this.logger.review("forwarded_permission.response_timed_out", {
|
|
425
422
|
requestId,
|
|
426
423
|
requesterAgentName,
|
|
427
424
|
targetSessionId,
|
|
@@ -465,14 +462,14 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
465
462
|
approved: false,
|
|
466
463
|
state: "denied",
|
|
467
464
|
};
|
|
468
|
-
if (this.
|
|
469
|
-
this.
|
|
465
|
+
if (shouldAutoApprovePermissionState("ask", this.config.current())) {
|
|
466
|
+
this.logger.review(
|
|
470
467
|
"forwarded_permission.auto_approved",
|
|
471
468
|
forwardedPermissionLogDetails,
|
|
472
469
|
);
|
|
473
470
|
decision = { approved: true, state: "approved" };
|
|
474
471
|
} else {
|
|
475
|
-
this.
|
|
472
|
+
this.logger.review(
|
|
476
473
|
"forwarded_permission.prompted",
|
|
477
474
|
forwardedPermissionLogDetails,
|
|
478
475
|
);
|
|
@@ -508,7 +505,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
|
|
|
508
505
|
}
|
|
509
506
|
|
|
510
507
|
const responsePath = join(location.responsesDir, `${request.id}.json`);
|
|
511
|
-
this.
|
|
508
|
+
this.logger.review(
|
|
512
509
|
decision.approved
|
|
513
510
|
? "forwarded_permission.approved"
|
|
514
511
|
: "forwarded_permission.denied",
|
package/src/gate-prompter.ts
CHANGED
|
@@ -8,7 +8,5 @@ import type { PromptPermissionDetails } from "./permission-prompter";
|
|
|
8
8
|
*/
|
|
9
9
|
export interface GatePrompter {
|
|
10
10
|
canConfirm(): boolean;
|
|
11
|
-
|
|
12
|
-
details: PromptPermissionDetails,
|
|
13
|
-
): Promise<PermissionPromptDecision>;
|
|
11
|
+
prompt(details: PromptPermissionDetails): Promise<PermissionPromptDecision>;
|
|
14
12
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BashCommand } from "#src/handlers/gates/bash-program";
|
|
2
2
|
import { pickMostRestrictive } from "#src/handlers/gates/candidate-check";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
4
4
|
import type { PermissionCheckResult } from "#src/types";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -30,7 +30,7 @@ export function resolveBashCommandCheck(
|
|
|
30
30
|
command: string,
|
|
31
31
|
commands: BashCommand[],
|
|
32
32
|
agentName: string | undefined,
|
|
33
|
-
resolver:
|
|
33
|
+
resolver: ScopedPermissionResolver,
|
|
34
34
|
): PermissionCheckResult {
|
|
35
35
|
const results = commands.map((cmd) => {
|
|
36
36
|
const result = resolver.resolve("bash", { command: cmd.text }, agentName);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
3
3
|
import { SessionApproval } from "#src/session-approval";
|
|
4
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
5
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
@@ -21,7 +21,7 @@ import type { ToolCallContext } from "./types";
|
|
|
21
21
|
export function describeBashExternalDirectoryGate(
|
|
22
22
|
tcc: ToolCallContext,
|
|
23
23
|
bashProgram: BashProgram | null,
|
|
24
|
-
resolver:
|
|
24
|
+
resolver: ScopedPermissionResolver,
|
|
25
25
|
): GateResult {
|
|
26
26
|
if (tcc.toolName !== "bash" || !tcc.cwd) return null;
|
|
27
27
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
3
3
|
import { SessionApproval } from "#src/session-approval";
|
|
4
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
5
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
@@ -25,7 +25,7 @@ import type { ToolCallContext } from "./types";
|
|
|
25
25
|
export function describeBashPathGate(
|
|
26
26
|
tcc: ToolCallContext,
|
|
27
27
|
bashProgram: BashProgram | null,
|
|
28
|
-
resolver:
|
|
28
|
+
resolver: ScopedPermissionResolver,
|
|
29
29
|
): GateResult {
|
|
30
30
|
if (tcc.toolName !== "bash") return null;
|
|
31
31
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getPathBearingToolPath } from "#src/path-utils";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
3
3
|
import { SessionApproval } from "#src/session-approval";
|
|
4
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
5
5
|
import type { GateDescriptor, GateResult } from "./descriptor";
|
|
@@ -15,7 +15,7 @@ import type { ToolCallContext } from "./types";
|
|
|
15
15
|
*/
|
|
16
16
|
export function describePathGate(
|
|
17
17
|
tcc: ToolCallContext,
|
|
18
|
-
resolver:
|
|
18
|
+
resolver: ScopedPermissionResolver,
|
|
19
19
|
): GateResult {
|
|
20
20
|
const filePath = getPathBearingToolPath(tcc.toolName, tcc.input);
|
|
21
21
|
if (!filePath) return null;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import type { GatePrompter } from "#src/gate-prompter";
|
|
8
8
|
import type { PermissionPromptDecision } from "#src/permission-dialog";
|
|
9
9
|
import { applyPermissionGate } from "#src/permission-gate";
|
|
10
|
-
import type {
|
|
10
|
+
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
11
11
|
import type { SessionApprovalRecorder } from "#src/session-approval-recorder";
|
|
12
12
|
import type { PermissionCheckResult } from "#src/types";
|
|
13
13
|
import type { GateDescriptor, GateResult } from "./descriptor";
|
|
@@ -28,7 +28,7 @@ import type { GateOutcome } from "./types";
|
|
|
28
28
|
*/
|
|
29
29
|
export class GateRunner {
|
|
30
30
|
constructor(
|
|
31
|
-
private readonly resolver:
|
|
31
|
+
private readonly resolver: ScopedPermissionResolver,
|
|
32
32
|
private readonly recorder: SessionApprovalRecorder,
|
|
33
33
|
private readonly prompter: GatePrompter,
|
|
34
34
|
private readonly reporter: DecisionReporter,
|
|
@@ -121,7 +121,7 @@ export class GateRunner {
|
|
|
121
121
|
canConfirm,
|
|
122
122
|
sessionApproval: descriptor.sessionApproval?.toGateApproval(),
|
|
123
123
|
promptForApproval: async () => {
|
|
124
|
-
const decision = await this.prompter.
|
|
124
|
+
const decision = await this.prompter.prompt({
|
|
125
125
|
requestId: toolCallId,
|
|
126
126
|
...descriptor.promptDetails,
|
|
127
127
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ScopedPermissionResolver } from "#src/permission-resolver";
|
|
3
3
|
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
4
4
|
import type { ToolInputFormatterLookup } from "#src/tool-input-formatter-registry";
|
|
5
5
|
import {
|
|
@@ -21,14 +21,14 @@ import type { GateOutcome, ToolCallContext } from "./types";
|
|
|
21
21
|
/**
|
|
22
22
|
* Narrow interface the pipeline needs from its session-side dependency.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* The three query methods needed to assemble gate inputs.
|
|
25
|
+
* The resolver is injected separately as a constructor parameter.
|
|
26
26
|
*
|
|
27
27
|
* `PermissionSession` satisfies this structurally at the construction call
|
|
28
28
|
* site; no `implements` clause is needed and would create a layer-inversion
|
|
29
29
|
* import from the domain module into the handler layer.
|
|
30
30
|
*/
|
|
31
|
-
export interface ToolCallGateInputs
|
|
31
|
+
export interface ToolCallGateInputs {
|
|
32
32
|
/** Active skill prompt entries for the skill-read gate. */
|
|
33
33
|
getActiveSkillEntries(): SkillPromptEntry[];
|
|
34
34
|
/** Combined infrastructure read directories (static + config-derived). */
|
|
@@ -50,6 +50,7 @@ export interface ToolCallGateInputs extends PermissionResolver {
|
|
|
50
50
|
*/
|
|
51
51
|
export class ToolCallGatePipeline {
|
|
52
52
|
constructor(
|
|
53
|
+
private readonly resolver: ScopedPermissionResolver,
|
|
53
54
|
private readonly inputs: ToolCallGateInputs,
|
|
54
55
|
private readonly customFormatters?: ToolInputFormatterLookup,
|
|
55
56
|
) {}
|
|
@@ -76,10 +77,10 @@ export class ToolCallGatePipeline {
|
|
|
76
77
|
const gateProducers: Array<() => GateResult | Promise<GateResult>> = [
|
|
77
78
|
() =>
|
|
78
79
|
describeSkillReadGate(tcc, () => this.inputs.getActiveSkillEntries()),
|
|
79
|
-
() => describePathGate(tcc, this.
|
|
80
|
+
() => describePathGate(tcc, this.resolver),
|
|
80
81
|
() => describeExternalDirectoryGate(tcc, infraDirs),
|
|
81
|
-
() => describeBashExternalDirectoryGate(tcc, bashProgram, this.
|
|
82
|
-
() => describeBashPathGate(tcc, bashProgram, this.
|
|
82
|
+
() => describeBashExternalDirectoryGate(tcc, bashProgram, this.resolver),
|
|
83
|
+
() => describeBashPathGate(tcc, bashProgram, this.resolver),
|
|
83
84
|
() => {
|
|
84
85
|
// Bash commands may chain several sub-commands (`a && b`, `a | b`, …);
|
|
85
86
|
// evaluate each unit from the shared parse on the bash surface and
|
|
@@ -91,9 +92,9 @@ export class ToolCallGatePipeline {
|
|
|
91
92
|
command ?? "",
|
|
92
93
|
bashProgram.commands(),
|
|
93
94
|
tcc.agentName ?? undefined,
|
|
94
|
-
this.
|
|
95
|
+
this.resolver,
|
|
95
96
|
)
|
|
96
|
-
: this.
|
|
97
|
+
: this.resolver.resolve(
|
|
97
98
|
tcc.toolName,
|
|
98
99
|
tcc.input,
|
|
99
100
|
tcc.agentName ?? undefined,
|