@gotgenes/pi-permission-system 8.0.0 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/config/config.example.json +3 -0
- package/package.json +1 -1
- package/schemas/permissions.schema.json +12 -0
- package/src/extension-config.ts +23 -0
- package/src/handlers/gates/bash-external-directory.ts +2 -4
- package/src/handlers/gates/bash-path.ts +2 -4
- package/src/handlers/gates/descriptor.ts +6 -6
- package/src/handlers/gates/external-directory.ts +2 -4
- package/src/handlers/gates/helpers.ts +30 -1
- package/src/handlers/gates/path.ts +2 -4
- package/src/handlers/gates/runner.ts +29 -56
- package/src/handlers/gates/tool.ts +9 -6
- package/src/handlers/permission-gate-handler.ts +110 -141
- package/src/permission-manager.ts +6 -49
- package/src/permission-prompts.ts +5 -2
- package/src/permission-session.ts +3 -2
- package/src/scope-merge.ts +72 -0
- package/src/session-approval.ts +43 -0
- package/src/session-rules.ts +13 -0
- package/src/tool-input-preview.ts +0 -116
- package/src/tool-preview-formatter.ts +188 -0
- package/test/extension-config.test.ts +93 -0
- package/test/handlers/external-directory-integration.test.ts +3 -1
- package/test/handlers/external-directory-session-dedup.test.ts +17 -12
- package/test/handlers/gates/bash-external-directory.test.ts +11 -9
- package/test/handlers/gates/external-directory.test.ts +2 -5
- package/test/handlers/gates/helpers.test.ts +81 -0
- package/test/handlers/gates/path.test.ts +2 -2
- package/test/handlers/gates/runner.test.ts +18 -23
- package/test/handlers/gates/tool.test.ts +31 -4
- package/test/handlers/input-events.test.ts +1 -1
- package/test/handlers/input.test.ts +1 -1
- package/test/handlers/tool-call-events.test.ts +3 -2
- package/test/handlers/tool-call.test.ts +3 -2
- package/test/handlers/validate-requested-tool.test.ts +92 -0
- package/test/permission-prompts.test.ts +66 -38
- package/test/permission-session.test.ts +6 -3
- package/test/scope-merge.test.ts +116 -0
- package/test/session-approval.test.ts +75 -0
- package/test/session-rules.test.ts +49 -0
- package/test/tool-input-preview.test.ts +0 -244
- package/test/tool-preview-formatter.test.ts +385 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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
|
+
## [8.2.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v8.1.0...pi-permission-system-v8.2.0) (2026-05-31)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add SessionApproval value object and SessionRules.record ([8f98d92](https://github.com/gotgenes/pi-packages/commit/8f98d9223a424b0993d51c2d9106e7d01c6819d7))
|
|
14
|
+
* centralize decision-event construction in buildDecisionEvent ([19c2c83](https://github.com/gotgenes/pi-packages/commit/19c2c837b1907a4c302105ee86715533477247d4))
|
|
15
|
+
|
|
16
|
+
## [8.1.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v8.0.0...pi-permission-system-v8.1.0) (2026-05-31)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* add toolInputPreviewMaxLength and toolTextSummaryMaxLength config fields ([#266](https://github.com/gotgenes/pi-packages/issues/266)) ([3a7dafb](https://github.com/gotgenes/pi-packages/commit/3a7dafbb0bb8534dabda7eeba6c4d35ba2e8708b))
|
|
22
|
+
* use configured preview limits in permission prompts ([#266](https://github.com/gotgenes/pi-packages/issues/266)) ([83e2829](https://github.com/gotgenes/pi-packages/commit/83e2829175a55f2f0436c742e19e3753ee171e47))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
* document configurable tool-preview length knobs ([#266](https://github.com/gotgenes/pi-packages/issues/266)) ([6d0b134](https://github.com/gotgenes/pi-packages/commit/6d0b134be4ef4c90ddf582b32058c3ec9d2eb13f))
|
|
28
|
+
|
|
8
29
|
## [8.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.4.1...pi-permission-system-v8.0.0) (2026-05-30)
|
|
9
30
|
|
|
10
31
|
|
package/package.json
CHANGED
|
@@ -29,6 +29,18 @@
|
|
|
29
29
|
"type": "boolean",
|
|
30
30
|
"default": false
|
|
31
31
|
},
|
|
32
|
+
"toolInputPreviewMaxLength": {
|
|
33
|
+
"description": "Maximum character length of the inline-JSON tool-input preview shown in permission prompts. Omit to use the default (200). Set to a large value to disable truncation.",
|
|
34
|
+
"markdownDescription": "Maximum character length of the inline-JSON tool-input preview shown in permission prompts.\n\nOmit to use the default (200). Set to a large value (e.g. `10000`) to effectively disable truncation and see the full input.",
|
|
35
|
+
"type": "integer",
|
|
36
|
+
"minimum": 1
|
|
37
|
+
},
|
|
38
|
+
"toolTextSummaryMaxLength": {
|
|
39
|
+
"description": "Maximum character length of inline pattern/path summaries (e.g. grep patterns, find globs, ls paths) in permission prompts. Omit to use the default (80).",
|
|
40
|
+
"markdownDescription": "Maximum character length of inline pattern/path summaries (e.g. grep patterns, find globs, ls paths) shown in permission prompts.\n\nOmit to use the default (80). Increase this when working with long regexes or deep paths that are being cut off.",
|
|
41
|
+
"type": "integer",
|
|
42
|
+
"minimum": 1
|
|
43
|
+
},
|
|
32
44
|
"piInfrastructureReadPaths": {
|
|
33
45
|
"description": "Additional directories to auto-allow for reads as Pi infrastructure, bypassing the external_directory gate. Supports ~ expansion and wildcard patterns (* and ?).",
|
|
34
46
|
"markdownDescription": "Additional directories to auto-allow for reads as Pi infrastructure, bypassing the `external_directory` gate.\n\nThe extension auto-discovers the global node_modules root (walks up from the extension's install path; falls back to `npm root -g` from a dev checkout), `agentDir`, `agentDir/git`, and project-local `.pi/npm/` and `.pi/git/`. Add entries here for edge cases where auto-discovery is insufficient (e.g. custom `npmCommand` pointing to pnpm).\n\nSupports `~`/`$HOME` expansion. Entries may be plain directory prefixes or wildcard patterns using `*` (matches any characters, including `/`) and `?` (matches exactly one character). `**` and `*` are equivalent — both cross directory boundaries.",
|
package/src/extension-config.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface PermissionSystemExtensionConfig {
|
|
|
12
12
|
yoloMode: boolean;
|
|
13
13
|
/** Additional directories to auto-allow for reads as Pi infrastructure. */
|
|
14
14
|
piInfrastructureReadPaths?: string[];
|
|
15
|
+
/** Max length of the inline-JSON input preview shown in permission prompts. Defaults to 200. */
|
|
16
|
+
toolInputPreviewMaxLength?: number;
|
|
17
|
+
/** Max length of inline pattern/path summaries (grep/find/ls) in permission prompts. Defaults to 80. */
|
|
18
|
+
toolTextSummaryMaxLength?: number;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export const DEFAULT_EXTENSION_CONFIG: PermissionSystemExtensionConfig = {
|
|
@@ -42,6 +46,13 @@ export function detectMisplacedPermissionKeys(
|
|
|
42
46
|
return Object.keys(raw).filter((key) => PERMISSION_POLICY_KEYS.has(key));
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
/** Returns `raw` if it is a positive integer; otherwise `undefined`. */
|
|
50
|
+
export function normalizeOptionalPositiveInt(raw: unknown): number | undefined {
|
|
51
|
+
return typeof raw === "number" && Number.isInteger(raw) && raw > 0
|
|
52
|
+
? raw
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
export function normalizePermissionSystemConfig(
|
|
46
57
|
raw: unknown,
|
|
47
58
|
): PermissionSystemExtensionConfig {
|
|
@@ -60,6 +71,18 @@ export function normalizePermissionSystemConfig(
|
|
|
60
71
|
if (piInfrastructureReadPaths !== undefined) {
|
|
61
72
|
result.piInfrastructureReadPaths = piInfrastructureReadPaths;
|
|
62
73
|
}
|
|
74
|
+
const toolInputPreviewMaxLength = normalizeOptionalPositiveInt(
|
|
75
|
+
record.toolInputPreviewMaxLength,
|
|
76
|
+
);
|
|
77
|
+
if (toolInputPreviewMaxLength !== undefined) {
|
|
78
|
+
result.toolInputPreviewMaxLength = toolInputPreviewMaxLength;
|
|
79
|
+
}
|
|
80
|
+
const toolTextSummaryMaxLength = normalizeOptionalPositiveInt(
|
|
81
|
+
record.toolTextSummaryMaxLength,
|
|
82
|
+
);
|
|
83
|
+
if (toolTextSummaryMaxLength !== undefined) {
|
|
84
|
+
result.toolTextSummaryMaxLength = toolTextSummaryMaxLength;
|
|
85
|
+
}
|
|
63
86
|
return result;
|
|
64
87
|
}
|
|
65
88
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
2
|
import type { Rule } from "#src/rule";
|
|
3
|
+
import { SessionApproval } from "#src/session-approval";
|
|
3
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
4
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
5
6
|
import { extractExternalPathsFromBashCommand } from "./bash-path-extractor";
|
|
@@ -106,10 +107,7 @@ export async function describeBashExternalDirectoryGate(
|
|
|
106
107
|
cwd: tcc.cwd,
|
|
107
108
|
agentName: tcc.agentName ?? undefined,
|
|
108
109
|
},
|
|
109
|
-
sessionApproval:
|
|
110
|
-
surface: "external_directory",
|
|
111
|
-
patterns,
|
|
112
|
-
},
|
|
110
|
+
sessionApproval: SessionApproval.multiple("external_directory", patterns),
|
|
113
111
|
promptDetails: {
|
|
114
112
|
source: "tool_call",
|
|
115
113
|
agentName: tcc.agentName,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
2
|
import type { Rule } from "#src/rule";
|
|
3
|
+
import { SessionApproval } from "#src/session-approval";
|
|
3
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
4
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
5
6
|
import { extractTokensForPathRules } from "./bash-path-extractor";
|
|
@@ -117,10 +118,7 @@ export async function describeBashPathGate(
|
|
|
117
118
|
pathValue: worstToken,
|
|
118
119
|
agentName: tcc.agentName ?? undefined,
|
|
119
120
|
},
|
|
120
|
-
sessionApproval:
|
|
121
|
-
surface: "path",
|
|
122
|
-
pattern,
|
|
123
|
-
},
|
|
121
|
+
sessionApproval: SessionApproval.single("path", pattern),
|
|
124
122
|
promptDetails: {
|
|
125
123
|
source: "tool_call",
|
|
126
124
|
agentName: tcc.agentName,
|
|
@@ -3,6 +3,7 @@ import type { PermissionPromptDecision } from "#src/permission-dialog";
|
|
|
3
3
|
import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
4
4
|
import type { PromptPermissionDetails } from "#src/permission-prompter";
|
|
5
5
|
import type { Rule } from "#src/rule";
|
|
6
|
+
import type { SessionApproval } from "#src/session-approval";
|
|
6
7
|
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
7
8
|
|
|
8
9
|
// ── Descriptor types ───────────────────────────────────────────────────────
|
|
@@ -22,12 +23,11 @@ export interface GateDescriptor {
|
|
|
22
23
|
/** Structured denial context — the runner formats messages from this. */
|
|
23
24
|
denialContext: DenialContext;
|
|
24
25
|
/**
|
|
25
|
-
* Session-approval suggestion for "for this session" option.
|
|
26
|
-
*
|
|
26
|
+
* Session-approval suggestion for the "for this session" option.
|
|
27
|
+
* Wraps either a single pattern or multiple patterns behind a unified
|
|
28
|
+
* interface — the runner never needs to know which case applies.
|
|
27
29
|
*/
|
|
28
|
-
sessionApproval?:
|
|
29
|
-
| { surface: string; pattern: string }
|
|
30
|
-
| { surface: string; patterns: string[] };
|
|
30
|
+
sessionApproval?: SessionApproval;
|
|
31
31
|
/** Details passed to the interactive permission prompt (requestId is added by the runner). */
|
|
32
32
|
promptDetails: Omit<PromptPermissionDetails, "requestId">;
|
|
33
33
|
/** Extra context fields written to the review log alongside gate outcomes. */
|
|
@@ -87,7 +87,7 @@ export interface GateRunnerDeps {
|
|
|
87
87
|
sessionRules?: Rule[],
|
|
88
88
|
): PermissionCheckResult;
|
|
89
89
|
getSessionRuleset(): Rule[];
|
|
90
|
-
|
|
90
|
+
recordSessionApproval(approval: SessionApproval): void;
|
|
91
91
|
writeReviewLog(event: string, details: Record<string, unknown>): void;
|
|
92
92
|
emitDecision(event: PermissionDecisionEvent): void;
|
|
93
93
|
canConfirm(): boolean;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isPiInfrastructureRead,
|
|
5
5
|
normalizePathForComparison,
|
|
6
6
|
} from "#src/path-utils";
|
|
7
|
+
import { SessionApproval } from "#src/session-approval";
|
|
7
8
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
8
9
|
import type { GateResult } from "./descriptor";
|
|
9
10
|
import { formatExternalDirectoryAskPrompt } from "./external-directory-messages";
|
|
@@ -83,10 +84,7 @@ export function describeExternalDirectoryGate(
|
|
|
83
84
|
cwd: tcc.cwd,
|
|
84
85
|
agentName: tcc.agentName ?? undefined,
|
|
85
86
|
},
|
|
86
|
-
sessionApproval:
|
|
87
|
-
surface: "external_directory",
|
|
88
|
-
pattern,
|
|
89
|
-
},
|
|
87
|
+
sessionApproval: SessionApproval.single("external_directory", pattern),
|
|
90
88
|
promptDetails: {
|
|
91
89
|
source: "tool_call",
|
|
92
90
|
agentName: tcc.agentName,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
PermissionDecisionEvent,
|
|
3
|
+
PermissionDecisionResolution,
|
|
4
|
+
} from "#src/permission-events";
|
|
2
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -17,6 +20,32 @@ export function deriveDecisionValue(
|
|
|
17
20
|
return toolName;
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Build a `PermissionDecisionEvent` from the gate's inputs.
|
|
25
|
+
*
|
|
26
|
+
* Centralises the `origin / agentName / matchedPattern ?? null` normalization
|
|
27
|
+
* that is otherwise duplicated across the session-hit path and the gate-result
|
|
28
|
+
* path in `runGateCheck`.
|
|
29
|
+
*/
|
|
30
|
+
export function buildDecisionEvent(
|
|
31
|
+
decision: { surface: string; value: string },
|
|
32
|
+
check: Pick<PermissionCheckResult, "origin" | "matchedPattern">,
|
|
33
|
+
agentName: string | null,
|
|
34
|
+
result: "allow" | "deny",
|
|
35
|
+
resolution: PermissionDecisionResolution,
|
|
36
|
+
): PermissionDecisionEvent {
|
|
37
|
+
return {
|
|
38
|
+
surface: decision.surface,
|
|
39
|
+
value: decision.value,
|
|
40
|
+
result,
|
|
41
|
+
resolution,
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
|
|
43
|
+
origin: check.origin ?? null,
|
|
44
|
+
agentName: agentName ?? null,
|
|
45
|
+
matchedPattern: check.matchedPattern ?? null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
/**
|
|
21
50
|
* Map the gate outcome back to a PermissionDecisionResolution.
|
|
22
51
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getPathBearingToolPath } from "#src/path-utils";
|
|
2
2
|
import type { Rule } from "#src/rule";
|
|
3
|
+
import { SessionApproval } from "#src/session-approval";
|
|
3
4
|
import { deriveApprovalPattern } from "#src/session-rules";
|
|
4
5
|
import type { PermissionCheckResult } from "#src/types";
|
|
5
6
|
import type { GateDescriptor, GateResult } from "./descriptor";
|
|
@@ -55,10 +56,7 @@ export function describePathGate(
|
|
|
55
56
|
pathValue: filePath,
|
|
56
57
|
agentName: tcc.agentName ?? undefined,
|
|
57
58
|
},
|
|
58
|
-
sessionApproval:
|
|
59
|
-
surface: "path",
|
|
60
|
-
pattern,
|
|
61
|
-
},
|
|
59
|
+
sessionApproval: SessionApproval.single("path", pattern),
|
|
62
60
|
promptDetails: {
|
|
63
61
|
source: "tool_call",
|
|
64
62
|
agentName: tcc.agentName,
|
|
@@ -7,7 +7,7 @@ import type { PermissionPromptDecision } from "#src/permission-dialog";
|
|
|
7
7
|
import { applyPermissionGate } from "#src/permission-gate";
|
|
8
8
|
import type { PermissionCheckResult } from "#src/types";
|
|
9
9
|
import type { GateDescriptor, GateRunnerDeps } from "./descriptor";
|
|
10
|
-
import { deriveResolution } from "./helpers";
|
|
10
|
+
import { buildDecisionEvent, deriveResolution } from "./helpers";
|
|
11
11
|
import type { GateOutcome } from "./types";
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -56,37 +56,21 @@ export async function runGateCheck(
|
|
|
56
56
|
resolution: "session_approved",
|
|
57
57
|
sessionApprovalPattern: check.matchedPattern,
|
|
58
58
|
});
|
|
59
|
-
deps.emitDecision(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
59
|
+
deps.emitDecision(
|
|
60
|
+
buildDecisionEvent(
|
|
61
|
+
descriptor.decision,
|
|
62
|
+
check,
|
|
63
|
+
agentName,
|
|
64
|
+
"allow",
|
|
65
|
+
"session_approved",
|
|
66
|
+
),
|
|
67
|
+
);
|
|
69
68
|
return { action: "allow" };
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
// 3. Apply the deny/ask/allow gate
|
|
73
72
|
const canConfirm = deps.canConfirm();
|
|
74
73
|
|
|
75
|
-
// Resolve the first pattern for applyPermissionGate's sessionApproval param
|
|
76
|
-
const singleSessionApproval = descriptor.sessionApproval
|
|
77
|
-
? "pattern" in descriptor.sessionApproval
|
|
78
|
-
? {
|
|
79
|
-
surface: descriptor.sessionApproval.surface,
|
|
80
|
-
pattern: descriptor.sessionApproval.pattern,
|
|
81
|
-
}
|
|
82
|
-
: descriptor.sessionApproval.patterns.length > 0
|
|
83
|
-
? {
|
|
84
|
-
surface: descriptor.sessionApproval.surface,
|
|
85
|
-
pattern: descriptor.sessionApproval.patterns[0],
|
|
86
|
-
}
|
|
87
|
-
: undefined
|
|
88
|
-
: undefined;
|
|
89
|
-
|
|
90
74
|
// Construct messages from the centralized formatter.
|
|
91
75
|
const messages = {
|
|
92
76
|
denyReason: formatDenyReason(descriptor.denialContext),
|
|
@@ -99,7 +83,7 @@ export async function runGateCheck(
|
|
|
99
83
|
const gateResult = await applyPermissionGate({
|
|
100
84
|
state: check.state,
|
|
101
85
|
canConfirm,
|
|
102
|
-
sessionApproval:
|
|
86
|
+
sessionApproval: descriptor.sessionApproval?.toGateApproval(),
|
|
103
87
|
promptForApproval: async () => {
|
|
104
88
|
const decision = await deps.promptPermission({
|
|
105
89
|
requestId: toolCallId,
|
|
@@ -119,37 +103,26 @@ export async function runGateCheck(
|
|
|
119
103
|
gateResult.action === "allow" && gateResult.sessionApproval !== undefined;
|
|
120
104
|
|
|
121
105
|
// 5. Emit decision event
|
|
122
|
-
deps.emitDecision(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
106
|
+
deps.emitDecision(
|
|
107
|
+
buildDecisionEvent(
|
|
108
|
+
descriptor.decision,
|
|
109
|
+
check,
|
|
110
|
+
agentName,
|
|
111
|
+
gateResult.action === "allow" ? "allow" : "deny",
|
|
112
|
+
deriveResolution(
|
|
113
|
+
check.state,
|
|
114
|
+
gateResult.action,
|
|
115
|
+
hasSessionApproval,
|
|
116
|
+
canConfirm,
|
|
117
|
+
autoApproved,
|
|
118
|
+
),
|
|
132
119
|
),
|
|
133
|
-
|
|
134
|
-
origin: check.origin ?? null,
|
|
135
|
-
agentName: agentName ?? null,
|
|
136
|
-
matchedPattern: check.matchedPattern ?? null,
|
|
137
|
-
});
|
|
120
|
+
);
|
|
138
121
|
|
|
139
|
-
// 6. Record session approval
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for (const pattern of descriptor.sessionApproval.patterns) {
|
|
144
|
-
deps.approveSessionRule(descriptor.sessionApproval.surface, pattern);
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
deps.approveSessionRule(
|
|
148
|
-
descriptor.sessionApproval.surface,
|
|
149
|
-
descriptor.sessionApproval.pattern,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
122
|
+
// 6. Record session approval — tell the store; it owns the per-pattern loop
|
|
123
|
+
// hasSessionApproval already implies gateResult.action === "allow"
|
|
124
|
+
if (hasSessionApproval && descriptor.sessionApproval) {
|
|
125
|
+
deps.recordSessionApproval(descriptor.sessionApproval);
|
|
153
126
|
}
|
|
154
127
|
|
|
155
128
|
if (gateResult.action === "block") {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getPathBearingToolPath, PATH_BEARING_TOOLS } from "#src/path-utils";
|
|
2
2
|
import { suggestSessionPattern } from "#src/pattern-suggest";
|
|
3
3
|
import { formatAskPrompt } from "#src/permission-prompts";
|
|
4
|
-
import {
|
|
4
|
+
import { SessionApproval } from "#src/session-approval";
|
|
5
|
+
import type { ToolPreviewFormatter } from "#src/tool-preview-formatter";
|
|
5
6
|
import type { PermissionCheckResult } from "#src/types";
|
|
6
7
|
import type { GateDescriptor } from "./descriptor";
|
|
7
8
|
import { deriveDecisionValue } from "./helpers";
|
|
@@ -31,8 +32,9 @@ function deriveSuggestionValue(
|
|
|
31
32
|
export function describeToolGate(
|
|
32
33
|
tcc: ToolCallContext,
|
|
33
34
|
check: PermissionCheckResult,
|
|
35
|
+
formatter: ToolPreviewFormatter,
|
|
34
36
|
): GateDescriptor {
|
|
35
|
-
const permissionLogContext = getPermissionLogContext(
|
|
37
|
+
const permissionLogContext = formatter.getPermissionLogContext(
|
|
36
38
|
check,
|
|
37
39
|
tcc.input,
|
|
38
40
|
PATH_BEARING_TOOLS,
|
|
@@ -48,6 +50,7 @@ export function describeToolGate(
|
|
|
48
50
|
check,
|
|
49
51
|
tcc.agentName ?? undefined,
|
|
50
52
|
tcc.input,
|
|
53
|
+
formatter,
|
|
51
54
|
);
|
|
52
55
|
|
|
53
56
|
return {
|
|
@@ -59,10 +62,10 @@ export function describeToolGate(
|
|
|
59
62
|
agentName: tcc.agentName ?? undefined,
|
|
60
63
|
input: tcc.input,
|
|
61
64
|
},
|
|
62
|
-
sessionApproval:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
sessionApproval: SessionApproval.single(
|
|
66
|
+
suggestion.surface,
|
|
67
|
+
suggestion.pattern,
|
|
68
|
+
),
|
|
66
69
|
promptDetails: {
|
|
67
70
|
source: "tool_call",
|
|
68
71
|
agentName: tcc.agentName,
|