@gotgenes/pi-permission-system 0.7.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.
@@ -0,0 +1,139 @@
1
+ import { getNonEmptyString, toRecord } from "./common.js";
2
+
3
+ export type ToolRegistrationCheckResult =
4
+ | {
5
+ status: "missing-tool-name";
6
+ }
7
+ | {
8
+ status: "registered";
9
+ requestedToolName: string;
10
+ normalizedToolName: string;
11
+ }
12
+ | {
13
+ status: "unregistered";
14
+ requestedToolName: string;
15
+ normalizedToolName: string;
16
+ availableToolNames: string[];
17
+ };
18
+
19
+ function normalizeToolName(
20
+ toolName: string,
21
+ aliases: Record<string, string>,
22
+ ): string {
23
+ return aliases[toolName] || toolName;
24
+ }
25
+
26
+ function buildReverseAliases(
27
+ aliases: Record<string, string>,
28
+ ): Map<string, string[]> {
29
+ const reverse = new Map<string, string[]>();
30
+
31
+ for (const [alias, canonical] of Object.entries(aliases)) {
32
+ const existing = reverse.get(canonical) || [];
33
+ if (!existing.includes(alias)) {
34
+ existing.push(alias);
35
+ }
36
+ reverse.set(canonical, existing);
37
+ }
38
+
39
+ return reverse;
40
+ }
41
+
42
+ function addToolNameVariants(
43
+ value: string,
44
+ names: Set<string>,
45
+ aliases: Record<string, string>,
46
+ reverseAliases: ReadonlyMap<string, readonly string[]>,
47
+ ): void {
48
+ names.add(value);
49
+
50
+ const normalized = normalizeToolName(value, aliases);
51
+ names.add(normalized);
52
+
53
+ const canonicalFromAlias = aliases[value];
54
+ if (canonicalFromAlias) {
55
+ names.add(canonicalFromAlias);
56
+ }
57
+
58
+ const aliasValues = reverseAliases.get(value);
59
+ if (aliasValues) {
60
+ for (const alias of aliasValues) {
61
+ names.add(alias);
62
+ }
63
+ }
64
+
65
+ const aliasValuesForNormalized = reverseAliases.get(normalized);
66
+ if (aliasValuesForNormalized) {
67
+ for (const alias of aliasValuesForNormalized) {
68
+ names.add(alias);
69
+ }
70
+ }
71
+ }
72
+
73
+ export function getToolNameFromValue(value: unknown): string | null {
74
+ const direct = getNonEmptyString(value);
75
+ if (direct) {
76
+ return direct;
77
+ }
78
+
79
+ const record = toRecord(value);
80
+ const candidates = [record.toolName, record.name, record.tool];
81
+
82
+ for (const candidate of candidates) {
83
+ const stringValue = getNonEmptyString(candidate);
84
+ if (stringValue) {
85
+ return stringValue;
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ export function checkRequestedToolRegistration(
93
+ requestedToolName: string | null,
94
+ registeredTools: readonly unknown[],
95
+ aliases: Record<string, string> = {},
96
+ ): ToolRegistrationCheckResult {
97
+ const requested = getNonEmptyString(requestedToolName);
98
+ if (!requested) {
99
+ return {
100
+ status: "missing-tool-name",
101
+ };
102
+ }
103
+
104
+ const normalizedToolName = normalizeToolName(requested, aliases);
105
+ const reverseAliases = buildReverseAliases(aliases);
106
+
107
+ const registeredLookup = new Set<string>();
108
+ const availableToolNames = new Set<string>();
109
+
110
+ for (const tool of registeredTools) {
111
+ const name = getToolNameFromValue(tool);
112
+ if (!name) {
113
+ continue;
114
+ }
115
+
116
+ availableToolNames.add(name);
117
+ addToolNameVariants(name, registeredLookup, aliases, reverseAliases);
118
+ }
119
+
120
+ const isRegistered =
121
+ registeredLookup.has(requested) || registeredLookup.has(normalizedToolName);
122
+
123
+ if (isRegistered) {
124
+ return {
125
+ status: "registered",
126
+ requestedToolName: requested,
127
+ normalizedToolName,
128
+ };
129
+ }
130
+
131
+ return {
132
+ status: "unregistered",
133
+ requestedToolName: requested,
134
+ normalizedToolName,
135
+ availableToolNames: [...availableToolNames].sort((a, b) =>
136
+ a.localeCompare(b),
137
+ ),
138
+ };
139
+ }
package/src/types.ts ADDED
@@ -0,0 +1,50 @@
1
+ export type PermissionState = "allow" | "deny" | "ask";
2
+
3
+ export type BuiltInToolName =
4
+ | "bash"
5
+ | "read"
6
+ | "write"
7
+ | "edit"
8
+ | "grep"
9
+ | "find"
10
+ | "ls";
11
+
12
+ export type ToolPermissions = Record<string, PermissionState>;
13
+
14
+ export type BashPermissions = Record<string, PermissionState>;
15
+
16
+ export type SkillPermissions = Record<string, PermissionState>;
17
+
18
+ export type SpecialPermissionName = "doom_loop" | "external_directory";
19
+
20
+ export type SpecialPermissions = Record<string, PermissionState>;
21
+
22
+ export interface PermissionDefaultPolicy {
23
+ tools: PermissionState;
24
+ bash: PermissionState;
25
+ mcp: PermissionState;
26
+ skills: PermissionState;
27
+ special: PermissionState;
28
+ }
29
+
30
+ export interface AgentPermissions {
31
+ defaultPolicy?: Partial<PermissionDefaultPolicy>;
32
+ tools?: ToolPermissions;
33
+ bash?: BashPermissions;
34
+ mcp?: ToolPermissions;
35
+ skills?: SkillPermissions;
36
+ special?: SpecialPermissions;
37
+ }
38
+
39
+ export interface GlobalPermissionConfig extends AgentPermissions {
40
+ defaultPolicy: PermissionDefaultPolicy;
41
+ }
42
+
43
+ export interface PermissionCheckResult {
44
+ toolName: string;
45
+ state: PermissionState;
46
+ matchedPattern?: string;
47
+ command?: string;
48
+ target?: string;
49
+ source: "tool" | "bash" | "mcp" | "skill" | "special" | "default";
50
+ }
@@ -0,0 +1,84 @@
1
+ export type CompiledWildcardPattern<TState> = {
2
+ pattern: string;
3
+ state: TState;
4
+ regex: RegExp;
5
+ };
6
+
7
+ export type WildcardPatternMatch<TState> = {
8
+ state: TState;
9
+ matchedPattern: string;
10
+ matchedName: string;
11
+ };
12
+
13
+ function escapeRegExp(value: string): string {
14
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
+ }
16
+
17
+ export function compileWildcardPattern<TState>(
18
+ pattern: string,
19
+ state: TState,
20
+ ): CompiledWildcardPattern<TState> {
21
+ const escaped = pattern
22
+ .split("*")
23
+ .map((part) => escapeRegExp(part))
24
+ .join(".*");
25
+
26
+ return {
27
+ pattern,
28
+ state,
29
+ regex: new RegExp(`^${escaped}$`),
30
+ };
31
+ }
32
+
33
+ export function compileWildcardPatternEntries<TState>(
34
+ entries: Iterable<readonly [string, TState]>,
35
+ ): CompiledWildcardPattern<TState>[] {
36
+ return Array.from(entries, ([pattern, state]) =>
37
+ compileWildcardPattern(pattern, state),
38
+ );
39
+ }
40
+
41
+ export function compileWildcardPatterns<TState>(
42
+ patterns: Record<string, TState>,
43
+ ): CompiledWildcardPattern<TState>[] {
44
+ return compileWildcardPatternEntries(Object.entries(patterns));
45
+ }
46
+
47
+ export function findCompiledWildcardMatch<TState>(
48
+ patterns: readonly CompiledWildcardPattern<TState>[],
49
+ name: string,
50
+ ): WildcardPatternMatch<TState> | null {
51
+ for (let index = patterns.length - 1; index >= 0; index -= 1) {
52
+ const pattern = patterns[index];
53
+ if (pattern.regex.test(name)) {
54
+ return {
55
+ state: pattern.state,
56
+ matchedPattern: pattern.pattern,
57
+ matchedName: name,
58
+ };
59
+ }
60
+ }
61
+
62
+ return null;
63
+ }
64
+
65
+ export function findCompiledWildcardMatchForNames<TState>(
66
+ patterns: readonly CompiledWildcardPattern<TState>[],
67
+ names: readonly string[],
68
+ ): WildcardPatternMatch<TState> | null {
69
+ const normalizedNames = names
70
+ .map((value) => value.trim())
71
+ .filter((value) => value.length > 0);
72
+ if (normalizedNames.length === 0) {
73
+ return null;
74
+ }
75
+
76
+ for (const name of normalizedNames) {
77
+ const match = findCompiledWildcardMatch(patterns, name);
78
+ if (match) {
79
+ return match;
80
+ }
81
+ }
82
+
83
+ return null;
84
+ }
@@ -0,0 +1,29 @@
1
+ import type { PermissionSystemExtensionConfig } from "./extension-config.js";
2
+ import type { PermissionState } from "./types.js";
3
+
4
+ export interface AskPermissionResolutionOptions {
5
+ config: PermissionSystemExtensionConfig;
6
+ hasUI: boolean;
7
+ isSubagent: boolean;
8
+ }
9
+
10
+ export function isYoloModeEnabled(
11
+ config: PermissionSystemExtensionConfig,
12
+ ): boolean {
13
+ return config.yoloMode === true;
14
+ }
15
+
16
+ export function shouldAutoApprovePermissionState(
17
+ state: PermissionState,
18
+ config: PermissionSystemExtensionConfig,
19
+ ): boolean {
20
+ return state === "ask" && isYoloModeEnabled(config);
21
+ }
22
+
23
+ export function canResolveAskPermissionRequest(
24
+ options: AskPermissionResolutionOptions,
25
+ ): boolean {
26
+ return (
27
+ options.hasUI || options.isSubagent || isYoloModeEnabled(options.config)
28
+ );
29
+ }