@gotgenes/pi-permission-system 10.4.0 → 10.5.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 +19 -0
- package/package.json +1 -1
- package/src/handlers/before-agent-start.ts +11 -6
- 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 +2 -2
- package/src/handlers/gates/tool-call-gate-pipeline.ts +10 -9
- package/src/handlers/lifecycle.ts +7 -4
- package/src/handlers/permission-gate-handler.ts +3 -3
- package/src/index.ts +13 -4
- package/src/permission-resolver.ts +66 -2
- package/src/permission-session.ts +8 -72
- package/src/session-rules.ts +3 -2
- package/src/skill-prompt-sanitizer.ts +1 -1
- package/test/handlers/before-agent-start.test.ts +56 -86
- package/test/handlers/external-directory-session-dedup.test.ts +80 -160
- 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/tool-call-gate-pipeline.test.ts +30 -21
- package/test/handlers/input.test.ts +5 -4
- package/test/handlers/lifecycle.test.ts +79 -85
- package/test/handlers/tool-call.test.ts +3 -2
- package/test/helpers/gate-fixtures.ts +5 -9
- package/test/helpers/handler-fixtures.ts +100 -107
- package/test/helpers/session-fixtures.ts +192 -0
- package/test/permission-resolver.test.ts +196 -0
- package/test/permission-session.test.ts +14 -266
- package/test/session-rules.test.ts +13 -5
- package/src/agent-prep-session.ts +0 -28
- package/src/gate-handler-session.ts +0 -13
- package/src/session-lifecycle-session.ts +0 -24
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
|
+
## [10.5.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.5.0...pi-permission-system-v10.5.1) (2026-06-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* correct SkillPermissionChecker comment after resolver rewire ([#341](https://github.com/gotgenes/pi-packages/issues/341)) ([1528382](https://github.com/gotgenes/pi-packages/commit/15283820a920fead92b348410828332b69f0a0d9))
|
|
14
|
+
|
|
15
|
+
## [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)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* 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))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* 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))
|
|
26
|
+
|
|
8
27
|
## [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)
|
|
9
28
|
|
|
10
29
|
|
package/package.json
CHANGED
|
@@ -2,11 +2,12 @@ import type {
|
|
|
2
2
|
BeforeAgentStartEventResult,
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import type { AgentPrepSession } from "#src/agent-prep-session";
|
|
6
5
|
import {
|
|
7
6
|
createActiveToolsCacheKey,
|
|
8
7
|
createBeforeAgentStartPromptStateKey,
|
|
9
8
|
} from "#src/before-agent-start-cache";
|
|
9
|
+
import type { PermissionResolver } from "#src/permission-resolver";
|
|
10
|
+
import type { PermissionSession } from "#src/permission-session";
|
|
10
11
|
import { resolveSkillPromptEntries } from "#src/skill-prompt-sanitizer";
|
|
11
12
|
import { sanitizeAvailableToolsSection } from "#src/system-prompt-sanitizer";
|
|
12
13
|
import { getToolNameFromValue, type ToolRegistry } from "#src/tool-registry";
|
|
@@ -35,12 +36,14 @@ export function shouldExposeTool(
|
|
|
35
36
|
* Handles the `before_agent_start` event: tool filtering + prompt sanitization.
|
|
36
37
|
*
|
|
37
38
|
* Constructor deps:
|
|
38
|
-
* - `session` — encapsulates all mutable session state
|
|
39
|
+
* - `session` — encapsulates all mutable session state and lifecycle operations
|
|
40
|
+
* - `resolver` — owns permission-query surface: `getToolPermission`, `getPolicyCacheStamp`, skill check
|
|
39
41
|
* - `toolRegistry` — Pi tool API subset (getAll + setActive)
|
|
40
42
|
*/
|
|
41
43
|
export class AgentPrepHandler {
|
|
42
44
|
constructor(
|
|
43
|
-
private readonly session:
|
|
45
|
+
private readonly session: PermissionSession,
|
|
46
|
+
private readonly resolver: PermissionResolver,
|
|
44
47
|
private readonly toolRegistry: ToolRegistry,
|
|
45
48
|
) {}
|
|
46
49
|
|
|
@@ -63,7 +66,7 @@ export class AgentPrepHandler {
|
|
|
63
66
|
}
|
|
64
67
|
if (
|
|
65
68
|
shouldExposeTool(toolName, agentName, (t, a) =>
|
|
66
|
-
this.
|
|
69
|
+
this.resolver.getToolPermission(t, a),
|
|
67
70
|
)
|
|
68
71
|
) {
|
|
69
72
|
allowedTools.push(toolName);
|
|
@@ -79,7 +82,9 @@ export class AgentPrepHandler {
|
|
|
79
82
|
const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
|
|
80
83
|
agentName,
|
|
81
84
|
cwd: ctx.cwd,
|
|
82
|
-
permissionStamp: this.
|
|
85
|
+
permissionStamp: this.resolver.getPolicyCacheStamp(
|
|
86
|
+
agentName ?? undefined,
|
|
87
|
+
),
|
|
83
88
|
systemPrompt: event.systemPrompt,
|
|
84
89
|
allowedToolNames: allowedTools,
|
|
85
90
|
});
|
|
@@ -96,7 +101,7 @@ export class AgentPrepHandler {
|
|
|
96
101
|
);
|
|
97
102
|
const skillPromptResult = resolveSkillPromptEntries(
|
|
98
103
|
toolPromptResult.prompt,
|
|
99
|
-
this.
|
|
104
|
+
this.resolver,
|
|
100
105
|
agentName,
|
|
101
106
|
ctx.cwd,
|
|
102
107
|
);
|
|
@@ -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,
|
|
@@ -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,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import type { PermissionResolver } from "#src/permission-resolver";
|
|
4
|
+
import type { PermissionSession } from "#src/permission-session";
|
|
3
5
|
import type { ServiceLifecycle } from "#src/service-lifecycle";
|
|
4
|
-
import type { SessionLifecycleSession } from "#src/session-lifecycle-session";
|
|
5
6
|
import { PERMISSION_SYSTEM_STATUS_KEY } from "#src/status";
|
|
6
7
|
|
|
7
8
|
/** Minimal subset of SessionStartEvent used by this handler. */
|
|
@@ -18,14 +19,16 @@ interface ResourcesDiscoverPayload {
|
|
|
18
19
|
* Handles session lifecycle events: start, reload, and shutdown.
|
|
19
20
|
*
|
|
20
21
|
* Constructor deps:
|
|
21
|
-
* - `session` — encapsulates all mutable session state
|
|
22
|
+
* - `session` — encapsulates all mutable session state and lifecycle operations
|
|
23
|
+
* - `resolver` — owns permission-query surface: `getConfigIssues`
|
|
22
24
|
* - `serviceLifecycle` — owns the process-global service publication;
|
|
23
25
|
* `activate` publishes (skipped for registered subagent children) and emits
|
|
24
26
|
* the ready event; `teardown` unsubscribes all session listeners and unpublishes
|
|
25
27
|
*/
|
|
26
28
|
export class SessionLifecycleHandler {
|
|
27
29
|
constructor(
|
|
28
|
-
private readonly session:
|
|
30
|
+
private readonly session: PermissionSession,
|
|
31
|
+
private readonly resolver: PermissionResolver,
|
|
29
32
|
private readonly serviceLifecycle: ServiceLifecycle,
|
|
30
33
|
) {}
|
|
31
34
|
|
|
@@ -38,7 +41,7 @@ export class SessionLifecycleHandler {
|
|
|
38
41
|
this.session.logResolvedConfigPaths();
|
|
39
42
|
|
|
40
43
|
const agentName = this.session.resolveAgentName(ctx);
|
|
41
|
-
const policyIssues = this.
|
|
44
|
+
const policyIssues = this.resolver.getConfigIssues(agentName ?? undefined);
|
|
42
45
|
for (const issue of policyIssues) {
|
|
43
46
|
this.session.logger.warn(issue);
|
|
44
47
|
}
|
|
@@ -4,11 +4,11 @@ import type {
|
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
import { toRecord } from "#src/common";
|
|
7
|
-
import type { GateHandlerSession } from "#src/gate-handler-session";
|
|
8
7
|
import {
|
|
9
8
|
formatMissingToolNameReason,
|
|
10
9
|
formatUnknownToolReason,
|
|
11
10
|
} from "#src/permission-prompts";
|
|
11
|
+
import type { PermissionSession } from "#src/permission-session";
|
|
12
12
|
import {
|
|
13
13
|
checkRequestedToolRegistration,
|
|
14
14
|
getToolNameFromValue,
|
|
@@ -31,7 +31,7 @@ interface InputPayload {
|
|
|
31
31
|
* Handles permission gate events: tool_call and input.
|
|
32
32
|
*
|
|
33
33
|
* Constructor deps:
|
|
34
|
-
* - `session` —
|
|
34
|
+
* - `session` — state/lifecycle owner: bind per-event context, resolve agent name
|
|
35
35
|
* - `toolRegistry` — Pi tool API subset (getAll + setActive)
|
|
36
36
|
* - `pipeline` — owns tool-call gate-producer assembly and the run loop
|
|
37
37
|
* - `skillInputPipeline` — owns skill-input gate assembly (pre-check, notify, run)
|
|
@@ -39,7 +39,7 @@ interface InputPayload {
|
|
|
39
39
|
*/
|
|
40
40
|
export class PermissionGateHandler {
|
|
41
41
|
constructor(
|
|
42
|
-
private readonly session:
|
|
42
|
+
private readonly session: PermissionSession,
|
|
43
43
|
private readonly toolRegistry: ToolRegistry,
|
|
44
44
|
private readonly pipeline: ToolCallGatePipeline,
|
|
45
45
|
private readonly skillInputPipeline: SkillInputGatePipeline,
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { requestPermissionDecisionFromUi } from "./permission-dialog";
|
|
|
23
23
|
import { registerPermissionRpcHandlers } from "./permission-event-rpc";
|
|
24
24
|
import { PermissionManager } from "./permission-manager";
|
|
25
25
|
import { PermissionPrompter } from "./permission-prompter";
|
|
26
|
+
import { PermissionResolver } from "./permission-resolver";
|
|
26
27
|
import { PermissionSession } from "./permission-session";
|
|
27
28
|
import { LocalPermissionsService } from "./permissions-service";
|
|
28
29
|
import { PromptingGateway } from "./prompting-gateway";
|
|
@@ -156,15 +157,23 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
156
157
|
setActive: (names: string[]) => pi.setActiveTools(names),
|
|
157
158
|
};
|
|
158
159
|
|
|
159
|
-
const
|
|
160
|
-
|
|
160
|
+
const resolver = new PermissionResolver(permissionManager, sessionRules);
|
|
161
|
+
|
|
162
|
+
const lifecycle = new SessionLifecycleHandler(
|
|
163
|
+
session,
|
|
164
|
+
resolver,
|
|
165
|
+
serviceLifecycle,
|
|
166
|
+
);
|
|
167
|
+
const agentPrep = new AgentPrepHandler(session, resolver, toolRegistry);
|
|
168
|
+
|
|
161
169
|
const reporter = new GateDecisionReporter(session.logger, pi.events);
|
|
162
|
-
const gateRunner = new GateRunner(
|
|
170
|
+
const gateRunner = new GateRunner(resolver, sessionRules, gateway, reporter);
|
|
163
171
|
const toolCallGatePipeline = new ToolCallGatePipeline(
|
|
172
|
+
resolver,
|
|
164
173
|
session,
|
|
165
174
|
formatterRegistry,
|
|
166
175
|
);
|
|
167
|
-
const skillInputGatePipeline = new SkillInputGatePipeline(
|
|
176
|
+
const skillInputGatePipeline = new SkillInputGatePipeline(resolver);
|
|
168
177
|
const gates = new PermissionGateHandler(
|
|
169
178
|
session,
|
|
170
179
|
toolRegistry,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ScopedPermissionManager } from "./permission-manager";
|
|
2
|
+
import type { Rule } from "./rule";
|
|
3
|
+
import type { SessionRules } from "./session-rules";
|
|
4
|
+
import type { PermissionCheckResult, PermissionState } from "./types";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Resolves the effective permission for a surface/input, applying the current
|
|
@@ -8,10 +11,71 @@ import type { PermissionCheckResult } from "./types";
|
|
|
8
11
|
* previously threaded by hand: the ruleset was only ever fetched to be passed
|
|
9
12
|
* straight back into `checkPermission`, so the two are one operation.
|
|
10
13
|
*/
|
|
11
|
-
export interface
|
|
14
|
+
export interface ScopedPermissionResolver {
|
|
12
15
|
resolve(
|
|
13
16
|
surface: string,
|
|
14
17
|
input: unknown,
|
|
15
18
|
agentName?: string,
|
|
16
19
|
): PermissionCheckResult;
|
|
17
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Concrete collaborator that owns the resolution surface.
|
|
24
|
+
*
|
|
25
|
+
* Holds a `ScopedPermissionManager` and a `SessionRules` store, composing
|
|
26
|
+
* them so callers never thread the session ruleset by hand.
|
|
27
|
+
*
|
|
28
|
+
* Constructor deps:
|
|
29
|
+
* - `permissionManager` — the narrow session-scoped permission-checking interface
|
|
30
|
+
* - `sessionRules` — narrowed to `getRuleset` (ISP: the resolver only reads, never records)
|
|
31
|
+
*/
|
|
32
|
+
export class PermissionResolver implements ScopedPermissionResolver {
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly permissionManager: ScopedPermissionManager,
|
|
35
|
+
private readonly sessionRules: Pick<SessionRules, "getRuleset">,
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the effective permission for a surface/input, applying the current
|
|
40
|
+
* session rules. Composes `checkPermission` with `getRuleset()` so callers
|
|
41
|
+
* never thread the ruleset by hand.
|
|
42
|
+
*/
|
|
43
|
+
resolve(
|
|
44
|
+
surface: string,
|
|
45
|
+
input: unknown,
|
|
46
|
+
agentName?: string,
|
|
47
|
+
): PermissionCheckResult {
|
|
48
|
+
return this.checkPermission(
|
|
49
|
+
surface,
|
|
50
|
+
input,
|
|
51
|
+
agentName,
|
|
52
|
+
this.sessionRules.getRuleset(),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
checkPermission(
|
|
57
|
+
surface: string,
|
|
58
|
+
input: unknown,
|
|
59
|
+
agentName?: string,
|
|
60
|
+
sessionRules?: Rule[],
|
|
61
|
+
): PermissionCheckResult {
|
|
62
|
+
return this.permissionManager.checkPermission(
|
|
63
|
+
surface,
|
|
64
|
+
input,
|
|
65
|
+
agentName,
|
|
66
|
+
sessionRules,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getToolPermission(toolName: string, agentName?: string): PermissionState {
|
|
71
|
+
return this.permissionManager.getToolPermission(toolName, agentName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getConfigIssues(agentName?: string): string[] {
|
|
75
|
+
return this.permissionManager.getConfigIssues(agentName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getPolicyCacheStamp(agentName?: string): string {
|
|
79
|
+
return this.permissionManager.getPolicyCacheStamp(agentName);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -4,19 +4,15 @@ import {
|
|
|
4
4
|
getActiveAgentName,
|
|
5
5
|
getActiveAgentNameFromSystemPrompt,
|
|
6
6
|
} from "./active-agent";
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import type { SessionConfigStore } from "./config-store";
|
|
9
9
|
import type { PermissionSystemExtensionConfig } from "./extension-config";
|
|
10
10
|
import type { ExtensionPaths } from "./extension-paths";
|
|
11
11
|
import type { ForwardingController } from "./forwarding-manager";
|
|
12
|
-
import type {
|
|
12
|
+
import type { ToolCallGateInputs } from "./handlers/gates/tool-call-gate-pipeline";
|
|
13
13
|
import type { ScopedPermissionManager } from "./permission-manager";
|
|
14
|
-
import type { PermissionResolver } from "./permission-resolver";
|
|
15
14
|
import type { PromptingGatewayLifecycle } from "./prompting-gateway";
|
|
16
|
-
|
|
17
|
-
import type { SessionApproval } from "./session-approval";
|
|
18
|
-
import type { SessionApprovalRecorder } from "./session-approval-recorder";
|
|
19
|
-
import type { SessionLifecycleSession } from "./session-lifecycle-session";
|
|
15
|
+
|
|
20
16
|
import type { SessionLogger } from "./session-logger";
|
|
21
17
|
import type { SessionRules } from "./session-rules";
|
|
22
18
|
import type { SkillPromptEntry } from "./skill-prompt-sanitizer";
|
|
@@ -24,7 +20,6 @@ import {
|
|
|
24
20
|
resolveToolPreviewLimits,
|
|
25
21
|
type ToolPreviewFormatterOptions,
|
|
26
22
|
} from "./tool-preview-formatter";
|
|
27
|
-
import type { PermissionCheckResult, PermissionState } from "./types";
|
|
28
23
|
|
|
29
24
|
/**
|
|
30
25
|
* Encapsulates all mutable session state and exposes operations instead of
|
|
@@ -41,14 +36,7 @@ import type { PermissionCheckResult, PermissionState } from "./types";
|
|
|
41
36
|
* - `SessionConfigStore` — owns extension config; provides refresh, log, read
|
|
42
37
|
* - `PromptingGatewayLifecycle` — prompting lifecycle forwarded via activate/deactivate
|
|
43
38
|
*/
|
|
44
|
-
export class PermissionSession
|
|
45
|
-
implements
|
|
46
|
-
PermissionResolver,
|
|
47
|
-
SessionApprovalRecorder,
|
|
48
|
-
GateHandlerSession,
|
|
49
|
-
AgentPrepSession,
|
|
50
|
-
SessionLifecycleSession
|
|
51
|
-
{
|
|
39
|
+
export class PermissionSession implements ToolCallGateInputs {
|
|
52
40
|
private context: ExtensionContext | null = null;
|
|
53
41
|
private skillEntries: SkillPromptEntry[] = [];
|
|
54
42
|
private knownAgentName: string | null = null;
|
|
@@ -86,62 +74,6 @@ export class PermissionSession
|
|
|
86
74
|
return this.context;
|
|
87
75
|
}
|
|
88
76
|
|
|
89
|
-
// ── Permission checking (delegates to PermissionManager) ───────────────
|
|
90
|
-
|
|
91
|
-
checkPermission(
|
|
92
|
-
surface: string,
|
|
93
|
-
input: unknown,
|
|
94
|
-
agentName?: string,
|
|
95
|
-
sessionRules?: Rule[],
|
|
96
|
-
): PermissionCheckResult {
|
|
97
|
-
return this.permissionManager.checkPermission(
|
|
98
|
-
surface,
|
|
99
|
-
input,
|
|
100
|
-
agentName,
|
|
101
|
-
sessionRules,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Resolve the effective permission for a surface/input, applying the current
|
|
107
|
-
* session rules. Composes `checkPermission` with `getSessionRuleset` so
|
|
108
|
-
* callers never thread the ruleset by hand.
|
|
109
|
-
*/
|
|
110
|
-
resolve(
|
|
111
|
-
surface: string,
|
|
112
|
-
input: unknown,
|
|
113
|
-
agentName?: string,
|
|
114
|
-
): PermissionCheckResult {
|
|
115
|
-
return this.checkPermission(
|
|
116
|
-
surface,
|
|
117
|
-
input,
|
|
118
|
-
agentName,
|
|
119
|
-
this.getSessionRuleset(),
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
getToolPermission(toolName: string, agentName?: string): PermissionState {
|
|
124
|
-
return this.permissionManager.getToolPermission(toolName, agentName);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
getConfigIssues(agentName?: string): string[] {
|
|
128
|
-
return this.permissionManager.getConfigIssues(agentName);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
getPolicyCacheStamp(agentName?: string): string {
|
|
132
|
-
return this.permissionManager.getPolicyCacheStamp(agentName);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ── Session rules (delegates to SessionRules) ──────────────────────────
|
|
136
|
-
|
|
137
|
-
getSessionRuleset(): Rule[] {
|
|
138
|
-
return this.sessionRules.getRuleset();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
recordSessionApproval(approval: SessionApproval): void {
|
|
142
|
-
this.sessionRules.record(approval);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
77
|
// ── Session lifecycle ────────────────────────────────────────────────────
|
|
146
78
|
|
|
147
79
|
/**
|
|
@@ -232,6 +164,10 @@ export class PermissionSession
|
|
|
232
164
|
return this.knownAgentName;
|
|
233
165
|
}
|
|
234
166
|
|
|
167
|
+
// Read by config-modal (`controller.session.lastKnownActiveAgentName`).
|
|
168
|
+
// fallow cannot trace the getter through the command's object-literal
|
|
169
|
+
// wiring, so it reports a false positive here.
|
|
170
|
+
// fallow-ignore-next-line unused-class-member
|
|
235
171
|
get lastKnownActiveAgentName(): string | null {
|
|
236
172
|
return this.knownAgentName;
|
|
237
173
|
}
|
package/src/session-rules.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { dirname, sep } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import type { Ruleset } from "./rule";
|
|
4
4
|
import type { SessionApproval } from "./session-approval";
|
|
5
|
+
import type { SessionApprovalRecorder } from "./session-approval-recorder";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Ephemeral in-memory store of session-scoped permission approvals.
|
|
@@ -11,7 +12,7 @@ import type { SessionApproval } from "./session-approval";
|
|
|
11
12
|
*
|
|
12
13
|
* Cleared on session_shutdown — never persisted to disk.
|
|
13
14
|
*/
|
|
14
|
-
export class SessionRules {
|
|
15
|
+
export class SessionRules implements SessionApprovalRecorder {
|
|
15
16
|
private rules: Ruleset = [];
|
|
16
17
|
|
|
17
18
|
/** Record a wildcard pattern as approved for the given surface. */
|
|
@@ -36,7 +37,7 @@ export class SessionRules {
|
|
|
36
37
|
* The loop lives here so callers never need to know whether an approval
|
|
37
38
|
* carries one pattern or many — they just tell the store to record it.
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
+
recordSessionApproval(approval: SessionApproval): void {
|
|
40
41
|
for (const pattern of approval.patterns) {
|
|
41
42
|
this.approve(approval.surface, pattern);
|
|
42
43
|
}
|
|
@@ -8,7 +8,7 @@ import type { PermissionCheckResult, PermissionState } from "./types";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Narrow interface for the permission checker used by skill prompt resolution.
|
|
11
|
-
* Both `PermissionManager` and `
|
|
11
|
+
* Both `PermissionManager` and `PermissionResolver` satisfy this structurally.
|
|
12
12
|
*/
|
|
13
13
|
export interface SkillPermissionChecker {
|
|
14
14
|
checkPermission(
|