@komarspn/pi-permission-system 16.0.2
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 +2234 -0
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/config/config.example.json +39 -0
- package/package.json +82 -0
- package/schemas/permissions.schema.json +158 -0
- package/src/active-agent.ts +72 -0
- package/src/async-cache.ts +21 -0
- package/src/bash-arity.ts +210 -0
- package/src/builtin-tool-input-formatters.ts +82 -0
- package/src/canonicalize-path.ts +30 -0
- package/src/common.ts +121 -0
- package/src/config-loader.ts +432 -0
- package/src/config-modal.ts +259 -0
- package/src/config-paths.ts +47 -0
- package/src/config-reporter.ts +34 -0
- package/src/config-store.ts +222 -0
- package/src/decision-audit.ts +75 -0
- package/src/decision-reporter.ts +41 -0
- package/src/denial-messages.ts +232 -0
- package/src/expand-home.ts +28 -0
- package/src/extension-config.ts +79 -0
- package/src/extension-paths.ts +66 -0
- package/src/forwarded-permissions/io.ts +404 -0
- package/src/forwarded-permissions/permission-forwarder.ts +580 -0
- package/src/forwarding-manager.ts +74 -0
- package/src/gate-prompter.ts +12 -0
- package/src/handlers/before-agent-start.ts +94 -0
- package/src/handlers/gates/bash-command.ts +75 -0
- package/src/handlers/gates/bash-external-directory.ts +127 -0
- package/src/handlers/gates/bash-path-extractor.ts +15 -0
- package/src/handlers/gates/bash-path.ts +152 -0
- package/src/handlers/gates/bash-program.ts +1143 -0
- package/src/handlers/gates/bash-token-classification.ts +105 -0
- package/src/handlers/gates/candidate-check.ts +32 -0
- package/src/handlers/gates/descriptor.ts +81 -0
- package/src/handlers/gates/external-directory-messages.ts +20 -0
- package/src/handlers/gates/external-directory.ts +133 -0
- package/src/handlers/gates/helpers.ts +76 -0
- package/src/handlers/gates/path.ts +91 -0
- package/src/handlers/gates/runner.ts +186 -0
- package/src/handlers/gates/skill-input-gate-pipeline.ts +104 -0
- package/src/handlers/gates/skill-input.ts +46 -0
- package/src/handlers/gates/skill-read.ts +87 -0
- package/src/handlers/gates/tool-call-gate-pipeline.ts +129 -0
- package/src/handlers/gates/tool.ts +102 -0
- package/src/handlers/gates/types.ts +13 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/lifecycle.ts +95 -0
- package/src/handlers/permission-gate-handler.ts +190 -0
- package/src/handlers/tool-call-boundary.ts +91 -0
- package/src/index.ts +225 -0
- package/src/input-normalizer.ts +157 -0
- package/src/logging.ts +113 -0
- package/src/mcp-targets.ts +170 -0
- package/src/node-modules-discovery.ts +76 -0
- package/src/normalize.ts +43 -0
- package/src/path-utils.ts +355 -0
- package/src/pattern-suggest.ts +132 -0
- package/src/permission-dialog.ts +138 -0
- package/src/permission-event-rpc.ts +223 -0
- package/src/permission-events.ts +266 -0
- package/src/permission-forwarding.ts +188 -0
- package/src/permission-gate.ts +94 -0
- package/src/permission-manager.ts +392 -0
- package/src/permission-merge.ts +32 -0
- package/src/permission-prompter.ts +142 -0
- package/src/permission-prompts.ts +93 -0
- package/src/permission-resolver.ts +109 -0
- package/src/permission-session.ts +189 -0
- package/src/permission-ui-prompt.ts +127 -0
- package/src/permissions-service.ts +63 -0
- package/src/persistent-approval-recorder.ts +139 -0
- package/src/policy-loader.ts +350 -0
- package/src/prompting-gateway.ts +104 -0
- package/src/rule.ts +188 -0
- package/src/scope-merge.ts +72 -0
- package/src/service-lifecycle.ts +49 -0
- package/src/service.ts +163 -0
- package/src/session-approval-recorder.ts +6 -0
- package/src/session-approval.ts +43 -0
- package/src/session-logger.ts +91 -0
- package/src/session-rules.ts +79 -0
- package/src/skill-prompt-sanitizer.ts +292 -0
- package/src/status.ts +35 -0
- package/src/subagent-context.ts +104 -0
- package/src/subagent-lifecycle-events.ts +72 -0
- package/src/subagent-registry.ts +105 -0
- package/src/synthesize.ts +92 -0
- package/src/system-prompt-sanitizer.ts +274 -0
- package/src/tool-access-extractor-registry.ts +68 -0
- package/src/tool-input-formatter-registry.ts +67 -0
- package/src/tool-input-preview.ts +34 -0
- package/src/tool-input-prompt-formatters.ts +63 -0
- package/src/tool-preview-formatter.ts +207 -0
- package/src/tool-registry.ts +148 -0
- package/src/types.ts +64 -0
- package/src/wildcard-matcher.ts +120 -0
- package/src/yolo-mode.ts +30 -0
- package/test/active-agent.test.ts +155 -0
- package/test/async-cache.test.ts +48 -0
- package/test/bash-arity.test.ts +144 -0
- package/test/bash-external-directory.test.ts +956 -0
- package/test/builtin-tool-input-formatters.test.ts +109 -0
- package/test/canonicalize-path.test.ts +93 -0
- package/test/common.test.ts +287 -0
- package/test/composition-root.test.ts +603 -0
- package/test/config-loader.test.ts +740 -0
- package/test/config-modal.test.ts +320 -0
- package/test/config-paths.test.ts +83 -0
- package/test/config-pipeline.test.ts +90 -0
- package/test/config-reporter.test.ts +147 -0
- package/test/config-store.test.ts +466 -0
- package/test/decision-audit.test.ts +72 -0
- package/test/decision-reporter.test.ts +112 -0
- package/test/denial-messages.test.ts +656 -0
- package/test/detect-permissive-bash-fallback.test.ts +56 -0
- package/test/expand-home.test.ts +93 -0
- package/test/extension-config.test.ts +129 -0
- package/test/extension-paths.test.ts +108 -0
- package/test/forwarded-permissions/io.test.ts +251 -0
- package/test/forwarding-manager.test.ts +194 -0
- package/test/handlers/before-agent-start.test.ts +317 -0
- package/test/handlers/external-directory-integration.test.ts +623 -0
- package/test/handlers/external-directory-session-dedup.test.ts +430 -0
- package/test/handlers/external-directory-symlink-acceptance.test.ts +149 -0
- package/test/handlers/gates/bash-command-metamorphic.test.ts +83 -0
- package/test/handlers/gates/bash-command.test.ts +191 -0
- package/test/handlers/gates/bash-external-directory.test.ts +269 -0
- package/test/handlers/gates/bash-path.test.ts +337 -0
- package/test/handlers/gates/bash-program.test.ts +410 -0
- package/test/handlers/gates/bash-token-classification.test.ts +241 -0
- package/test/handlers/gates/candidate-check.test.ts +52 -0
- package/test/handlers/gates/external-directory-messages.test.ts +61 -0
- package/test/handlers/gates/external-directory.test.ts +259 -0
- package/test/handlers/gates/helpers.test.ts +177 -0
- package/test/handlers/gates/path.test.ts +294 -0
- package/test/handlers/gates/runner.test.ts +447 -0
- package/test/handlers/gates/skill-input-gate-pipeline.test.ts +176 -0
- package/test/handlers/gates/skill-input.test.ts +131 -0
- package/test/handlers/gates/skill-read.test.ts +158 -0
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +252 -0
- package/test/handlers/gates/tool.test.ts +223 -0
- package/test/handlers/input-events.test.ts +168 -0
- package/test/handlers/input.test.ts +199 -0
- package/test/handlers/lifecycle.test.ts +221 -0
- package/test/handlers/tool-call-boundary.test.ts +145 -0
- package/test/handlers/tool-call-events.test.ts +277 -0
- package/test/handlers/tool-call.test.ts +395 -0
- package/test/handlers/validate-requested-tool.test.ts +92 -0
- package/test/helpers/gate-fixtures.ts +323 -0
- package/test/helpers/handler-fixtures.ts +335 -0
- package/test/helpers/make-fake-pi.ts +100 -0
- package/test/helpers/manager-harness.ts +112 -0
- package/test/helpers/session-fixtures.ts +204 -0
- package/test/input-normalizer.test.ts +367 -0
- package/test/logging.test.ts +51 -0
- package/test/mcp-targets.test.ts +233 -0
- package/test/node-modules-discovery.test.ts +97 -0
- package/test/normalize.test.ts +247 -0
- package/test/path-utils.test.ts +650 -0
- package/test/pattern-suggest.test.ts +248 -0
- package/test/permission-dialog.test.ts +241 -0
- package/test/permission-event-rpc.test.ts +541 -0
- package/test/permission-events.test.ts +402 -0
- package/test/permission-forwarder.test.ts +369 -0
- package/test/permission-forwarding.test.ts +315 -0
- package/test/permission-gate.test.ts +305 -0
- package/test/permission-manager-unified.test.ts +3368 -0
- package/test/permission-merge.test.ts +61 -0
- package/test/permission-prompter.test.ts +518 -0
- package/test/permission-prompts.test.ts +363 -0
- package/test/permission-resolver.test.ts +265 -0
- package/test/permission-session.test.ts +363 -0
- package/test/permission-ui-prompt.test.ts +146 -0
- package/test/permissions-service.test.ts +177 -0
- package/test/persistent-approval-recorder.test.ts +133 -0
- package/test/pi-infrastructure-read.test.ts +369 -0
- package/test/policy-loader.test.ts +561 -0
- package/test/prompting-gateway.test.ts +230 -0
- package/test/rule.test.ts +604 -0
- package/test/scope-merge.test.ts +116 -0
- package/test/service-lifecycle.test.ts +163 -0
- package/test/service.test.ts +308 -0
- package/test/session-approval.test.ts +75 -0
- package/test/session-logger.test.ts +200 -0
- package/test/session-rules.test.ts +304 -0
- package/test/session-start.test.ts +112 -0
- package/test/skill-prompt-sanitizer.test.ts +374 -0
- package/test/status.test.ts +10 -0
- package/test/subagent-context.test.ts +326 -0
- package/test/subagent-lifecycle-events.test.ts +132 -0
- package/test/subagent-registry.test.ts +145 -0
- package/test/synthesize.test.ts +300 -0
- package/test/system-prompt-sanitizer.test.ts +382 -0
- package/test/tool-access-extractor-registry.test.ts +77 -0
- package/test/tool-input-formatter-registry.test.ts +75 -0
- package/test/tool-input-preview.test.ts +129 -0
- package/test/tool-input-prompt-formatters.test.ts +115 -0
- package/test/tool-preview-formatter.test.ts +458 -0
- package/test/tool-registry.test.ts +197 -0
- package/test/wildcard-matcher.test.ts +424 -0
- package/test/yolo-mode.test.ts +188 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import type { PermissionManager } from "#src/permission-manager";
|
|
4
|
+
import {
|
|
5
|
+
findSkillPathMatch,
|
|
6
|
+
parseAllSkillPromptSections,
|
|
7
|
+
resolveSkillPromptEntries,
|
|
8
|
+
} from "#src/skill-prompt-sanitizer";
|
|
9
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
10
|
+
import { createManager } from "#test/helpers/manager-harness";
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const CWD = "/projects/my-app";
|
|
19
|
+
|
|
20
|
+
function makeManager(
|
|
21
|
+
defaultState: "allow" | "deny" | "ask" = "allow",
|
|
22
|
+
overrides: Record<string, "allow" | "deny" | "ask"> = {},
|
|
23
|
+
): PermissionManager {
|
|
24
|
+
return {
|
|
25
|
+
checkPermission: vi.fn(
|
|
26
|
+
(_surface: string, input: { name?: string }): PermissionCheckResult => {
|
|
27
|
+
const name = input.name ?? "";
|
|
28
|
+
const state = overrides[name] ?? defaultState;
|
|
29
|
+
return { toolName: "skill", state, source: "tool", origin: "builtin" };
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
} as unknown as PermissionManager;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function skillBlock(
|
|
36
|
+
name: string,
|
|
37
|
+
location = `/skills/${name}/SKILL.md`,
|
|
38
|
+
): string {
|
|
39
|
+
return [
|
|
40
|
+
" <skill>",
|
|
41
|
+
` <name>${name}</name>`,
|
|
42
|
+
` <description>Description of ${name}</description>`,
|
|
43
|
+
` <location>${location}</location>`,
|
|
44
|
+
" </skill>",
|
|
45
|
+
].join("\n");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function availableSkillsSection(...names: string[]): string {
|
|
49
|
+
return [
|
|
50
|
+
"<available_skills>",
|
|
51
|
+
...names.map((n) => skillBlock(n)),
|
|
52
|
+
"</available_skills>",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── resolveSkillPromptEntries ───────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
describe("resolveSkillPromptEntries", () => {
|
|
59
|
+
test("returns unchanged prompt and empty entries when no skills section present", () => {
|
|
60
|
+
const input = "You are a helpful assistant.";
|
|
61
|
+
const manager = makeManager("allow");
|
|
62
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
63
|
+
expect(result.prompt).toBe(input);
|
|
64
|
+
expect(result.entries).toEqual([]);
|
|
65
|
+
expect(manager.checkPermission).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("keeps all skills when all are allowed", () => {
|
|
69
|
+
const input = availableSkillsSection("librarian", "ask-user");
|
|
70
|
+
const manager = makeManager("allow");
|
|
71
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
72
|
+
expect(result.prompt).toContain("librarian");
|
|
73
|
+
expect(result.prompt).toContain("ask-user");
|
|
74
|
+
expect(result.entries).toHaveLength(2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("removes denied skill from section", () => {
|
|
78
|
+
const input = availableSkillsSection("librarian", "dangerous");
|
|
79
|
+
const manager = makeManager("allow", { dangerous: "deny" });
|
|
80
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
81
|
+
expect(result.prompt).toContain("librarian");
|
|
82
|
+
expect(result.prompt).not.toContain("dangerous");
|
|
83
|
+
// denied skill is excluded from returned entries
|
|
84
|
+
expect(result.entries.map((e) => e.name)).not.toContain("dangerous");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("removes entire section when all skills are denied", () => {
|
|
88
|
+
const input = `Intro\n${availableSkillsSection("dangerous")}\nOutro`;
|
|
89
|
+
const manager = makeManager("deny");
|
|
90
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
91
|
+
expect(result.prompt).not.toContain("<available_skills>");
|
|
92
|
+
expect(result.prompt).toContain("Intro");
|
|
93
|
+
expect(result.prompt).toContain("Outro");
|
|
94
|
+
expect(result.entries).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("keeps ask-state skills in section and entries", () => {
|
|
98
|
+
const input = availableSkillsSection("librarian");
|
|
99
|
+
const manager = makeManager("ask");
|
|
100
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
101
|
+
expect(result.prompt).toContain("librarian");
|
|
102
|
+
expect(result.entries).toHaveLength(1);
|
|
103
|
+
expect(result.entries[0].state).toBe("ask");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("delegates permission check to permissionManager for each skill", () => {
|
|
107
|
+
const input = availableSkillsSection("alpha", "beta");
|
|
108
|
+
const manager = makeManager("allow");
|
|
109
|
+
resolveSkillPromptEntries(input, manager, null, CWD);
|
|
110
|
+
expect(manager.checkPermission).toHaveBeenCalledWith(
|
|
111
|
+
"skill",
|
|
112
|
+
{ name: "alpha" },
|
|
113
|
+
undefined,
|
|
114
|
+
);
|
|
115
|
+
expect(manager.checkPermission).toHaveBeenCalledWith(
|
|
116
|
+
"skill",
|
|
117
|
+
{ name: "beta" },
|
|
118
|
+
undefined,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("passes agentName to permissionManager", () => {
|
|
123
|
+
const input = availableSkillsSection("librarian");
|
|
124
|
+
const manager = makeManager("allow");
|
|
125
|
+
resolveSkillPromptEntries(input, manager, "my-agent", CWD);
|
|
126
|
+
expect(manager.checkPermission).toHaveBeenCalledWith(
|
|
127
|
+
"skill",
|
|
128
|
+
{ name: "librarian" },
|
|
129
|
+
"my-agent",
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("caches permission result: checkPermission called once per unique skill name", () => {
|
|
134
|
+
// Same skill appears in two separate sections.
|
|
135
|
+
const input = [
|
|
136
|
+
availableSkillsSection("librarian"),
|
|
137
|
+
availableSkillsSection("librarian"),
|
|
138
|
+
].join("\n");
|
|
139
|
+
const manager = makeManager("allow");
|
|
140
|
+
resolveSkillPromptEntries(input, manager, null, CWD);
|
|
141
|
+
// Should only be called once despite appearing twice.
|
|
142
|
+
expect(manager.checkPermission).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("resolves entry normalizedLocation relative to cwd", () => {
|
|
146
|
+
const location = "/skills/librarian/SKILL.md";
|
|
147
|
+
const input = availableSkillsSection("librarian");
|
|
148
|
+
const manager = makeManager("allow");
|
|
149
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
150
|
+
expect(result.entries[0].normalizedLocation).toBe(location);
|
|
151
|
+
expect(result.entries[0].normalizedBaseDir).toBe("/skills/librarian");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("handles multi-section prompt: processes each section independently", () => {
|
|
155
|
+
const section1 = availableSkillsSection("alpha");
|
|
156
|
+
const section2 = availableSkillsSection("beta");
|
|
157
|
+
const input = `${section1}\n${section2}`;
|
|
158
|
+
const manager = makeManager("allow", { beta: "deny" });
|
|
159
|
+
const result = resolveSkillPromptEntries(input, manager, null, CWD);
|
|
160
|
+
expect(result.entries.map((e) => e.name)).toContain("alpha");
|
|
161
|
+
expect(result.entries.map((e) => e.name)).not.toContain("beta");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ── findSkillPathMatch ──────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
describe("findSkillPathMatch", () => {
|
|
168
|
+
const entries = [
|
|
169
|
+
{
|
|
170
|
+
name: "librarian",
|
|
171
|
+
description: "desc",
|
|
172
|
+
location: "/skills/librarian/SKILL.md",
|
|
173
|
+
state: "allow" as const,
|
|
174
|
+
normalizedLocation: "/skills/librarian/SKILL.md",
|
|
175
|
+
normalizedBaseDir: "/skills/librarian",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "ask-user",
|
|
179
|
+
description: "desc",
|
|
180
|
+
location: "/skills/ask-user/SKILL.md",
|
|
181
|
+
state: "allow" as const,
|
|
182
|
+
normalizedLocation: "/skills/ask-user/SKILL.md",
|
|
183
|
+
normalizedBaseDir: "/skills/ask-user",
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
test("returns null for empty normalized path", () => {
|
|
188
|
+
expect(findSkillPathMatch("", entries)).toBeNull();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("returns null for empty entries array", () => {
|
|
192
|
+
expect(findSkillPathMatch("/skills/librarian/SKILL.md", [])).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("matches exact location path", () => {
|
|
196
|
+
const match = findSkillPathMatch("/skills/librarian/SKILL.md", entries);
|
|
197
|
+
expect(match?.name).toBe("librarian");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("matches path within skill base directory", () => {
|
|
201
|
+
const match = findSkillPathMatch(
|
|
202
|
+
"/skills/librarian/extra/helper.md",
|
|
203
|
+
entries,
|
|
204
|
+
);
|
|
205
|
+
expect(match?.name).toBe("librarian");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("returns null for path not within any skill directory", () => {
|
|
209
|
+
const match = findSkillPathMatch("/other/path/file.md", entries);
|
|
210
|
+
expect(match).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("returns null for sibling path that shares a prefix", () => {
|
|
214
|
+
// "/skills/librarian-extra" should not match "/skills/librarian"
|
|
215
|
+
const match = findSkillPathMatch(
|
|
216
|
+
"/skills/librarian-extra/SKILL.md",
|
|
217
|
+
entries,
|
|
218
|
+
);
|
|
219
|
+
expect(match).toBeNull();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("prefers longer matching base directory (most specific skill wins)", () => {
|
|
223
|
+
const nestedEntries = [
|
|
224
|
+
{
|
|
225
|
+
name: "parent",
|
|
226
|
+
description: "desc",
|
|
227
|
+
location: "/skills/parent/SKILL.md",
|
|
228
|
+
state: "allow" as const,
|
|
229
|
+
normalizedLocation: "/skills/parent/SKILL.md",
|
|
230
|
+
normalizedBaseDir: "/skills/parent",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "child",
|
|
234
|
+
description: "desc",
|
|
235
|
+
location: "/skills/parent/child/SKILL.md",
|
|
236
|
+
state: "allow" as const,
|
|
237
|
+
normalizedLocation: "/skills/parent/child/SKILL.md",
|
|
238
|
+
normalizedBaseDir: "/skills/parent/child",
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
const match = findSkillPathMatch(
|
|
242
|
+
"/skills/parent/child/helper.md",
|
|
243
|
+
nestedEntries,
|
|
244
|
+
);
|
|
245
|
+
expect(match?.name).toBe("child");
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Moved from permission-system.test.ts catch-all (#342)
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
test("parseAllSkillPromptSections finds every available_skills block", () => {
|
|
254
|
+
const prompt = [
|
|
255
|
+
"Some preamble",
|
|
256
|
+
"<available_skills>",
|
|
257
|
+
" <skill>",
|
|
258
|
+
" <name>skill-one</name>",
|
|
259
|
+
" <description>First skill</description>",
|
|
260
|
+
" <location>/path/to/one</location>",
|
|
261
|
+
" </skill>",
|
|
262
|
+
"</available_skills>",
|
|
263
|
+
"Some content between",
|
|
264
|
+
"<available_skills>",
|
|
265
|
+
" <skill>",
|
|
266
|
+
" <name>skill-two</name>",
|
|
267
|
+
" <description>Second skill</description>",
|
|
268
|
+
" <location>/path/to/two</location>",
|
|
269
|
+
" </skill>",
|
|
270
|
+
"</available_skills>",
|
|
271
|
+
"Footer",
|
|
272
|
+
].join("\n");
|
|
273
|
+
|
|
274
|
+
const sections = parseAllSkillPromptSections(prompt);
|
|
275
|
+
|
|
276
|
+
expect(sections.length).toBe(2);
|
|
277
|
+
expect(sections[0].entries[0]?.name).toBe("skill-one");
|
|
278
|
+
expect(sections[1].entries[0]?.name).toBe("skill-two");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("REGRESSION: resolveSkillPromptEntries sanitizes every available_skills block", () => {
|
|
282
|
+
const { manager, cleanup } = createManager({
|
|
283
|
+
permission: {
|
|
284
|
+
"*": "ask",
|
|
285
|
+
skill: { "denied-skill": "deny" },
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const prompt = [
|
|
291
|
+
"System prompt start",
|
|
292
|
+
"<available_skills>",
|
|
293
|
+
" <skill>",
|
|
294
|
+
" <name>visible-skill</name>",
|
|
295
|
+
" <description>Allowed skill</description>",
|
|
296
|
+
" <location>/skills/visible/index.ts</location>",
|
|
297
|
+
" </skill>",
|
|
298
|
+
" <skill>",
|
|
299
|
+
" <name>denied-skill</name>",
|
|
300
|
+
" <description>Denied in first block</description>",
|
|
301
|
+
" <location>/skills/blocked/one.ts</location>",
|
|
302
|
+
" </skill>",
|
|
303
|
+
"</available_skills>",
|
|
304
|
+
"Agent identity section",
|
|
305
|
+
"<available_skills>",
|
|
306
|
+
" <skill>",
|
|
307
|
+
" <name>denied-skill</name>",
|
|
308
|
+
" <description>Denied in second block</description>",
|
|
309
|
+
" <location>/skills/blocked/two.ts</location>",
|
|
310
|
+
" </skill>",
|
|
311
|
+
"</available_skills>",
|
|
312
|
+
"System prompt end",
|
|
313
|
+
].join("\n");
|
|
314
|
+
|
|
315
|
+
const result = resolveSkillPromptEntries(prompt, manager, null, "/cwd");
|
|
316
|
+
|
|
317
|
+
expect(result.prompt).not.toContain("denied-skill");
|
|
318
|
+
expect(result.prompt).toContain("visible-skill");
|
|
319
|
+
expect((result.prompt.match(/<available_skills>/g) ?? []).length).toBe(1);
|
|
320
|
+
expect(result.entries.map((entry) => entry.name)).toEqual([
|
|
321
|
+
"visible-skill",
|
|
322
|
+
]);
|
|
323
|
+
} finally {
|
|
324
|
+
cleanup();
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("REGRESSION: resolveSkillPromptEntries keeps only visible skills available for path matching", () => {
|
|
329
|
+
const { manager, cleanup } = createManager({
|
|
330
|
+
permission: {
|
|
331
|
+
"*": "ask",
|
|
332
|
+
skill: { "blocked-skill": "deny" },
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const prompt = [
|
|
338
|
+
"System prompt start",
|
|
339
|
+
"<available_skills>",
|
|
340
|
+
" <skill>",
|
|
341
|
+
" <name>blocked-skill</name>",
|
|
342
|
+
" <description>Blocked skill</description>",
|
|
343
|
+
" <location>@./skills/blocked/entry.ts</location>",
|
|
344
|
+
" </skill>",
|
|
345
|
+
"</available_skills>",
|
|
346
|
+
"Middle section",
|
|
347
|
+
"<available_skills>",
|
|
348
|
+
" <skill>",
|
|
349
|
+
" <name>visible-skill</name>",
|
|
350
|
+
" <description>Visible skill</description>",
|
|
351
|
+
" <location>@./skills/visible/entry.ts</location>",
|
|
352
|
+
" </skill>",
|
|
353
|
+
"</available_skills>",
|
|
354
|
+
"System prompt end",
|
|
355
|
+
].join("\n");
|
|
356
|
+
|
|
357
|
+
const result = resolveSkillPromptEntries(prompt, manager, null, "/cwd");
|
|
358
|
+
const visiblePath = resolve("/cwd", "./skills/visible/file.ts");
|
|
359
|
+
const blockedPath = resolve("/cwd", "./skills/blocked/file.ts");
|
|
360
|
+
const matchedVisibleSkill = findSkillPathMatch(
|
|
361
|
+
process.platform === "win32" ? visiblePath.toLowerCase() : visiblePath,
|
|
362
|
+
result.entries,
|
|
363
|
+
);
|
|
364
|
+
const matchedBlockedSkill = findSkillPathMatch(
|
|
365
|
+
process.platform === "win32" ? blockedPath.toLowerCase() : blockedPath,
|
|
366
|
+
result.entries,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(matchedVisibleSkill?.name).toBe("visible-skill");
|
|
370
|
+
expect(matchedBlockedSkill).toBe(null);
|
|
371
|
+
} finally {
|
|
372
|
+
cleanup();
|
|
373
|
+
}
|
|
374
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
3
|
+
import { getPermissionSystemStatus } from "#src/status";
|
|
4
|
+
|
|
5
|
+
test("Permission-system status is only exposed when yolo mode is enabled", () => {
|
|
6
|
+
expect(getPermissionSystemStatus(DEFAULT_EXTENSION_CONFIG)).toBe(undefined);
|
|
7
|
+
expect(
|
|
8
|
+
getPermissionSystemStatus({ ...DEFAULT_EXTENSION_CONFIG, yoloMode: true }),
|
|
9
|
+
).toBe("yolo");
|
|
10
|
+
});
|