@gotgenes/pi-permission-system 7.1.3 → 7.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 +14 -0
- package/package.json +7 -3
- package/src/active-agent.ts +1 -1
- package/src/bash-arity.ts +1 -0
- package/src/config-modal.ts +2 -0
- package/src/forwarded-permissions/io.ts +4 -2
- package/src/forwarded-permissions/polling.ts +8 -7
- package/src/handlers/before-agent-start.ts +7 -6
- package/src/handlers/gates/bash-external-directory.ts +4 -4
- package/src/handlers/gates/bash-path-extractor.ts +4 -6
- package/src/handlers/gates/bash-path.ts +5 -5
- package/src/handlers/gates/descriptor.ts +6 -6
- package/src/handlers/gates/external-directory.ts +2 -2
- package/src/handlers/gates/helpers.ts +2 -2
- package/src/handlers/gates/path.ts +4 -4
- package/src/handlers/gates/runner.ts +7 -4
- package/src/handlers/gates/skill-read.ts +5 -5
- package/src/handlers/gates/tool.ts +5 -5
- package/src/handlers/lifecycle.ts +9 -8
- package/src/handlers/permission-gate-handler.ts +12 -7
- package/src/logging.ts +3 -0
- package/src/node-modules-discovery.ts +1 -1
- package/src/normalize.ts +1 -0
- package/src/permission-event-rpc.ts +2 -0
- package/src/permission-manager.ts +7 -6
- package/src/permission-merge.ts +4 -2
- package/src/permission-prompter.ts +3 -0
- package/src/permission-prompts.ts +1 -1
- package/src/policy-loader.ts +5 -5
- package/src/service.ts +1 -0
- package/src/skill-prompt-sanitizer.ts +3 -3
- package/src/tool-registry.ts +1 -1
- package/src/wildcard-matcher.ts +2 -2
- package/src/yolo-mode.ts +2 -1
- package/{tests → test}/active-agent.test.ts +1 -1
- package/{tests → test}/bash-arity.test.ts +4 -4
- package/{tests → test}/bash-external-directory.test.ts +3 -3
- package/{tests → test}/common.test.ts +1 -1
- package/{tests → test}/config-loader.test.ts +1 -1
- package/{tests → test}/config-modal.test.ts +9 -11
- package/{tests → test}/config-paths.test.ts +1 -1
- package/{tests → test}/config-reporter.test.ts +4 -4
- package/{tests → test}/denial-messages.test.ts +2 -2
- package/{tests → test}/expand-home.test.ts +1 -1
- package/{tests → test}/extension-config.test.ts +1 -1
- package/{tests → test}/extension-paths.test.ts +2 -2
- package/{tests → test}/forwarded-permissions/io.test.ts +2 -2
- package/{tests → test}/forwarding-manager.test.ts +1 -1
- package/{tests → test}/handlers/before-agent-start.test.ts +5 -5
- package/{tests → test}/handlers/external-directory-integration.test.ts +8 -8
- package/{tests → test}/handlers/external-directory-session-dedup.test.ts +6 -6
- package/{tests → test}/handlers/gates/bash-external-directory.test.ts +5 -8
- package/{tests → test}/handlers/gates/bash-path.test.ts +6 -9
- package/{tests → test}/handlers/gates/external-directory-messages.test.ts +1 -1
- package/{tests → test}/handlers/gates/external-directory.test.ts +4 -7
- package/{tests → test}/handlers/gates/helpers.test.ts +1 -1
- package/{tests → test}/handlers/gates/path.test.ts +6 -6
- package/{tests → test}/handlers/gates/runner.test.ts +5 -5
- package/{tests → test}/handlers/gates/skill-read.test.ts +12 -14
- package/{tests → test}/handlers/gates/tool.test.ts +4 -4
- package/{tests → test}/handlers/input-events.test.ts +7 -7
- package/{tests → test}/handlers/input.test.ts +5 -5
- package/{tests → test}/handlers/lifecycle.test.ts +2 -2
- package/{tests → test}/handlers/tool-call-events.test.ts +7 -7
- package/{tests → test}/handlers/tool-call.test.ts +5 -5
- package/{tests → test}/input-normalizer.test.ts +2 -2
- package/{tests → test}/mcp-targets.test.ts +1 -1
- package/{tests → test}/node-modules-discovery.test.ts +1 -1
- package/{tests → test}/normalize.test.ts +1 -1
- package/{tests → test}/path-utils.test.ts +1 -1
- package/{tests → test}/pattern-suggest.test.ts +1 -1
- package/{tests → test}/permission-dialog.test.ts +1 -1
- package/{tests → test}/permission-event-rpc.test.ts +4 -3
- package/{tests → test}/permission-events.test.ts +6 -4
- package/{tests → test}/permission-forwarding.test.ts +2 -1
- package/{tests → test}/permission-gate.test.ts +2 -2
- package/{tests → test}/permission-manager-unified.test.ts +9 -7
- package/{tests → test}/permission-merge.test.ts +1 -1
- package/{tests → test}/permission-prompter.test.ts +4 -4
- package/{tests → test}/permission-prompts.test.ts +4 -4
- package/{tests → test}/permission-session.test.ts +9 -9
- package/{tests → test}/permission-system.test.ts +21 -21
- package/{tests → test}/pi-infrastructure-read.test.ts +2 -2
- package/{tests → test}/policy-loader.test.ts +1 -1
- package/{tests → test}/rule.test.ts +2 -2
- package/{tests → test}/runtime.test.ts +4 -4
- package/{tests → test}/service.test.ts +4 -4
- package/{tests → test}/session-logger.test.ts +2 -2
- package/{tests → test}/session-rules.test.ts +2 -2
- package/{tests → test}/session-start.test.ts +4 -4
- package/{tests → test}/skill-prompt-sanitizer.test.ts +3 -3
- package/{tests → test}/subagent-context.test.ts +2 -2
- package/{tests → test}/synthesize.test.ts +3 -3
- package/{tests → test}/system-prompt-sanitizer.test.ts +1 -1
- package/{tests → test}/tool-input-preview.test.ts +3 -3
- package/{tests → test}/tool-registry.test.ts +1 -1
- package/{tests → test}/wildcard-matcher.test.ts +1 -1
- package/{tests → test}/yolo-mode.test.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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
|
+
## [7.2.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.1.4...pi-permission-system-v7.2.0) (2026-05-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add eslint config with type-aware rules and import enforcement ([4fb3cc6](https://github.com/gotgenes/pi-packages/commit/4fb3cc678da10d350b85c464318476ba9ae99dca))
|
|
14
|
+
|
|
15
|
+
## [7.1.4](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.1.3...pi-permission-system-v7.1.4) (2026-05-23)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* add package.json imports field for #src/#test path aliases ([#157](https://github.com/gotgenes/pi-packages/issues/157)) ([75b4598](https://github.com/gotgenes/pi-packages/commit/75b45980810583452f7741678359c004900c8bd0))
|
|
21
|
+
|
|
8
22
|
## [7.1.3](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.1.2...pi-permission-system-v7.1.3) (2026-05-23)
|
|
9
23
|
|
|
10
24
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-permission-system",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/service.ts"
|
|
8
8
|
},
|
|
9
|
+
"imports": {
|
|
10
|
+
"#src/*": "./src/*",
|
|
11
|
+
"#test/*": "./test/*"
|
|
12
|
+
},
|
|
9
13
|
"files": [
|
|
10
14
|
"src",
|
|
11
|
-
"
|
|
15
|
+
"test",
|
|
12
16
|
"config/config.example.json",
|
|
13
17
|
"schemas/permissions.schema.json",
|
|
14
18
|
"README.md",
|
|
@@ -73,6 +77,6 @@
|
|
|
73
77
|
"test": "vitest run",
|
|
74
78
|
"test:watch": "vitest",
|
|
75
79
|
"lint:md": "rumdl check *.md docs/**/*.md",
|
|
76
|
-
"lint": "biome check . && pnpm run lint:md"
|
|
80
|
+
"lint": "biome check . && eslint . && pnpm run lint:md"
|
|
77
81
|
}
|
|
78
82
|
}
|
package/src/active-agent.ts
CHANGED
package/src/bash-arity.ts
CHANGED
|
@@ -175,6 +175,7 @@ export function prefix(tokens: string[]): string[] {
|
|
|
175
175
|
.map((t) => t.toLowerCase())
|
|
176
176
|
.join(" ");
|
|
177
177
|
const arity = ARITY[key];
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ARITY record type hides that a key may be absent at runtime
|
|
178
179
|
if (arity !== undefined) {
|
|
179
180
|
return tokens.slice(0, Math.min(arity, tokens.length));
|
|
180
181
|
}
|
package/src/config-modal.ts
CHANGED
|
@@ -61,6 +61,7 @@ function toOnOff(value: boolean): string {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function formatRulesSummary(rules: Ruleset): string {
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- origin may be absent despite its type
|
|
64
65
|
const configRules = rules.filter((r) => r.layer === "config" && r.origin);
|
|
65
66
|
if (configRules.length === 0) return "";
|
|
66
67
|
const formatted = configRules
|
|
@@ -171,6 +172,7 @@ async function openSettingsModal(
|
|
|
171
172
|
margin: 1,
|
|
172
173
|
};
|
|
173
174
|
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- ctx.ui.custom<void> is valid; rule does not allow void in generic fn call type args
|
|
174
176
|
await ctx.ui.custom<void>(
|
|
175
177
|
(_tui, _theme, _keybindings, done) => {
|
|
176
178
|
let current = controller.getConfig();
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
writeFileSync,
|
|
10
10
|
} from "node:fs";
|
|
11
11
|
|
|
12
|
-
import { isPermissionDecisionState } from "
|
|
12
|
+
import { isPermissionDecisionState } from "#src/permission-dialog";
|
|
13
13
|
import {
|
|
14
14
|
createPermissionForwardingLocation,
|
|
15
15
|
type ForwardedPermissionRequest,
|
|
16
16
|
type ForwardedPermissionResponse,
|
|
17
17
|
type PermissionForwardingLocation,
|
|
18
|
-
} from "
|
|
18
|
+
} from "#src/permission-forwarding";
|
|
19
19
|
|
|
20
20
|
type LogFn = (event: string, details: Record<string, unknown>) => void;
|
|
21
21
|
|
|
@@ -262,6 +262,7 @@ export function readForwardedPermissionRequest(
|
|
|
262
262
|
const raw = readFileSync(filePath, "utf-8");
|
|
263
263
|
const parsed = JSON.parse(raw) as Partial<ForwardedPermissionRequest>;
|
|
264
264
|
if (
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JSON.parse can return null for the string "null"
|
|
265
266
|
!parsed ||
|
|
266
267
|
typeof parsed.id !== "string" ||
|
|
267
268
|
typeof parsed.createdAt !== "number" ||
|
|
@@ -303,6 +304,7 @@ export function readForwardedPermissionResponse(
|
|
|
303
304
|
const raw = readFileSync(filePath, "utf-8");
|
|
304
305
|
const parsed = JSON.parse(raw) as Partial<ForwardedPermissionResponse>;
|
|
305
306
|
if (
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JSON.parse can return null for the string "null"
|
|
306
308
|
!parsed ||
|
|
307
309
|
typeof parsed.approved !== "boolean" ||
|
|
308
310
|
!isPermissionDecisionState(parsed.state) ||
|
|
@@ -5,12 +5,12 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import {
|
|
6
6
|
getActiveAgentName,
|
|
7
7
|
getActiveAgentNameFromSystemPrompt,
|
|
8
|
-
} from "
|
|
9
|
-
import { toRecord } from "
|
|
8
|
+
} from "#src/active-agent";
|
|
9
|
+
import { toRecord } from "#src/common";
|
|
10
10
|
import type {
|
|
11
11
|
PermissionPromptDecision,
|
|
12
12
|
RequestPermissionOptions,
|
|
13
|
-
} from "
|
|
13
|
+
} from "#src/permission-dialog";
|
|
14
14
|
import {
|
|
15
15
|
type ForwardedPermissionRequest,
|
|
16
16
|
type ForwardedPermissionResponse,
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
PERMISSION_FORWARDING_TIMEOUT_MS,
|
|
20
20
|
resolvePermissionForwardingTargetSessionId,
|
|
21
21
|
SUBAGENT_PARENT_SESSION_ENV_CANDIDATES,
|
|
22
|
-
} from "
|
|
23
|
-
import { isSubagentExecutionContext } from "
|
|
22
|
+
} from "#src/permission-forwarding";
|
|
23
|
+
import { isSubagentExecutionContext } from "#src/subagent-context";
|
|
24
24
|
|
|
25
25
|
import {
|
|
26
26
|
cleanupPermissionForwardingLocationIfEmpty,
|
|
@@ -69,6 +69,7 @@ function getContextSystemPrompt(ctx: ExtensionContext): string | undefined {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
try {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- getSystemPrompt is a Pi SDK accessor returning any
|
|
72
73
|
const systemPrompt = getSystemPrompt.call(ctx);
|
|
73
74
|
return typeof systemPrompt === "string" ? systemPrompt : undefined;
|
|
74
75
|
} catch (error) {
|
|
@@ -135,8 +136,8 @@ export async function waitForForwardedPermissionApproval(
|
|
|
135
136
|
|
|
136
137
|
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}-${process.pid}`;
|
|
137
138
|
const requesterAgentName =
|
|
138
|
-
getActiveAgentName(ctx)
|
|
139
|
-
getActiveAgentNameFromSystemPrompt(getContextSystemPrompt(ctx))
|
|
139
|
+
getActiveAgentName(ctx) ??
|
|
140
|
+
getActiveAgentNameFromSystemPrompt(getContextSystemPrompt(ctx)) ??
|
|
140
141
|
"unknown";
|
|
141
142
|
const request: ForwardedPermissionRequest = {
|
|
142
143
|
id: requestId,
|
|
@@ -6,12 +6,12 @@ import type {
|
|
|
6
6
|
import {
|
|
7
7
|
createActiveToolsCacheKey,
|
|
8
8
|
createBeforeAgentStartPromptStateKey,
|
|
9
|
-
} from "
|
|
10
|
-
import type { PermissionSession } from "
|
|
11
|
-
import { resolveSkillPromptEntries } from "
|
|
12
|
-
import { sanitizeAvailableToolsSection } from "
|
|
13
|
-
import { getToolNameFromValue, type ToolRegistry } from "
|
|
14
|
-
import type { PermissionState } from "
|
|
9
|
+
} from "#src/before-agent-start-cache";
|
|
10
|
+
import type { PermissionSession } from "#src/permission-session";
|
|
11
|
+
import { resolveSkillPromptEntries } from "#src/skill-prompt-sanitizer";
|
|
12
|
+
import { sanitizeAvailableToolsSection } from "#src/system-prompt-sanitizer";
|
|
13
|
+
import { getToolNameFromValue, type ToolRegistry } from "#src/tool-registry";
|
|
14
|
+
import type { PermissionState } from "#src/types";
|
|
15
15
|
|
|
16
16
|
/** Minimal subset of BeforeAgentStartEvent used by this handler. */
|
|
17
17
|
interface BeforeAgentStartPayload {
|
|
@@ -45,6 +45,7 @@ export class AgentPrepHandler {
|
|
|
45
45
|
private readonly toolRegistry: ToolRegistry,
|
|
46
46
|
) {}
|
|
47
47
|
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
48
49
|
async handle(
|
|
49
50
|
event: BeforeAgentStartPayload,
|
|
50
51
|
ctx: ExtensionContext,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getNonEmptyString, toRecord } from "
|
|
2
|
-
import type { Rule } from "
|
|
3
|
-
import { deriveApprovalPattern } from "
|
|
4
|
-
import type { PermissionCheckResult } from "
|
|
1
|
+
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
|
+
import type { Rule } from "#src/rule";
|
|
3
|
+
import { deriveApprovalPattern } from "#src/session-rules";
|
|
4
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
5
5
|
import { extractExternalPathsFromBashCommand } from "./bash-path-extractor";
|
|
6
6
|
import type { GateResult } from "./descriptor";
|
|
7
7
|
import { formatBashExternalDirectoryAskPrompt } from "./external-directory-messages";
|
|
@@ -4,7 +4,7 @@ import { basename } from "node:path";
|
|
|
4
4
|
import {
|
|
5
5
|
isPathOutsideWorkingDirectory,
|
|
6
6
|
normalizePathForComparison,
|
|
7
|
-
} from "
|
|
7
|
+
} from "#src/path-utils";
|
|
8
8
|
|
|
9
9
|
// ── tree-sitter-bash lazy parser ───────────────────────────────────────────
|
|
10
10
|
|
|
@@ -40,13 +40,11 @@ async function initParser(): Promise<TSParser> {
|
|
|
40
40
|
const bashWasm = req.resolve("tree-sitter-bash/tree-sitter-bash.wasm");
|
|
41
41
|
const bash = await Language.load(bashWasm);
|
|
42
42
|
parser.setLanguage(bash);
|
|
43
|
-
return parser
|
|
43
|
+
return parser;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function getParser(): Promise<TSParser> {
|
|
47
|
-
|
|
48
|
-
parserPromise = initParser();
|
|
49
|
-
}
|
|
47
|
+
parserPromise ??= initParser();
|
|
50
48
|
return parserPromise;
|
|
51
49
|
}
|
|
52
50
|
|
|
@@ -83,7 +81,7 @@ function resolveNodeText(node: TSNode): string {
|
|
|
83
81
|
case "raw_string": {
|
|
84
82
|
// Strip surrounding single quotes: 'content' → content
|
|
85
83
|
const t = node.text;
|
|
86
|
-
if (t.length >= 2 && t
|
|
84
|
+
if (t.length >= 2 && t.startsWith("'") && t.endsWith("'")) {
|
|
87
85
|
return t.slice(1, -1);
|
|
88
86
|
}
|
|
89
87
|
return t;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getNonEmptyString, toRecord } from "
|
|
2
|
-
import type { Rule } from "
|
|
3
|
-
import { deriveApprovalPattern } from "
|
|
4
|
-
import type { PermissionCheckResult } from "
|
|
1
|
+
import { getNonEmptyString, toRecord } from "#src/common";
|
|
2
|
+
import type { Rule } from "#src/rule";
|
|
3
|
+
import { deriveApprovalPattern } from "#src/session-rules";
|
|
4
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
5
5
|
import { extractTokensForPathRules } from "./bash-path-extractor";
|
|
6
6
|
import type { GateResult } from "./descriptor";
|
|
7
7
|
import { formatPathAskPrompt } from "./path";
|
|
@@ -73,7 +73,7 @@ export async function describeBashPathGate(
|
|
|
73
73
|
worstToken = token;
|
|
74
74
|
break; // Short-circuit on deny.
|
|
75
75
|
}
|
|
76
|
-
if (check.state === "ask" &&
|
|
76
|
+
if (check.state === "ask" && worstCheck?.state !== "ask") {
|
|
77
77
|
worstCheck = check;
|
|
78
78
|
worstToken = token;
|
|
79
79
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { DenialContext } from "
|
|
2
|
-
import type { PermissionPromptDecision } from "
|
|
3
|
-
import type { PermissionDecisionEvent } from "
|
|
4
|
-
import type { PromptPermissionDetails } from "
|
|
5
|
-
import type { Rule } from "
|
|
6
|
-
import type { PermissionCheckResult, PermissionState } from "
|
|
1
|
+
import type { DenialContext } from "#src/denial-messages";
|
|
2
|
+
import type { PermissionPromptDecision } from "#src/permission-dialog";
|
|
3
|
+
import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
4
|
+
import type { PromptPermissionDetails } from "#src/permission-prompter";
|
|
5
|
+
import type { Rule } from "#src/rule";
|
|
6
|
+
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
7
7
|
|
|
8
8
|
// ── Descriptor types ───────────────────────────────────────────────────────
|
|
9
9
|
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
isPathOutsideWorkingDirectory,
|
|
4
4
|
isPiInfrastructureRead,
|
|
5
5
|
normalizePathForComparison,
|
|
6
|
-
} from "
|
|
7
|
-
import { deriveApprovalPattern } from "
|
|
6
|
+
} from "#src/path-utils";
|
|
7
|
+
import { deriveApprovalPattern } from "#src/session-rules";
|
|
8
8
|
import type { GateResult } from "./descriptor";
|
|
9
9
|
import { formatExternalDirectoryAskPrompt } from "./external-directory-messages";
|
|
10
10
|
import type { ToolCallContext } from "./types";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PermissionDecisionResolution } from "
|
|
2
|
-
import type { PermissionCheckResult } from "
|
|
1
|
+
import type { PermissionDecisionResolution } from "#src/permission-events";
|
|
2
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Derive the human-readable value for a decision event from a check result.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getPathBearingToolPath } from "
|
|
2
|
-
import type { Rule } from "
|
|
3
|
-
import { deriveApprovalPattern } from "
|
|
4
|
-
import type { PermissionCheckResult } from "
|
|
1
|
+
import { getPathBearingToolPath } from "#src/path-utils";
|
|
2
|
+
import type { Rule } from "#src/rule";
|
|
3
|
+
import { deriveApprovalPattern } from "#src/session-rules";
|
|
4
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
5
5
|
import type { GateDescriptor, GateResult } from "./descriptor";
|
|
6
6
|
import type { ToolCallContext } from "./types";
|
|
7
7
|
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
formatDenyReason,
|
|
3
3
|
formatUnavailableReason,
|
|
4
4
|
formatUserDeniedReason,
|
|
5
|
-
} from "
|
|
6
|
-
import type { PermissionPromptDecision } from "
|
|
7
|
-
import { applyPermissionGate } from "
|
|
8
|
-
import type { PermissionCheckResult } from "
|
|
5
|
+
} from "#src/denial-messages";
|
|
6
|
+
import type { PermissionPromptDecision } from "#src/permission-dialog";
|
|
7
|
+
import { applyPermissionGate } from "#src/permission-gate";
|
|
8
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
9
9
|
import type { GateDescriptor, GateRunnerDeps } from "./descriptor";
|
|
10
10
|
import { deriveResolution } from "./helpers";
|
|
11
11
|
import type { GateOutcome } from "./types";
|
|
@@ -61,6 +61,7 @@ export async function runGateCheck(
|
|
|
61
61
|
value: descriptor.decision.value,
|
|
62
62
|
result: "allow",
|
|
63
63
|
resolution: "session_approved",
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
|
|
64
65
|
origin: check.origin ?? null,
|
|
65
66
|
agentName: agentName ?? null,
|
|
66
67
|
matchedPattern: check.matchedPattern ?? null,
|
|
@@ -107,6 +108,7 @@ export async function runGateCheck(
|
|
|
107
108
|
autoApproved = decision.autoApproved === true;
|
|
108
109
|
return decision;
|
|
109
110
|
},
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger methods are plain functions; no this-binding issue
|
|
110
112
|
writeLog: deps.writeReviewLog,
|
|
111
113
|
logContext: { ...descriptor.logContext, agentName },
|
|
112
114
|
messages,
|
|
@@ -128,6 +130,7 @@ export async function runGateCheck(
|
|
|
128
130
|
canConfirm,
|
|
129
131
|
autoApproved,
|
|
130
132
|
),
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
|
|
131
134
|
origin: check.origin ?? null,
|
|
132
135
|
agentName: agentName ?? null,
|
|
133
136
|
matchedPattern: check.matchedPattern ?? null,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { toRecord } from "
|
|
2
|
-
import { normalizePathForComparison } from "
|
|
3
|
-
import { formatSkillPathAskPrompt } from "
|
|
4
|
-
import type { SkillPromptEntry } from "
|
|
5
|
-
import { findSkillPathMatch } from "
|
|
1
|
+
import { toRecord } from "#src/common";
|
|
2
|
+
import { normalizePathForComparison } from "#src/path-utils";
|
|
3
|
+
import { formatSkillPathAskPrompt } from "#src/permission-prompts";
|
|
4
|
+
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
5
|
+
import { findSkillPathMatch } from "#src/skill-prompt-sanitizer";
|
|
6
6
|
import type { GateDescriptor } from "./descriptor";
|
|
7
7
|
import type { ToolCallContext } from "./types";
|
|
8
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { getPathBearingToolPath, PATH_BEARING_TOOLS } from "
|
|
2
|
-
import { suggestSessionPattern } from "
|
|
3
|
-
import { formatAskPrompt } from "
|
|
4
|
-
import { getPermissionLogContext } from "
|
|
5
|
-
import type { PermissionCheckResult } from "
|
|
1
|
+
import { getPathBearingToolPath, PATH_BEARING_TOOLS } from "#src/path-utils";
|
|
2
|
+
import { suggestSessionPattern } from "#src/pattern-suggest";
|
|
3
|
+
import { formatAskPrompt } from "#src/permission-prompts";
|
|
4
|
+
import { getPermissionLogContext } from "#src/tool-input-preview";
|
|
5
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
6
6
|
import type { GateDescriptor } from "./descriptor";
|
|
7
7
|
import { deriveDecisionValue } from "./helpers";
|
|
8
8
|
import type { ToolCallContext } from "./types";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
import type { PermissionSession } from "
|
|
4
|
-
import { PERMISSION_SYSTEM_STATUS_KEY } from "
|
|
3
|
+
import type { PermissionSession } from "#src/permission-session";
|
|
4
|
+
import { PERMISSION_SYSTEM_STATUS_KEY } from "#src/status";
|
|
5
5
|
|
|
6
6
|
/** Minimal subset of SessionStartEvent used by this handler. */
|
|
7
7
|
interface SessionStartPayload {
|
|
@@ -26,7 +26,7 @@ export class SessionLifecycleHandler {
|
|
|
26
26
|
private readonly cleanupRpc: () => void,
|
|
27
27
|
) {}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
handleSessionStart(
|
|
30
30
|
event: SessionStartPayload,
|
|
31
31
|
ctx: ExtensionContext,
|
|
32
32
|
): Promise<void> {
|
|
@@ -48,13 +48,12 @@ export class SessionLifecycleHandler {
|
|
|
48
48
|
cwd: ctx.cwd,
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
+
return Promise.resolve();
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
event: ResourcesDiscoverPayload,
|
|
55
|
-
): Promise<void> {
|
|
54
|
+
handleResourcesDiscover(event: ResourcesDiscoverPayload): Promise<void> {
|
|
56
55
|
if (event.reason !== "reload") {
|
|
57
|
-
return;
|
|
56
|
+
return Promise.resolve();
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
const { session } = this;
|
|
@@ -64,9 +63,10 @@ export class SessionLifecycleHandler {
|
|
|
64
63
|
reason: event.reason,
|
|
65
64
|
cwd: session.getRuntimeContext()?.cwd ?? null,
|
|
66
65
|
});
|
|
66
|
+
return Promise.resolve();
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
handleSessionShutdown(): Promise<void> {
|
|
70
70
|
const { session } = this;
|
|
71
71
|
const ctx = session.getRuntimeContext();
|
|
72
72
|
if (ctx) {
|
|
@@ -74,5 +74,6 @@ export class SessionLifecycleHandler {
|
|
|
74
74
|
}
|
|
75
75
|
session.shutdown();
|
|
76
76
|
this.cleanupRpc();
|
|
77
|
+
return Promise.resolve();
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -3,24 +3,24 @@ import type {
|
|
|
3
3
|
InputEventResult,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
|
-
import { toRecord } from "
|
|
6
|
+
import { toRecord } from "#src/common";
|
|
7
7
|
import {
|
|
8
8
|
emitDecisionEvent,
|
|
9
9
|
type PermissionEventBus,
|
|
10
|
-
} from "
|
|
11
|
-
import { applyPermissionGate } from "
|
|
12
|
-
import type { PromptPermissionDetails } from "
|
|
10
|
+
} from "#src/permission-events";
|
|
11
|
+
import { applyPermissionGate } from "#src/permission-gate";
|
|
12
|
+
import type { PromptPermissionDetails } from "#src/permission-prompter";
|
|
13
13
|
import {
|
|
14
14
|
formatMissingToolNameReason,
|
|
15
15
|
formatSkillAskPrompt,
|
|
16
16
|
formatUnknownToolReason,
|
|
17
|
-
} from "
|
|
18
|
-
import type { PermissionSession } from "
|
|
17
|
+
} from "#src/permission-prompts";
|
|
18
|
+
import type { PermissionSession } from "#src/permission-session";
|
|
19
19
|
import {
|
|
20
20
|
checkRequestedToolRegistration,
|
|
21
21
|
getToolNameFromValue,
|
|
22
22
|
type ToolRegistry,
|
|
23
|
-
} from "
|
|
23
|
+
} from "#src/tool-registry";
|
|
24
24
|
import { describeBashExternalDirectoryGate } from "./gates/bash-external-directory";
|
|
25
25
|
import { describeBashPathGate } from "./gates/bash-path";
|
|
26
26
|
import type { GateRunnerDeps } from "./gates/descriptor";
|
|
@@ -104,6 +104,7 @@ export class PermissionGateHandler {
|
|
|
104
104
|
session.prompt(ctx, details);
|
|
105
105
|
const emitDecision: GateRunnerDeps["emitDecision"] = (e) =>
|
|
106
106
|
emitDecisionEvent(this.events, e);
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger.review is a plain function closure; no this-binding issue
|
|
107
108
|
const writeReviewLog = session.logger.review;
|
|
108
109
|
const checkPermission: GateRunnerDeps["checkPermission"] = (
|
|
109
110
|
surface,
|
|
@@ -305,6 +306,7 @@ export class PermissionGateHandler {
|
|
|
305
306
|
skillInputAutoApproved = decision.autoApproved === true;
|
|
306
307
|
return decision;
|
|
307
308
|
},
|
|
309
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger.review is a plain function closure; no this-binding issue
|
|
308
310
|
writeLog: session.logger.review,
|
|
309
311
|
logContext: {
|
|
310
312
|
source: "skill_input",
|
|
@@ -324,6 +326,7 @@ export class PermissionGateHandler {
|
|
|
324
326
|
surface: "skill",
|
|
325
327
|
value: skillName,
|
|
326
328
|
result: skillInputGate.action === "allow" ? "allow" : "deny",
|
|
329
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive fallback; TypeScript narrows check.state before the ternary's else branch */
|
|
327
330
|
resolution:
|
|
328
331
|
check.state === "allow"
|
|
329
332
|
? "policy_allow"
|
|
@@ -336,6 +339,8 @@ export class PermissionGateHandler {
|
|
|
336
339
|
: skillInputCanConfirm
|
|
337
340
|
? "user_denied"
|
|
338
341
|
: "confirmation_unavailable",
|
|
342
|
+
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
|
|
339
344
|
origin: check.origin ?? null,
|
|
340
345
|
agentName: agentName ?? null,
|
|
341
346
|
matchedPattern: check.matchedPattern ?? null,
|
package/src/logging.ts
CHANGED
|
@@ -21,12 +21,15 @@ export function safeJsonStringify(value: unknown): string | undefined {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (typeof currentValue === "object" && currentValue !== null) {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- JSON.stringify replacer receives any; currentValue is narrowed to object here
|
|
24
25
|
if (seen.has(currentValue)) {
|
|
25
26
|
return "[Circular]";
|
|
26
27
|
}
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- same as above
|
|
27
29
|
seen.add(currentValue);
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- JSON.stringify replacer must return any
|
|
30
33
|
return currentValue;
|
|
31
34
|
});
|
|
32
35
|
}
|
|
@@ -40,7 +40,7 @@ function discoverGlobalNodeModulesViaSubprocess(): string | null {
|
|
|
40
40
|
timeout: 5000,
|
|
41
41
|
stdio: ["ignore", "pipe", "ignore"],
|
|
42
42
|
});
|
|
43
|
-
const root = result.stdout
|
|
43
|
+
const root = result.stdout.trim();
|
|
44
44
|
if (result.status === 0 && root && existsSync(root)) {
|
|
45
45
|
return root;
|
|
46
46
|
}
|
package/src/normalize.ts
CHANGED
|
@@ -20,6 +20,7 @@ export function normalizeFlatConfig(permission: FlatPermissionConfig): Ruleset {
|
|
|
20
20
|
if (isPermissionState(value)) {
|
|
21
21
|
rules.push({ surface, pattern: "*", action: value, origin: "builtin" });
|
|
22
22
|
}
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive null check; value type does not include null but runtime JSON may
|
|
23
24
|
} else if (typeof value === "object" && value !== null) {
|
|
24
25
|
for (const [pattern, action] of Object.entries(value)) {
|
|
25
26
|
if (isPermissionState(action)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-deprecated -- this module implements the deprecated event-bus RPC channel; references to its own deprecated symbols are intentional */
|
|
1
2
|
/**
|
|
2
3
|
* Permission event bus RPC handlers.
|
|
3
4
|
*
|
|
@@ -112,6 +113,7 @@ function handleCheckRpc(
|
|
|
112
113
|
const data: PermissionsCheckReplyData = {
|
|
113
114
|
result: result.state,
|
|
114
115
|
matchedPattern: result.matchedPattern ?? null,
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the reply record
|
|
115
117
|
origin: result.origin ?? null,
|
|
116
118
|
};
|
|
117
119
|
events.emit(replyChannel, successReply(data));
|
|
@@ -78,7 +78,7 @@ export class PermissionManager {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
private resolvePermissions(agentName?: string): ResolvedPermissions {
|
|
81
|
-
const cacheKey = agentName
|
|
81
|
+
const cacheKey = agentName ?? "__global__";
|
|
82
82
|
const stamp = this.loader.getCacheStamp(agentName);
|
|
83
83
|
const cached = this.resolvedPermissionsCache.get(cacheKey);
|
|
84
84
|
if (cached?.stamp === stamp) {
|
|
@@ -107,17 +107,19 @@ export class PermissionManager {
|
|
|
107
107
|
|
|
108
108
|
for (const [surface, value] of Object.entries(scope.permission)) {
|
|
109
109
|
const baseVal = mergedPermission[surface];
|
|
110
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive null/type checks; config values may differ at runtime */
|
|
110
111
|
const bothObjects =
|
|
111
112
|
typeof baseVal === "object" &&
|
|
112
113
|
baseVal !== null &&
|
|
113
114
|
typeof value === "object" &&
|
|
114
115
|
value !== null;
|
|
116
|
+
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
115
117
|
|
|
116
118
|
if (bothObjects) {
|
|
117
119
|
// Shallow-merge: each incoming pattern is attributed to this scope;
|
|
118
120
|
// existing patterns from lower scopes keep their earlier origin.
|
|
119
121
|
if (!origins.has(surface)) origins.set(surface, new Map());
|
|
120
|
-
for (const pattern of Object.keys(value
|
|
122
|
+
for (const pattern of Object.keys(value)) {
|
|
121
123
|
origins.get(surface)!.set(pattern, scopeName);
|
|
122
124
|
}
|
|
123
125
|
} else {
|
|
@@ -125,10 +127,9 @@ export class PermissionManager {
|
|
|
125
127
|
const surfaceOrigins = new Map<string, RuleOrigin>();
|
|
126
128
|
if (typeof value === "string") {
|
|
127
129
|
surfaceOrigins.set("*", scopeName);
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive null check
|
|
128
131
|
} else if (typeof value === "object" && value !== null) {
|
|
129
|
-
for (const pattern of Object.keys(
|
|
130
|
-
value as Record<string, unknown>,
|
|
131
|
-
)) {
|
|
132
|
+
for (const pattern of Object.keys(value)) {
|
|
132
133
|
surfaceOrigins.set(pattern, scopeName);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -146,7 +147,7 @@ export class PermissionManager {
|
|
|
146
147
|
// The "*" key feeds synthesizeDefaults() only — it is NOT included as a
|
|
147
148
|
// config rule so that extension tools fall through to source:"default".
|
|
148
149
|
const universalFallback = isPermissionState(mergedPermission["*"])
|
|
149
|
-
?
|
|
150
|
+
? mergedPermission["*"]
|
|
150
151
|
: DEFAULT_UNIVERSAL_FALLBACK;
|
|
151
152
|
// Track which scope contributed the universal fallback.
|
|
152
153
|
const universalFallbackOrigin: RuleOrigin =
|
package/src/permission-merge.ts
CHANGED
|
@@ -12,15 +12,17 @@ export function mergeFlatPermissions(
|
|
|
12
12
|
const merged: FlatPermissionConfig = { ...base };
|
|
13
13
|
for (const [key, value] of Object.entries(override)) {
|
|
14
14
|
const baseVal = merged[key];
|
|
15
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive null/type checks; config values may differ at runtime */
|
|
15
16
|
if (
|
|
16
17
|
typeof baseVal === "object" &&
|
|
17
18
|
baseVal !== null &&
|
|
18
19
|
typeof value === "object" &&
|
|
19
20
|
value !== null
|
|
20
21
|
) {
|
|
22
|
+
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
21
23
|
merged[key] = {
|
|
22
|
-
...
|
|
23
|
-
...
|
|
24
|
+
...baseVal,
|
|
25
|
+
...value,
|
|
24
26
|
};
|
|
25
27
|
} else {
|
|
26
28
|
merged[key] = value;
|
|
@@ -148,6 +148,7 @@ export class PermissionPrompter implements PermissionPrompterApi {
|
|
|
148
148
|
private buildForwardingDeps(): PermissionForwardingDeps {
|
|
149
149
|
const { deps } = this;
|
|
150
150
|
const logger: ForwardedPermissionLogger = {
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger methods are plain function closures; no this-binding issue
|
|
151
152
|
writeReviewLog: deps.writeReviewLog,
|
|
152
153
|
writeDebugLog: () => undefined,
|
|
153
154
|
};
|
|
@@ -155,7 +156,9 @@ export class PermissionPrompter implements PermissionPrompterApi {
|
|
|
155
156
|
forwardingDir: deps.forwardingDir,
|
|
156
157
|
subagentSessionsDir: deps.subagentSessionsDir,
|
|
157
158
|
logger,
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- logger methods are plain function closures; no this-binding issue
|
|
158
160
|
writeReviewLog: deps.writeReviewLog,
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- same as above
|
|
159
162
|
requestPermissionDecisionFromUi: deps.requestPermissionDecisionFromUi,
|
|
160
163
|
shouldAutoApprove: () => false,
|
|
161
164
|
};
|