@gotgenes/pi-permission-system 5.5.0 → 5.6.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 CHANGED
@@ -5,6 +5,29 @@ 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
+ ## [5.6.0](https://github.com/gotgenes/pi-permission-system/compare/v5.5.1...v5.6.0) (2026-05-07)
9
+
10
+
11
+ ### Features
12
+
13
+ * implement runGateCheck gate runner ([#118](https://github.com/gotgenes/pi-permission-system/issues/118)) ([46da4b6](https://github.com/gotgenes/pi-permission-system/commit/46da4b616cddef22d9e1d198a3aac9c640d62bac))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * plan gate runner extraction ([#118](https://github.com/gotgenes/pi-permission-system/issues/118)) ([8c0eb18](https://github.com/gotgenes/pi-permission-system/commit/8c0eb1881dabc19dba65f72ba5c30ae02e4070a0))
19
+ * **retro:** add retro notes for issue [#111](https://github.com/gotgenes/pi-permission-system/issues/111) ([e327323](https://github.com/gotgenes/pi-permission-system/commit/e327323187822e779454eeafd7372c6256f869ba))
20
+ * update target architecture for gate runner ([#118](https://github.com/gotgenes/pi-permission-system/issues/118)) ([40e1b1b](https://github.com/gotgenes/pi-permission-system/commit/40e1b1b016730e9be3da18fcb831001a2f498081))
21
+
22
+ ## [5.5.1](https://github.com/gotgenes/pi-permission-system/compare/v5.5.0...v5.5.1) (2026-05-07)
23
+
24
+
25
+ ### Documentation
26
+
27
+ * plan narrow handler dependencies by splitting ExtensionRuntime ([#111](https://github.com/gotgenes/pi-permission-system/issues/111)) ([cb44dee](https://github.com/gotgenes/pi-permission-system/commit/cb44deea383fbcca8c8ba25bd4098f7dd8c35bfb))
28
+ * **retro:** add retro notes for issue [#108](https://github.com/gotgenes/pi-permission-system/issues/108) ([e967fd9](https://github.com/gotgenes/pi-permission-system/commit/e967fd96c6ed0a9ac0a4f0037889f1d9c88ebd97))
29
+ * update target architecture for gate interfaces and SessionState ([#111](https://github.com/gotgenes/pi-permission-system/issues/111)) ([85620c4](https://github.com/gotgenes/pi-permission-system/commit/85620c4486fdcf756c7039c15bdd8a295621ad37))
30
+
8
31
  ## [5.5.0](https://github.com/gotgenes/pi-permission-system/compare/v5.4.0...v5.5.0) (2026-05-07)
9
32
 
10
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -41,12 +41,12 @@ export async function handleBeforeAgentStart(
41
41
  event: BeforeAgentStartPayload,
42
42
  ctx: ExtensionContext,
43
43
  ): Promise<BeforeAgentStartEventResult> {
44
- deps.runtime.runtimeContext = ctx;
44
+ deps.session.runtimeContext = ctx;
45
45
  deps.refreshExtensionConfig(ctx);
46
46
  deps.startForwardedPermissionPolling(ctx);
47
47
 
48
48
  const agentName = deps.resolveAgentName(ctx, event.systemPrompt);
49
- const { permissionManager } = deps.runtime;
49
+ const { permissionManager } = deps.session;
50
50
  const allTools = deps.getAllTools();
51
51
  const allowedTools: string[] = [];
52
52
 
@@ -63,12 +63,12 @@ export async function handleBeforeAgentStart(
63
63
  const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
64
64
  if (
65
65
  shouldApplyCachedAgentStartState(
66
- deps.runtime.lastActiveToolsCacheKey,
66
+ deps.session.lastActiveToolsCacheKey,
67
67
  activeToolsCacheKey,
68
68
  )
69
69
  ) {
70
70
  deps.setActiveTools(allowedTools);
71
- deps.runtime.lastActiveToolsCacheKey = activeToolsCacheKey;
71
+ deps.session.lastActiveToolsCacheKey = activeToolsCacheKey;
72
72
  }
73
73
 
74
74
  const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
@@ -83,14 +83,14 @@ export async function handleBeforeAgentStart(
83
83
 
84
84
  if (
85
85
  !shouldApplyCachedAgentStartState(
86
- deps.runtime.lastPromptStateCacheKey,
86
+ deps.session.lastPromptStateCacheKey,
87
87
  promptStateCacheKey,
88
88
  )
89
89
  ) {
90
90
  return {};
91
91
  }
92
92
 
93
- deps.runtime.lastPromptStateCacheKey = promptStateCacheKey;
93
+ deps.session.lastPromptStateCacheKey = promptStateCacheKey;
94
94
 
95
95
  const toolPromptResult = sanitizeAvailableToolsSection(
96
96
  event.systemPrompt,
@@ -102,7 +102,7 @@ export async function handleBeforeAgentStart(
102
102
  agentName,
103
103
  ctx.cwd,
104
104
  );
105
- deps.runtime.activeSkillEntries = skillPromptResult.entries;
105
+ deps.session.activeSkillEntries = skillPromptResult.entries;
106
106
 
107
107
  if (skillPromptResult.prompt !== event.systemPrompt) {
108
108
  return { systemPrompt: skillPromptResult.prompt };
@@ -5,23 +5,34 @@ import {
5
5
  formatBashExternalDirectoryDenyReason,
6
6
  formatExternalDirectoryHardStopHint,
7
7
  } from "../../external-directory";
8
- import type { PermissionPromptDecision } from "../../permission-dialog";
9
- import { applyPermissionGate } from "../../permission-gate";
8
+ import type { Rule } from "../../rule";
10
9
  import { deriveApprovalPattern } from "../../session-rules";
11
- import type { HandlerDeps } from "../types";
12
- import type { GateOutcome, ToolCallContext } from "./types";
10
+ import type { PermissionCheckResult } from "../../types";
11
+ import type { GateResult } from "./descriptor";
12
+ import type { ToolCallContext } from "./types";
13
+
14
+ /** Function type for checkPermission used by the descriptor factory. */
15
+ type CheckPermissionFn = (
16
+ surface: string,
17
+ input: unknown,
18
+ agentName?: string,
19
+ sessionRules?: Rule[],
20
+ ) => PermissionCheckResult;
13
21
 
14
22
  /**
15
- * Evaluate the bash external-directory permission gate.
23
+ * Build a pure descriptor for the bash external-directory permission gate.
16
24
  *
17
25
  * Extracts paths from a bash command and checks whether any reference
18
26
  * directories outside the working directory. Returns `null` when the gate
19
27
  * does not apply (tool is not bash, no CWD, or no external paths found).
28
+ * Returns a `GateBypass` when all paths are session-covered.
29
+ * Returns a `GateDescriptor` with multi-pattern sessionApproval for uncovered paths.
20
30
  */
21
- export async function evaluateBashExternalDirectoryGate(
31
+ export async function describeBashExternalDirectoryGate(
22
32
  tcc: ToolCallContext,
23
- deps: HandlerDeps,
24
- ): Promise<GateOutcome | null> {
33
+ checkPermission: CheckPermissionFn,
34
+ getSessionRuleset: () => Rule[],
35
+ ): Promise<GateResult> {
25
36
  if (tcc.toolName !== "bash" || !tcc.cwd) return null;
26
37
 
27
38
  const command = getNonEmptyString(toRecord(tcc.input).command);
@@ -33,10 +44,10 @@ export async function evaluateBashExternalDirectoryGate(
33
44
  );
34
45
  if (externalPaths.length === 0) return null;
35
46
 
36
- const bashSessionRules = deps.runtime.sessionRules.getRuleset();
47
+ const bashSessionRules = getSessionRuleset();
37
48
  const uncoveredPaths = externalPaths.filter(
38
49
  (p) =>
39
- deps.runtime.permissionManager.checkPermission(
50
+ checkPermission(
40
51
  "external_directory",
41
52
  { path: p },
42
53
  tcc.agentName ?? undefined,
@@ -45,63 +56,42 @@ export async function evaluateBashExternalDirectoryGate(
45
56
  );
46
57
 
47
58
  if (uncoveredPaths.length === 0) {
48
- deps.runtime.writeReviewLog("permission_request.session_approved", {
49
- source: "tool_call",
50
- toolCallId: tcc.toolCallId,
51
- toolName: tcc.toolName,
52
- agentName: tcc.agentName,
53
- command,
54
- externalPaths,
55
- resolution: "session_approved",
56
- });
57
- return null;
59
+ return {
60
+ action: "allow",
61
+ log: {
62
+ event: "permission_request.session_approved",
63
+ details: {
64
+ source: "tool_call",
65
+ toolCallId: tcc.toolCallId,
66
+ toolName: tcc.toolName,
67
+ agentName: tcc.agentName,
68
+ command,
69
+ externalPaths,
70
+ resolution: "session_approved",
71
+ },
72
+ },
73
+ };
58
74
  }
59
75
 
60
76
  // Get the config-level policy (no path → no session check).
61
- const extCheck = deps.runtime.permissionManager.checkPermission(
77
+ const extCheck = checkPermission(
62
78
  "external_directory",
63
79
  {},
64
80
  tcc.agentName ?? undefined,
65
81
  );
66
82
 
67
- let bashExtDecision: PermissionPromptDecision | null = null;
68
83
  const bashExtMessage = formatBashExternalDirectoryAskPrompt(
69
84
  command,
70
85
  uncoveredPaths,
71
86
  tcc.cwd,
72
87
  tcc.agentName ?? undefined,
73
88
  );
74
- const bashExtGate = await applyPermissionGate({
75
- state: extCheck.state,
76
- canConfirm: deps.canRequestPermissionConfirmation(
77
- deps.runtime.runtimeContext!,
78
- ),
79
- promptForApproval: async () => {
80
- const decision = await deps.promptPermission(
81
- deps.runtime.runtimeContext!,
82
- {
83
- requestId: tcc.toolCallId,
84
- source: "tool_call",
85
- agentName: tcc.agentName,
86
- message: bashExtMessage,
87
- toolCallId: tcc.toolCallId,
88
- toolName: tcc.toolName,
89
- command,
90
- },
91
- );
92
- bashExtDecision = decision;
93
- return decision;
94
- },
95
- writeLog: deps.runtime.writeReviewLog,
96
- logContext: {
97
- source: "tool_call",
98
- toolCallId: tcc.toolCallId,
99
- toolName: tcc.toolName,
100
- agentName: tcc.agentName,
101
- command,
102
- externalPaths: uncoveredPaths,
103
- message: bashExtMessage,
104
- },
89
+
90
+ const patterns = uncoveredPaths.map((p) => deriveApprovalPattern(p));
91
+
92
+ return {
93
+ surface: "external_directory",
94
+ input: {},
105
95
  messages: {
106
96
  denyReason: formatBashExternalDirectoryDenyReason(
107
97
  command,
@@ -117,18 +107,31 @@ export async function evaluateBashExternalDirectoryGate(
117
107
  return `User denied external directory access for bash command '${command}'.${reasonSuffix} ${formatExternalDirectoryHardStopHint()}`;
118
108
  },
119
109
  },
120
- });
121
-
122
- if (bashExtGate.action === "block") {
123
- return { action: "block", reason: bashExtGate.reason };
124
- }
125
-
126
- if (bashExtDecision?.state === "approved_for_session") {
127
- for (const extPath of uncoveredPaths) {
128
- const pattern = deriveApprovalPattern(extPath);
129
- deps.runtime.sessionRules.approve("external_directory", pattern);
130
- }
131
- }
132
-
133
- return { action: "allow" };
110
+ sessionApproval: {
111
+ surface: "external_directory",
112
+ patterns,
113
+ },
114
+ promptDetails: {
115
+ source: "tool_call",
116
+ agentName: tcc.agentName,
117
+ message: bashExtMessage,
118
+ toolCallId: tcc.toolCallId,
119
+ toolName: tcc.toolName,
120
+ command,
121
+ },
122
+ logContext: {
123
+ source: "tool_call",
124
+ toolCallId: tcc.toolCallId,
125
+ toolName: tcc.toolName,
126
+ agentName: tcc.agentName,
127
+ command,
128
+ externalPaths: uncoveredPaths,
129
+ message: bashExtMessage,
130
+ },
131
+ decision: {
132
+ surface: "external_directory",
133
+ value: command,
134
+ },
135
+ preCheck: extCheck,
136
+ };
134
137
  }
@@ -0,0 +1,115 @@
1
+ import type { PermissionPromptDecision } from "../../permission-dialog";
2
+ import type {
3
+ PermissionDecisionEvent,
4
+ PermissionDecisionResolution,
5
+ } from "../../permission-events";
6
+ import type { Rule } from "../../rule";
7
+ import type { PermissionCheckResult, PermissionState } from "../../types";
8
+ import type { PromptPermissionDetails } from "../types";
9
+
10
+ // ── Descriptor types ───────────────────────────────────────────────────────
11
+
12
+ /**
13
+ * Pure output of a gate function — describes what to check and how to present it.
14
+ *
15
+ * The gate runner (`runGateCheck`) uses this descriptor to execute the
16
+ * mechanical check→log→emit→approve cycle without the gate needing to know
17
+ * about logging, event emission, or session-rule recording.
18
+ */
19
+ export interface GateDescriptor {
20
+ /** Permission surface to check (e.g. "bash", "external_directory", "skill"). */
21
+ surface: string;
22
+ /** Input passed to checkPermission. */
23
+ input: unknown;
24
+ /** Message strings/factories for each outcome. */
25
+ messages: {
26
+ denyReason: string;
27
+ unavailableReason: string;
28
+ userDeniedReason: (decision: PermissionPromptDecision) => string;
29
+ };
30
+ /**
31
+ * Session-approval suggestion for "for this session" option.
32
+ * Single pattern or multiple patterns (bash external-directory gate).
33
+ */
34
+ sessionApproval?:
35
+ | { surface: string; pattern: string }
36
+ | { surface: string; patterns: string[] };
37
+ /** Details passed to the interactive permission prompt (requestId is added by the runner). */
38
+ promptDetails: Omit<PromptPermissionDetails, "requestId">;
39
+ /** Extra context fields written to the review log alongside gate outcomes. */
40
+ logContext: Record<string, unknown>;
41
+ /** Surface and value for the decision event (may differ from the check surface). */
42
+ decision: {
43
+ surface: string;
44
+ value: string;
45
+ };
46
+ /**
47
+ * When set, the gate has already resolved the permission state
48
+ * (e.g. from a skill entry match). The runner uses this directly
49
+ * instead of calling checkPermission.
50
+ */
51
+ preResolved?: {
52
+ state: PermissionState;
53
+ };
54
+ /**
55
+ * When set, the runner uses this pre-computed check result directly
56
+ * instead of calling checkPermission. Used when the orchestrator has
57
+ * already performed the check (e.g. to build messages from the result).
58
+ */
59
+ preCheck?: PermissionCheckResult;
60
+ }
61
+
62
+ /**
63
+ * Early allow result — gate has determined the action without needing the runner.
64
+ *
65
+ * Used for cases like Pi infrastructure read bypass where the gate short-circuits
66
+ * with a deterministic allow before reaching the permission check.
67
+ */
68
+ export interface GateBypass {
69
+ action: "allow";
70
+ /** Optional review log entry to emit. */
71
+ log?: { event: string; details: Record<string, unknown> };
72
+ /** Optional decision event to emit. */
73
+ decision?: PermissionDecisionEvent;
74
+ }
75
+
76
+ /** Union of possible gate function return values. */
77
+ export type GateResult = GateDescriptor | GateBypass | null;
78
+
79
+ // ── Runner dependency interface ────────────────────────────────────────────
80
+
81
+ /**
82
+ * Infrastructure dependencies for the gate runner.
83
+ *
84
+ * Built once in the orchestrator and reused for all gates.
85
+ * Handles all side effects: permission checks, logging, event emission,
86
+ * session-rule recording.
87
+ */
88
+ export interface GateRunnerDeps {
89
+ checkPermission(
90
+ surface: string,
91
+ input: unknown,
92
+ agentName?: string,
93
+ sessionRules?: Rule[],
94
+ ): PermissionCheckResult;
95
+ getSessionRuleset(): Rule[];
96
+ approveSessionRule(surface: string, pattern: string): void;
97
+ writeReviewLog(event: string, details: Record<string, unknown>): void;
98
+ emitDecision(event: PermissionDecisionEvent): void;
99
+ canConfirm(): boolean;
100
+ promptPermission(
101
+ details: PromptPermissionDetails,
102
+ ): Promise<PermissionPromptDecision>;
103
+ }
104
+
105
+ // ── Type guard helpers ─────────────────────────────────────────────────────
106
+
107
+ /** Check whether a GateResult is a GateBypass (early allow). */
108
+ export function isGateBypass(result: GateResult): result is GateBypass {
109
+ return result !== null && "action" in result;
110
+ }
111
+
112
+ /** Check whether a GateResult is a GateDescriptor (needs runner). */
113
+ export function isGateDescriptor(result: GateResult): result is GateDescriptor {
114
+ return result !== null && !("action" in result);
115
+ }
@@ -7,24 +7,22 @@ import {
7
7
  isPiInfrastructureRead,
8
8
  normalizePathForComparison,
9
9
  } from "../../external-directory";
10
- import type { PermissionPromptDecision } from "../../permission-dialog";
11
- import { emitDecisionEvent } from "../../permission-events";
12
- import { applyPermissionGate } from "../../permission-gate";
13
10
  import { deriveApprovalPattern } from "../../session-rules";
14
- import type { HandlerDeps } from "../types";
15
- import { deriveResolution } from "./helpers";
16
- import type { GateOutcome, ToolCallContext } from "./types";
11
+ import type { GateResult } from "./descriptor";
12
+ import type { ToolCallContext } from "./types";
17
13
 
18
14
  /**
19
- * Evaluate the external-directory permission gate for file tools.
15
+ * Build a pure descriptor for the external-directory permission gate.
20
16
  *
21
17
  * Returns `null` when the gate does not apply (no CWD, tool is not
22
18
  * path-bearing, or path is inside the working directory).
19
+ * Returns a `GateBypass` for Pi infrastructure reads.
20
+ * Returns a `GateDescriptor` for external paths needing a permission check.
23
21
  */
24
- export async function evaluateExternalDirectoryGate(
22
+ export function describeExternalDirectoryGate(
25
23
  tcc: ToolCallContext,
26
- deps: HandlerDeps,
27
- ): Promise<GateOutcome | null> {
24
+ infraDirs: string[],
25
+ ): GateResult {
28
26
  if (!tcc.cwd) return null;
29
27
 
30
28
  const externalDirectoryPath = getPathBearingToolPath(tcc.toolName, tcc.input);
@@ -40,110 +38,46 @@ export async function evaluateExternalDirectoryGate(
40
38
  );
41
39
 
42
40
  // ── Pi infrastructure read bypass ──────────────────────────────────────
43
- const allInfraDirs = [
44
- ...deps.runtime.piInfrastructureDirs,
45
- ...(deps.runtime.config.piInfrastructureReadPaths ?? []),
46
- ];
47
41
  if (
48
- isPiInfrastructureRead(
49
- tcc.toolName,
50
- normalizedExtPath,
51
- allInfraDirs,
52
- tcc.cwd,
53
- )
42
+ isPiInfrastructureRead(tcc.toolName, normalizedExtPath, infraDirs, tcc.cwd)
54
43
  ) {
55
- deps.runtime.writeReviewLog(
56
- "permission_request.infrastructure_auto_allowed",
57
- {
58
- source: "tool_call",
59
- toolCallId: tcc.toolCallId,
60
- toolName: tcc.toolName,
61
- agentName: tcc.agentName,
62
- path: externalDirectoryPath,
44
+ return {
45
+ action: "allow",
46
+ log: {
47
+ event: "permission_request.infrastructure_auto_allowed",
48
+ details: {
49
+ source: "tool_call",
50
+ toolCallId: tcc.toolCallId,
51
+ toolName: tcc.toolName,
52
+ agentName: tcc.agentName,
53
+ path: externalDirectoryPath,
54
+ },
63
55
  },
64
- );
65
- emitDecisionEvent(deps.events, {
66
- surface: tcc.toolName,
67
- value: externalDirectoryPath,
68
- result: "allow",
69
- resolution: "infrastructure_auto_allowed",
70
- origin: null,
71
- agentName: tcc.agentName ?? null,
72
- matchedPattern: null,
73
- });
74
- return { action: "allow" };
75
- }
76
-
77
- // ── Policy check ───────────────────────────────────────────────────────
78
- const extCheck = deps.runtime.permissionManager.checkPermission(
79
- "external_directory",
80
- { path: normalizedExtPath },
81
- tcc.agentName ?? undefined,
82
- deps.runtime.sessionRules.getRuleset(),
83
- );
84
-
85
- // Session-rule hit
86
- if (extCheck.source === "session") {
87
- deps.runtime.writeReviewLog("permission_request.session_approved", {
88
- source: "tool_call",
89
- toolCallId: tcc.toolCallId,
90
- toolName: tcc.toolName,
91
- agentName: tcc.agentName,
92
- path: externalDirectoryPath,
93
- resolution: "session_approved",
94
- sessionApprovalPattern: extCheck.matchedPattern,
95
- });
96
- emitDecisionEvent(deps.events, {
97
- surface: "external_directory",
98
- value: externalDirectoryPath,
99
- result: "allow",
100
- resolution: "session_approved",
101
- origin: extCheck.origin ?? null,
102
- agentName: tcc.agentName ?? null,
103
- matchedPattern: extCheck.matchedPattern ?? null,
104
- });
105
- return { action: "allow" };
56
+ decision: {
57
+ surface: tcc.toolName,
58
+ value: externalDirectoryPath,
59
+ result: "allow",
60
+ resolution: "infrastructure_auto_allowed",
61
+ origin: null,
62
+ agentName: tcc.agentName ?? null,
63
+ matchedPattern: null,
64
+ },
65
+ };
106
66
  }
107
67
 
108
- // ── Interactive gate ───────────────────────────────────────────────────
109
- let extDirDecision: PermissionPromptDecision | null = null;
68
+ // ── Build descriptor for permission check ───────────────────────────────
110
69
  const extDirMessage = formatExternalDirectoryAskPrompt(
111
70
  tcc.toolName,
112
71
  externalDirectoryPath,
113
72
  tcc.cwd,
114
73
  tcc.agentName ?? undefined,
115
74
  );
116
- const extDirCanConfirm = deps.canRequestPermissionConfirmation(
117
- deps.runtime.runtimeContext!,
118
- );
119
- const extDirGateResult = await applyPermissionGate({
120
- state: extCheck.state,
121
- canConfirm: extDirCanConfirm,
122
- promptForApproval: async () => {
123
- const decision = await deps.promptPermission(
124
- deps.runtime.runtimeContext!,
125
- {
126
- requestId: tcc.toolCallId,
127
- source: "tool_call",
128
- agentName: tcc.agentName,
129
- message: extDirMessage,
130
- toolCallId: tcc.toolCallId,
131
- toolName: tcc.toolName,
132
- path: externalDirectoryPath,
133
- },
134
- );
135
- extDirDecision = decision;
136
- return decision;
137
- },
138
- writeLog: deps.runtime.writeReviewLog,
139
- logContext: {
140
- source: "tool_call",
141
- toolCallId: tcc.toolCallId,
142
- toolName: tcc.toolName,
143
- agentName: tcc.agentName,
144
- path: externalDirectoryPath,
145
- message: extDirMessage,
146
- },
75
+
76
+ const pattern = deriveApprovalPattern(normalizedExtPath);
77
+
78
+ return {
79
+ surface: "external_directory",
80
+ input: { path: normalizedExtPath },
147
81
  messages: {
148
82
  denyReason: formatExternalDirectoryDenyReason(
149
83
  tcc.toolName,
@@ -159,31 +93,29 @@ export async function evaluateExternalDirectoryGate(
159
93
  decision.denialReason,
160
94
  ),
161
95
  },
162
- });
163
-
164
- emitDecisionEvent(deps.events, {
165
- surface: "external_directory",
166
- value: externalDirectoryPath,
167
- result: extDirGateResult.action === "allow" ? "allow" : "deny",
168
- resolution: deriveResolution(
169
- extCheck.state,
170
- extDirGateResult.action,
171
- extDirDecision?.state === "approved_for_session",
172
- extDirCanConfirm,
173
- ),
174
- origin: extCheck.origin ?? null,
175
- agentName: tcc.agentName ?? null,
176
- matchedPattern: extCheck.matchedPattern ?? null,
177
- });
178
-
179
- if (extDirGateResult.action === "block") {
180
- return { action: "block", reason: extDirGateResult.reason };
181
- }
182
-
183
- if (extDirDecision?.state === "approved_for_session") {
184
- const pattern = deriveApprovalPattern(normalizedExtPath);
185
- deps.runtime.sessionRules.approve("external_directory", pattern);
186
- }
187
-
188
- return { action: "allow" };
96
+ sessionApproval: {
97
+ surface: "external_directory",
98
+ pattern,
99
+ },
100
+ promptDetails: {
101
+ source: "tool_call",
102
+ agentName: tcc.agentName,
103
+ message: extDirMessage,
104
+ toolCallId: tcc.toolCallId,
105
+ toolName: tcc.toolName,
106
+ path: externalDirectoryPath,
107
+ },
108
+ logContext: {
109
+ source: "tool_call",
110
+ toolCallId: tcc.toolCallId,
111
+ toolName: tcc.toolName,
112
+ agentName: tcc.agentName,
113
+ path: externalDirectoryPath,
114
+ message: extDirMessage,
115
+ },
116
+ decision: {
117
+ surface: "external_directory",
118
+ value: externalDirectoryPath,
119
+ },
120
+ };
189
121
  }
@@ -1,6 +1,14 @@
1
- export { evaluateBashExternalDirectoryGate } from "./bash-external-directory";
2
- export { evaluateExternalDirectoryGate } from "./external-directory";
1
+ export { describeBashExternalDirectoryGate } from "./bash-external-directory";
2
+ export type {
3
+ GateBypass,
4
+ GateDescriptor,
5
+ GateResult,
6
+ GateRunnerDeps,
7
+ } from "./descriptor";
8
+ export { isGateBypass, isGateDescriptor } from "./descriptor";
9
+ export { describeExternalDirectoryGate } from "./external-directory";
3
10
  export { deriveDecisionValue, deriveResolution } from "./helpers";
4
- export { evaluateSkillReadGate } from "./skill-read";
5
- export { evaluateToolGate } from "./tool";
11
+ export { runGateCheck } from "./runner";
12
+ export { describeSkillReadGate } from "./skill-read";
13
+ export { describeToolGate } from "./tool";
6
14
  export type { GateOutcome, ToolCallContext } from "./types";