pi-permission-system 0.3.0 → 0.4.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 +32 -0
- package/README.md +1 -1
- package/package.json +61 -61
- package/src/index.ts +7 -0
- package/src/status.ts +20 -0
- package/src/system-prompt-sanitizer.ts +116 -78
- package/src/test.ts +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,38 @@ 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
|
+
## [0.4.0] - 2026-04-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- System prompt sanitizer now removes inactive tool guidelines from the `Guidelines:` section
|
|
12
|
+
- Guideline filtering based on allowed tools (e.g., removes task/mcp/bash/write guidance when tools are denied)
|
|
13
|
+
- New `TOOL_GUIDELINE_RULES` configuration for extensible guideline filtering
|
|
14
|
+
- Helper functions: `findSection()`, `removeLineSection()`, `sanitizeGuidelinesSection()`
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to ^0.64.0
|
|
18
|
+
- Updated `@sinclair/typebox` peer dependency to ^0.34.49
|
|
19
|
+
- Refactored system prompt sanitizer to handle both `Available tools:` and `Guidelines:` sections
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- Added tests for system prompt sanitizer removing Available tools section
|
|
23
|
+
- Added tests for guideline filtering based on allowed tools
|
|
24
|
+
- Added tests for inactive built-in write/edit/task/mcp guidance removal
|
|
25
|
+
|
|
26
|
+
## [0.3.1] - 2026-03-24
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Permission system status module (`status.ts`) to expose yolo mode status to the UI
|
|
30
|
+
- `syncPermissionSystemStatus()` function to sync status with the TUI status bar
|
|
31
|
+
- `PERMISSION_SYSTEM_STATUS_KEY` and `PERMISSION_SYSTEM_YOLO_STATUS_VALUE` constants for status identification
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Integrated status sync on config load, config save, and extension unload
|
|
35
|
+
- Status is only exposed when yolo mode is enabled
|
|
36
|
+
|
|
37
|
+
### Tests
|
|
38
|
+
- Added test for permission-system status being undefined when yolo mode is disabled and "yolo" when enabled
|
|
39
|
+
|
|
8
40
|
## [0.3.0] - 2026-03-23
|
|
9
41
|
|
|
10
42
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🔐 pi-permission-system
|
|
2
2
|
|
|
3
|
-
[](package.json)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
|
|
6
6
|
Permission enforcement extension for the Pi coding agent that provides centralized, deterministic permission gates for tool, bash, MCP, skill, and special operations.
|
package/package.json
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-permission-system",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.ts"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.ts",
|
|
12
|
-
"src",
|
|
13
|
-
"config.json",
|
|
14
|
-
"config/config.example.json",
|
|
15
|
-
"schemas/permissions.schema.json",
|
|
16
|
-
"asset",
|
|
17
|
-
"README.md",
|
|
18
|
-
"CHANGELOG.md",
|
|
19
|
-
"LICENSE"
|
|
20
|
-
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
|
|
23
|
-
"lint": "npm run build",
|
|
24
|
-
"test": "bun ./src/test.ts && bun ./src/config-modal-test.ts",
|
|
25
|
-
"check": "npm run lint && npm run test"
|
|
26
|
-
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"pi-package",
|
|
29
|
-
"pi",
|
|
30
|
-
"pi-extension",
|
|
31
|
-
"permissions",
|
|
32
|
-
"policy",
|
|
33
|
-
"coding-agent"
|
|
34
|
-
],
|
|
35
|
-
"author": "MasuRii",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
40
|
-
},
|
|
41
|
-
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
42
|
-
"bugs": {
|
|
43
|
-
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
44
|
-
},
|
|
45
|
-
"engines": {
|
|
46
|
-
"node": ">=20"
|
|
47
|
-
},
|
|
48
|
-
"publishConfig": {
|
|
49
|
-
"access": "public"
|
|
50
|
-
},
|
|
51
|
-
"pi": {
|
|
52
|
-
"extensions": [
|
|
53
|
-
"./index.ts"
|
|
54
|
-
]
|
|
55
|
-
},
|
|
56
|
-
"peerDependencies": {
|
|
57
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
58
|
-
"@mariozechner/pi-tui": "^0.
|
|
59
|
-
"@sinclair/typebox": "^0.34.
|
|
60
|
-
}
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-permission-system",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"config.json",
|
|
14
|
+
"config/config.example.json",
|
|
15
|
+
"schemas/permissions.schema.json",
|
|
16
|
+
"asset",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
|
|
23
|
+
"lint": "npm run build",
|
|
24
|
+
"test": "bun ./src/test.ts && bun ./src/config-modal-test.ts",
|
|
25
|
+
"check": "npm run lint && npm run test"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"pi-package",
|
|
29
|
+
"pi",
|
|
30
|
+
"pi-extension",
|
|
31
|
+
"permissions",
|
|
32
|
+
"policy",
|
|
33
|
+
"coding-agent"
|
|
34
|
+
],
|
|
35
|
+
"author": "MasuRii",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"pi": {
|
|
52
|
+
"extensions": [
|
|
53
|
+
"./index.ts"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@mariozechner/pi-coding-agent": "^0.64.0",
|
|
58
|
+
"@mariozechner/pi-tui": "^0.64.0",
|
|
59
|
+
"@sinclair/typebox": "^0.34.49"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { PermissionManager } from "./permission-manager.js";
|
|
|
29
29
|
import { sanitizeAvailableToolsSection } from "./system-prompt-sanitizer.js";
|
|
30
30
|
import { checkRequestedToolRegistration, getToolNameFromValue } from "./tool-registry.js";
|
|
31
31
|
import type { PermissionCheckResult, PermissionState } from "./types.js";
|
|
32
|
+
import { PERMISSION_SYSTEM_STATUS_KEY, syncPermissionSystemStatus } from "./status.js";
|
|
32
33
|
import { canResolveAskPermissionRequest, shouldAutoApprovePermissionState } from "./yolo-mode.js";
|
|
33
34
|
|
|
34
35
|
const PI_AGENT_DIR = join(homedir(), ".pi", "agent");
|
|
@@ -906,6 +907,10 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
906
907
|
const result = loadPermissionSystemConfig();
|
|
907
908
|
setExtensionConfig(result.config);
|
|
908
909
|
|
|
910
|
+
if (runtimeContext?.hasUI) {
|
|
911
|
+
syncPermissionSystemStatus(runtimeContext, result.config);
|
|
912
|
+
}
|
|
913
|
+
|
|
909
914
|
if (result.warning && result.warning !== lastConfigWarning) {
|
|
910
915
|
lastConfigWarning = result.warning;
|
|
911
916
|
notifyWarning(result.warning);
|
|
@@ -933,6 +938,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
933
938
|
}
|
|
934
939
|
|
|
935
940
|
setExtensionConfig(normalized);
|
|
941
|
+
syncPermissionSystemStatus(ctx, normalized);
|
|
936
942
|
lastConfigWarning = null;
|
|
937
943
|
|
|
938
944
|
writeDebugLog("config.saved", {
|
|
@@ -1142,6 +1148,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1142
1148
|
});
|
|
1143
1149
|
|
|
1144
1150
|
pi.on("session_shutdown", async () => {
|
|
1151
|
+
runtimeContext?.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
|
|
1145
1152
|
runtimeContext = null;
|
|
1146
1153
|
stopForwardedPermissionPolling();
|
|
1147
1154
|
});
|
package/src/status.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { EXTENSION_ID, type PermissionSystemExtensionConfig } from "./extension-config.js";
|
|
4
|
+
import { isYoloModeEnabled } from "./yolo-mode.js";
|
|
5
|
+
|
|
6
|
+
export const PERMISSION_SYSTEM_STATUS_KEY = EXTENSION_ID;
|
|
7
|
+
export const PERMISSION_SYSTEM_YOLO_STATUS_VALUE = "yolo";
|
|
8
|
+
|
|
9
|
+
type PermissionStatusContext = Pick<ExtensionContext, "hasUI" | "ui"> | Pick<ExtensionCommandContext, "ui">;
|
|
10
|
+
|
|
11
|
+
export function getPermissionSystemStatus(config: PermissionSystemExtensionConfig): string | undefined {
|
|
12
|
+
return isYoloModeEnabled(config) ? PERMISSION_SYSTEM_YOLO_STATUS_VALUE : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function syncPermissionSystemStatus(
|
|
16
|
+
ctx: PermissionStatusContext,
|
|
17
|
+
config: PermissionSystemExtensionConfig,
|
|
18
|
+
): void {
|
|
19
|
+
ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, getPermissionSystemStatus(config));
|
|
20
|
+
}
|
|
@@ -3,18 +3,59 @@ export interface SanitizeSystemPromptResult {
|
|
|
3
3
|
removed: boolean;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
type
|
|
7
|
-
name: string;
|
|
8
|
-
lines: string[];
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type ToolPromptSection = {
|
|
6
|
+
type LineSection = {
|
|
12
7
|
start: number;
|
|
13
8
|
end: number;
|
|
14
|
-
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type GuidelineRule = {
|
|
12
|
+
matches: (guideline: string) => boolean;
|
|
13
|
+
shouldKeep: (allowedTools: ReadonlySet<string>) => boolean;
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
const AVAILABLE_TOOLS_SECTION_HEADER = "Available tools:";
|
|
17
|
+
const GUIDELINES_SECTION_HEADER = "Guidelines:";
|
|
18
|
+
|
|
19
|
+
const TOOL_GUIDELINE_RULES: readonly GuidelineRule[] = [
|
|
20
|
+
{
|
|
21
|
+
matches: (guideline) => guideline === "use bash for file operations like ls, rg, find",
|
|
22
|
+
shouldKeep: (allowedTools) => allowedTools.has("bash"),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
matches: (guideline) => guideline === "prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)",
|
|
26
|
+
shouldKeep: (allowedTools) =>
|
|
27
|
+
allowedTools.has("bash") && (allowedTools.has("grep") || allowedTools.has("find") || allowedTools.has("ls")),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
matches: (guideline) =>
|
|
31
|
+
guideline === "use read to examine files before editing. you must use this tool instead of cat or sed."
|
|
32
|
+
|| guideline === "use read to examine files instead of cat or sed.",
|
|
33
|
+
shouldKeep: (allowedTools) => allowedTools.has("read"),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
matches: (guideline) => guideline === "use edit for precise changes (old text must match exactly)",
|
|
37
|
+
shouldKeep: (allowedTools) => allowedTools.has("edit"),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
matches: (guideline) => guideline === "use write only for new files or complete rewrites",
|
|
41
|
+
shouldKeep: (allowedTools) => allowedTools.has("write"),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
matches: (guideline) =>
|
|
45
|
+
guideline === "when summarizing your actions, output plain text directly - do not use cat or bash to display what you did",
|
|
46
|
+
shouldKeep: (allowedTools) => allowedTools.has("edit") || allowedTools.has("write"),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
matches: (guideline) =>
|
|
50
|
+
guideline === "use task when work should be delegated to one or more specialized agents instead of handled entirely in the current session.",
|
|
51
|
+
shouldKeep: (allowedTools) => allowedTools.has("task"),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
matches: (guideline) =>
|
|
55
|
+
guideline === "use mcp for mcp discovery first: search by capability, describe one exact tool name, then call it.",
|
|
56
|
+
shouldKeep: (allowedTools) => allowedTools.has("mcp"),
|
|
57
|
+
},
|
|
58
|
+
];
|
|
18
59
|
|
|
19
60
|
function normalizePrompt(prompt: string): string {
|
|
20
61
|
return (prompt || "").replace(/\r\n/g, "\n");
|
|
@@ -24,77 +65,89 @@ function collapseExtraBlankLines(text: string): string {
|
|
|
24
65
|
return text.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
25
66
|
}
|
|
26
67
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
68
|
+
function normalizeGuidelineText(line: string): string {
|
|
69
|
+
return line.trim().replace(/^[-*]\s+/, "").replace(/\s+/g, " ").toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isTopLevelSectionHeader(line: string): boolean {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
return trimmed.length > 0 && trimmed.endsWith(":") && !trimmed.startsWith("-");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function findSection(lines: readonly string[], header: string): LineSection | null {
|
|
78
|
+
const start = lines.findIndex((line) => line.trim() === header);
|
|
30
79
|
if (start === -1) {
|
|
31
80
|
return null;
|
|
32
81
|
}
|
|
33
82
|
|
|
34
|
-
|
|
35
|
-
let index = start + 1;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const line = lines[index];
|
|
39
|
-
const trimmed = line.trim();
|
|
40
|
-
|
|
41
|
-
if (!trimmed) {
|
|
42
|
-
index += 1;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!trimmed.startsWith("- ")) {
|
|
83
|
+
let end = lines.length;
|
|
84
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
85
|
+
if (isTopLevelSectionHeader(lines[index])) {
|
|
86
|
+
end = index;
|
|
47
87
|
break;
|
|
48
88
|
}
|
|
89
|
+
}
|
|
49
90
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
return { start, end };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function removeLineSection(lines: readonly string[], section: LineSection | null): { lines: string[]; removed: boolean } {
|
|
95
|
+
if (!section) {
|
|
96
|
+
return { lines: [...lines], removed: false };
|
|
97
|
+
}
|
|
54
98
|
|
|
55
|
-
|
|
56
|
-
|
|
99
|
+
return {
|
|
100
|
+
lines: [...lines.slice(0, section.start), ...lines.slice(section.end)],
|
|
101
|
+
removed: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const nextTrimmed = nextLine.trim();
|
|
105
|
+
function shouldKeepGuideline(line: string, allowedTools: ReadonlySet<string>): boolean {
|
|
106
|
+
const normalized = normalizeGuidelineText(line);
|
|
61
107
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
108
|
+
for (const rule of TOOL_GUIDELINE_RULES) {
|
|
109
|
+
if (rule.matches(normalized)) {
|
|
110
|
+
return rule.shouldKeep(allowedTools);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
67
113
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
71
116
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
function sanitizeGuidelinesSection(lines: readonly string[], allowedTools: ReadonlySet<string>): { lines: string[]; removed: boolean } {
|
|
118
|
+
const section = findSection(lines, GUIDELINES_SECTION_HEADER);
|
|
119
|
+
if (!section) {
|
|
120
|
+
return { lines: [...lines], removed: false };
|
|
121
|
+
}
|
|
75
122
|
|
|
76
|
-
|
|
77
|
-
|
|
123
|
+
const before = lines.slice(0, section.start + 1);
|
|
124
|
+
const after = lines.slice(section.end);
|
|
125
|
+
const body = lines.slice(section.start + 1, section.end);
|
|
126
|
+
const filteredBody = body.filter((line) => {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (!trimmed.startsWith("- ")) {
|
|
129
|
+
return true;
|
|
78
130
|
}
|
|
79
131
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
132
|
+
return shouldKeepGuideline(line, allowedTools);
|
|
133
|
+
});
|
|
83
134
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
135
|
+
const removed = filteredBody.length !== body.length;
|
|
136
|
+
if (!removed) {
|
|
137
|
+
return { lines: [...lines], removed: false };
|
|
88
138
|
}
|
|
89
139
|
|
|
90
|
-
|
|
91
|
-
|
|
140
|
+
const hasBullet = filteredBody.some((line) => line.trim().startsWith("- "));
|
|
141
|
+
if (!hasBullet) {
|
|
142
|
+
return {
|
|
143
|
+
lines: [...lines.slice(0, section.start), ...after],
|
|
144
|
+
removed: true,
|
|
145
|
+
};
|
|
92
146
|
}
|
|
93
147
|
|
|
94
148
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
entries,
|
|
149
|
+
lines: [...before, ...filteredBody, ...after],
|
|
150
|
+
removed: true,
|
|
98
151
|
};
|
|
99
152
|
}
|
|
100
153
|
|
|
@@ -102,29 +155,14 @@ export function sanitizeAvailableToolsSection(
|
|
|
102
155
|
systemPrompt: string,
|
|
103
156
|
allowedToolNames: readonly string[],
|
|
104
157
|
): SanitizeSystemPromptResult {
|
|
105
|
-
const section = parseAvailableToolsSection(systemPrompt);
|
|
106
|
-
if (!section) {
|
|
107
|
-
return { prompt: systemPrompt, removed: false };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
158
|
const allowedTools = new Set(allowedToolNames.map((toolName) => toolName.trim()).filter(Boolean));
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const lines = normalizePrompt(systemPrompt).split("\n");
|
|
118
|
-
const replacement = visibleEntries.length > 0
|
|
119
|
-
? [lines[section.start], ...visibleEntries.flatMap((entry) => entry.lines)]
|
|
120
|
-
: [];
|
|
159
|
+
const normalizedLines = normalizePrompt(systemPrompt).split("\n");
|
|
160
|
+
const removedToolsSection = removeLineSection(normalizedLines, findSection(normalizedLines, AVAILABLE_TOOLS_SECTION_HEADER));
|
|
161
|
+
const sanitizedGuidelines = sanitizeGuidelinesSection(removedToolsSection.lines, allowedTools);
|
|
162
|
+
const removed = removedToolsSection.removed || sanitizedGuidelines.removed;
|
|
121
163
|
|
|
122
164
|
return {
|
|
123
|
-
prompt: collapseExtraBlankLines(
|
|
124
|
-
|
|
125
|
-
...replacement,
|
|
126
|
-
...lines.slice(section.end),
|
|
127
|
-
].join("\n")),
|
|
128
|
-
removed: true,
|
|
165
|
+
prompt: removed ? collapseExtraBlankLines(sanitizedGuidelines.lines.join("\n")) : systemPrompt,
|
|
166
|
+
removed,
|
|
129
167
|
};
|
|
130
168
|
}
|
package/src/test.ts
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
} from "./permission-forwarding.js";
|
|
14
14
|
import { PermissionManager } from "./permission-manager.js";
|
|
15
15
|
import { checkRequestedToolRegistration, getToolNameFromValue } from "./tool-registry.js";
|
|
16
|
+
import { getPermissionSystemStatus } from "./status.js";
|
|
17
|
+
import { sanitizeAvailableToolsSection } from "./system-prompt-sanitizer.js";
|
|
16
18
|
import type { GlobalPermissionConfig } from "./types.js";
|
|
17
19
|
import { canResolveAskPermissionRequest, shouldAutoApprovePermissionState } from "./yolo-mode.js";
|
|
18
20
|
|
|
@@ -198,6 +200,72 @@ runTest("Yolo mode resolves ask permissions without UI or delegation forwarding"
|
|
|
198
200
|
);
|
|
199
201
|
});
|
|
200
202
|
|
|
203
|
+
runTest("Permission-system status is only exposed when yolo mode is enabled", () => {
|
|
204
|
+
assert.equal(getPermissionSystemStatus(DEFAULT_EXTENSION_CONFIG), undefined);
|
|
205
|
+
assert.equal(
|
|
206
|
+
getPermissionSystemStatus({ ...DEFAULT_EXTENSION_CONFIG, yoloMode: true }),
|
|
207
|
+
"yolo",
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
runTest("System prompt sanitizer removes the Available tools section and surrounding boilerplate", () => {
|
|
212
|
+
const prompt = [
|
|
213
|
+
"Available tools:",
|
|
214
|
+
"- read: Read file contents",
|
|
215
|
+
"- mcp: Discover, inspect, and call MCP tools across configured servers",
|
|
216
|
+
"",
|
|
217
|
+
"In addition to the tools above, you may have access to other custom tools depending on the project.",
|
|
218
|
+
"",
|
|
219
|
+
"Guidelines:",
|
|
220
|
+
"- Use mcp for MCP discovery first: search by capability, describe one exact tool name, then call it.",
|
|
221
|
+
"- Be concise in your responses",
|
|
222
|
+
].join("\n");
|
|
223
|
+
|
|
224
|
+
const result = sanitizeAvailableToolsSection(prompt, ["read", "mcp"]);
|
|
225
|
+
|
|
226
|
+
assert.equal(result.removed, true);
|
|
227
|
+
assert.equal(result.prompt.includes("Available tools:"), false);
|
|
228
|
+
assert.equal(result.prompt.includes("In addition to the tools above"), false);
|
|
229
|
+
assert.match(result.prompt, /Guidelines:/);
|
|
230
|
+
assert.match(result.prompt, /Use mcp for MCP discovery first/i);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
runTest("System prompt sanitizer removes denied tool guidelines while keeping global guidance", () => {
|
|
234
|
+
const prompt = [
|
|
235
|
+
"Guidelines:",
|
|
236
|
+
"- Use task when work SHOULD be delegated to one or more specialized agents instead of handled entirely in the current session.",
|
|
237
|
+
"- Use mcp for MCP discovery first: search by capability, describe one exact tool name, then call it.",
|
|
238
|
+
"- Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)",
|
|
239
|
+
"- Be concise in your responses",
|
|
240
|
+
"- Show file paths clearly when working with files",
|
|
241
|
+
].join("\n");
|
|
242
|
+
|
|
243
|
+
const result = sanitizeAvailableToolsSection(prompt, ["bash", "grep", "mcp"]);
|
|
244
|
+
|
|
245
|
+
assert.equal(result.removed, true);
|
|
246
|
+
assert.equal(result.prompt.includes("Use task when work SHOULD"), false);
|
|
247
|
+
assert.match(result.prompt, /Use mcp for MCP discovery first/i);
|
|
248
|
+
assert.match(result.prompt, /Prefer grep\/find\/ls tools over bash/i);
|
|
249
|
+
assert.match(result.prompt, /Be concise in your responses/);
|
|
250
|
+
assert.match(result.prompt, /Show file paths clearly when working with files/);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
runTest("System prompt sanitizer removes inactive built-in write guidance", () => {
|
|
254
|
+
const prompt = [
|
|
255
|
+
"Guidelines:",
|
|
256
|
+
"- Use write only for new files or complete rewrites",
|
|
257
|
+
"- When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did",
|
|
258
|
+
"- Be concise in your responses",
|
|
259
|
+
].join("\n");
|
|
260
|
+
|
|
261
|
+
const result = sanitizeAvailableToolsSection(prompt, ["read"]);
|
|
262
|
+
|
|
263
|
+
assert.equal(result.removed, true);
|
|
264
|
+
assert.equal(result.prompt.includes("Use write only for new files or complete rewrites"), false);
|
|
265
|
+
assert.equal(result.prompt.includes("do NOT use cat or bash to display what you did"), false);
|
|
266
|
+
assert.match(result.prompt, /Be concise in your responses/);
|
|
267
|
+
});
|
|
268
|
+
|
|
201
269
|
runTest("Permission-system logger respects debug toggle and keeps review log enabled by default", () => {
|
|
202
270
|
const baseDir = mkdtempSync(join(tmpdir(), "pi-permission-system-logs-"));
|
|
203
271
|
const logsDir = join(baseDir, "logs");
|