@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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated arity dictionary for common CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Keys are lowercase, space-joined command prefixes.
|
|
5
|
+
* Values are the total token count that defines the "human-understandable
|
|
6
|
+
* subcommand" — i.e. how many tokens to include in a session-approval pattern.
|
|
7
|
+
*
|
|
8
|
+
* Multi-level entries (e.g. "npm run": 3) take precedence over shorter entries
|
|
9
|
+
* ("npm": 2) because `prefix()` uses longest-match-wins.
|
|
10
|
+
*
|
|
11
|
+
* Exported for testability.
|
|
12
|
+
*/
|
|
13
|
+
export const ARITY: Record<string, number> = {
|
|
14
|
+
// Version control
|
|
15
|
+
git: 2,
|
|
16
|
+
hg: 2,
|
|
17
|
+
svn: 2,
|
|
18
|
+
|
|
19
|
+
// Node.js package managers
|
|
20
|
+
npm: 2,
|
|
21
|
+
"npm run": 3,
|
|
22
|
+
"npm exec": 3,
|
|
23
|
+
npx: 2,
|
|
24
|
+
pnpm: 2,
|
|
25
|
+
"pnpm run": 3,
|
|
26
|
+
"pnpm exec": 3,
|
|
27
|
+
"pnpm dlx": 3,
|
|
28
|
+
yarn: 2,
|
|
29
|
+
"yarn run": 3,
|
|
30
|
+
bun: 2,
|
|
31
|
+
"bun run": 3,
|
|
32
|
+
"bun add": 2,
|
|
33
|
+
"bun x": 3,
|
|
34
|
+
|
|
35
|
+
// Runtimes
|
|
36
|
+
deno: 2,
|
|
37
|
+
"deno run": 3,
|
|
38
|
+
"deno task": 3,
|
|
39
|
+
"deno compile": 3,
|
|
40
|
+
|
|
41
|
+
// Python
|
|
42
|
+
pip: 2,
|
|
43
|
+
pip3: 2,
|
|
44
|
+
uv: 2,
|
|
45
|
+
"uv run": 3,
|
|
46
|
+
"uv pip": 3,
|
|
47
|
+
|
|
48
|
+
// Rust
|
|
49
|
+
cargo: 2,
|
|
50
|
+
|
|
51
|
+
// Go
|
|
52
|
+
go: 2,
|
|
53
|
+
"go run": 3,
|
|
54
|
+
|
|
55
|
+
// Ruby
|
|
56
|
+
bundle: 2,
|
|
57
|
+
"bundle exec": 3,
|
|
58
|
+
|
|
59
|
+
// Docker / container
|
|
60
|
+
docker: 2,
|
|
61
|
+
"docker compose": 3,
|
|
62
|
+
"docker container": 3,
|
|
63
|
+
"docker image": 3,
|
|
64
|
+
"docker network": 3,
|
|
65
|
+
"docker volume": 3,
|
|
66
|
+
podman: 2,
|
|
67
|
+
"podman compose": 3,
|
|
68
|
+
|
|
69
|
+
// Kubernetes
|
|
70
|
+
kubectl: 2,
|
|
71
|
+
helm: 2,
|
|
72
|
+
|
|
73
|
+
// Cloud CLIs
|
|
74
|
+
aws: 3,
|
|
75
|
+
az: 3,
|
|
76
|
+
gcloud: 3,
|
|
77
|
+
gh: 2,
|
|
78
|
+
"gh pr": 3,
|
|
79
|
+
"gh issue": 3,
|
|
80
|
+
"gh repo": 3,
|
|
81
|
+
fly: 2,
|
|
82
|
+
vercel: 2,
|
|
83
|
+
wrangler: 2,
|
|
84
|
+
|
|
85
|
+
// Build tools
|
|
86
|
+
make: 1,
|
|
87
|
+
bazel: 2,
|
|
88
|
+
|
|
89
|
+
// Infrastructure
|
|
90
|
+
terraform: 2,
|
|
91
|
+
tofu: 2,
|
|
92
|
+
pulumi: 2,
|
|
93
|
+
|
|
94
|
+
// System service management
|
|
95
|
+
systemctl: 2,
|
|
96
|
+
service: 2,
|
|
97
|
+
|
|
98
|
+
// Shell file-ops — args are paths/targets, not subcommands
|
|
99
|
+
ls: 1,
|
|
100
|
+
ll: 1,
|
|
101
|
+
la: 1,
|
|
102
|
+
cat: 1,
|
|
103
|
+
less: 1,
|
|
104
|
+
more: 1,
|
|
105
|
+
head: 1,
|
|
106
|
+
tail: 1,
|
|
107
|
+
grep: 1,
|
|
108
|
+
rg: 1,
|
|
109
|
+
ag: 1,
|
|
110
|
+
find: 1,
|
|
111
|
+
touch: 1,
|
|
112
|
+
mkdir: 1,
|
|
113
|
+
rm: 1,
|
|
114
|
+
cp: 1,
|
|
115
|
+
mv: 1,
|
|
116
|
+
ln: 1,
|
|
117
|
+
chmod: 1,
|
|
118
|
+
chown: 1,
|
|
119
|
+
du: 1,
|
|
120
|
+
df: 1,
|
|
121
|
+
echo: 1,
|
|
122
|
+
printf: 1,
|
|
123
|
+
diff: 1,
|
|
124
|
+
patch: 1,
|
|
125
|
+
wc: 1,
|
|
126
|
+
sort: 1,
|
|
127
|
+
uniq: 1,
|
|
128
|
+
awk: 1,
|
|
129
|
+
sed: 1,
|
|
130
|
+
tar: 1,
|
|
131
|
+
zip: 1,
|
|
132
|
+
unzip: 1,
|
|
133
|
+
|
|
134
|
+
// Network
|
|
135
|
+
curl: 1,
|
|
136
|
+
wget: 1,
|
|
137
|
+
ssh: 1,
|
|
138
|
+
scp: 1,
|
|
139
|
+
rsync: 1,
|
|
140
|
+
ping: 1,
|
|
141
|
+
|
|
142
|
+
// Process management
|
|
143
|
+
kill: 1,
|
|
144
|
+
killall: 1,
|
|
145
|
+
pkill: 1,
|
|
146
|
+
|
|
147
|
+
// Package managers (system)
|
|
148
|
+
brew: 2,
|
|
149
|
+
apt: 2,
|
|
150
|
+
"apt-get": 2,
|
|
151
|
+
yum: 2,
|
|
152
|
+
dnf: 2,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Return the semantically meaningful prefix tokens for a tokenized command.
|
|
157
|
+
*
|
|
158
|
+
* Performs a longest-match-wins lookup against the `ARITY` dictionary:
|
|
159
|
+
* iterates from the longest possible prefix down to a single token, returning
|
|
160
|
+
* the first (longest) match. Lookup is case-insensitive; the returned tokens
|
|
161
|
+
* preserve their original casing.
|
|
162
|
+
*
|
|
163
|
+
* When no entry matches, defaults to arity 1 (first token only).
|
|
164
|
+
* When the resolved arity exceeds the available tokens, it is clamped.
|
|
165
|
+
*
|
|
166
|
+
* @param tokens - The command split by whitespace (e.g. `["git", "checkout", "main"]`).
|
|
167
|
+
* @returns The prefix tokens defining the meaningful subcommand.
|
|
168
|
+
*/
|
|
169
|
+
export function prefix(tokens: string[]): string[] {
|
|
170
|
+
if (tokens.length === 0) return [];
|
|
171
|
+
|
|
172
|
+
for (let n = tokens.length; n >= 1; n--) {
|
|
173
|
+
const key = tokens
|
|
174
|
+
.slice(0, n)
|
|
175
|
+
.map((t) => t.toLowerCase())
|
|
176
|
+
.join(" ");
|
|
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
|
|
179
|
+
if (arity !== undefined) {
|
|
180
|
+
return tokens.slice(0, Math.min(arity, tokens.length));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Unknown command — default arity 1.
|
|
185
|
+
return [tokens[0]];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Remove shell comment lines from a bash command string.
|
|
190
|
+
*
|
|
191
|
+
* A comment line is one whose first non-whitespace character is `#`. Agents
|
|
192
|
+
* frequently prepend descriptive comments before the real command
|
|
193
|
+
* (e.g. `"# Check debug logs\nfind ..."`); such prefixes defeat wildcard
|
|
194
|
+
* pattern matching and session-approval suggestions, which tokenize the
|
|
195
|
+
* leading text. Stripping comment lines lets matching operate on the actual
|
|
196
|
+
* command.
|
|
197
|
+
*
|
|
198
|
+
* The original command is never returned: when every line is a comment (or
|
|
199
|
+
* the input is blank) an empty string is returned, and each caller applies
|
|
200
|
+
* its own fallback.
|
|
201
|
+
*
|
|
202
|
+
* @param command - Raw bash command, possibly multi-line.
|
|
203
|
+
* @returns The command with comment lines removed and surrounding whitespace
|
|
204
|
+
* trimmed, or an empty string when nothing meaningful remains.
|
|
205
|
+
*/
|
|
206
|
+
export function stripBashCommentLines(command: string): string {
|
|
207
|
+
const lines = command.split("\n");
|
|
208
|
+
const meaningful = lines.filter((line) => !/^\s*#/.test(line));
|
|
209
|
+
return meaningful.join("\n").trim();
|
|
210
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in tool input formatters registered through the public seam at startup.
|
|
3
|
+
*
|
|
4
|
+
* Each formatter here dogfoods `ToolInputFormatterRegistry.register` — it goes
|
|
5
|
+
* through exactly the same path a third-party extension would use.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { toRecord } from "./common";
|
|
9
|
+
import type {
|
|
10
|
+
ToolInputFormatter,
|
|
11
|
+
ToolInputFormatterRegistry,
|
|
12
|
+
} from "./tool-input-formatter-registry";
|
|
13
|
+
import { truncateInlineText } from "./tool-input-preview";
|
|
14
|
+
|
|
15
|
+
/** Maximum total length of the generated argument summary (before "with " prefix). */
|
|
16
|
+
const MCP_ARGS_SUMMARY_MAX_LENGTH = 160;
|
|
17
|
+
|
|
18
|
+
/** Maximum length of a single string argument value (before quoting). */
|
|
19
|
+
const MCP_ARG_VALUE_MAX_LENGTH = 60;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render a single MCP argument value as a compact, readable fragment.
|
|
23
|
+
*
|
|
24
|
+
* - Strings: quoted and truncated.
|
|
25
|
+
* - Numbers / booleans: plain string conversion.
|
|
26
|
+
* - Arrays: `[N items]`.
|
|
27
|
+
* - Objects: `{…}`.
|
|
28
|
+
* - Everything else: plain string conversion.
|
|
29
|
+
*/
|
|
30
|
+
function renderArgValue(value: unknown): string {
|
|
31
|
+
if (typeof value === "string") {
|
|
32
|
+
return `"${truncateInlineText(value, MCP_ARG_VALUE_MAX_LENGTH)}"`;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
35
|
+
return String(value);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return `[${value.length} items]`;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "object" && value !== null) {
|
|
41
|
+
return "{…}";
|
|
42
|
+
}
|
|
43
|
+
return String(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format an MCP tool call's `arguments` payload as a human-readable summary.
|
|
48
|
+
*
|
|
49
|
+
* Returns `undefined` when `arguments` is absent or empty — the MCP ask-prompt
|
|
50
|
+
* is then left unchanged (no suffix appended).
|
|
51
|
+
*
|
|
52
|
+
* Intended to be registered as the `"mcp"` formatter via
|
|
53
|
+
* `registerBuiltinToolInputFormatters`.
|
|
54
|
+
*/
|
|
55
|
+
export const formatMcpInputForPrompt: ToolInputFormatter = (
|
|
56
|
+
input: Record<string, unknown>,
|
|
57
|
+
): string | undefined => {
|
|
58
|
+
const args = toRecord(input.arguments);
|
|
59
|
+
const entries = Object.entries(args);
|
|
60
|
+
if (entries.length === 0) return undefined;
|
|
61
|
+
|
|
62
|
+
const parts = entries.map(
|
|
63
|
+
([key, value]) => `${key}: ${renderArgValue(value)}`,
|
|
64
|
+
);
|
|
65
|
+
const summary = truncateInlineText(
|
|
66
|
+
parts.join(", "),
|
|
67
|
+
MCP_ARGS_SUMMARY_MAX_LENGTH,
|
|
68
|
+
);
|
|
69
|
+
return `with ${summary}`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Register all built-in tool input formatters into `registry`.
|
|
74
|
+
*
|
|
75
|
+
* Called once from the extension factory (`index.ts`) immediately after the
|
|
76
|
+
* registry is constructed, before any third-party registration can occur.
|
|
77
|
+
*/
|
|
78
|
+
export function registerBuiltinToolInputFormatters(
|
|
79
|
+
registry: ToolInputFormatterRegistry,
|
|
80
|
+
): void {
|
|
81
|
+
registry.register("mcp", formatMcpInputForPrompt);
|
|
82
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve symlinks in an absolute path, best-effort.
|
|
6
|
+
*
|
|
7
|
+
* Splits the path into components and tries `realpathSync` from the full path
|
|
8
|
+
* down to `/`, re-appending the non-existent tail to the first ancestor that
|
|
9
|
+
* resolves. Returns the input unchanged when no ancestor resolves (unreachable
|
|
10
|
+
* in practice since `/` always exists) or when a non-ENOENT/ENOTDIR error is
|
|
11
|
+
* encountered (e.g. `EACCES`, `ELOOP`), so callers fall back to lexical
|
|
12
|
+
* containment for paths that cannot be resolved.
|
|
13
|
+
*/
|
|
14
|
+
export function canonicalizePath(absolutePath: string): string {
|
|
15
|
+
if (!absolutePath) return absolutePath;
|
|
16
|
+
|
|
17
|
+
const parts = absolutePath.split("/").filter(Boolean);
|
|
18
|
+
for (let i = parts.length; i >= 0; i--) {
|
|
19
|
+
const candidate = "/" + parts.slice(0, i).join("/");
|
|
20
|
+
try {
|
|
21
|
+
const real = realpathSync(candidate);
|
|
22
|
+
const tail = parts.slice(i);
|
|
23
|
+
return tail.length === 0 ? real : join(real, ...tail);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
26
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") return absolutePath;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return absolutePath;
|
|
30
|
+
}
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { DenyWithReason, PermissionState } from "./types";
|
|
2
|
+
|
|
3
|
+
export function toRecord(value: unknown): Record<string, unknown> {
|
|
4
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return value as Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getNonEmptyString(value: unknown): string | null {
|
|
12
|
+
if (typeof value !== "string") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Returns `raw` if it is an array of strings; otherwise `undefined`. */
|
|
21
|
+
export function normalizeOptionalStringArray(
|
|
22
|
+
raw: unknown,
|
|
23
|
+
): string[] | undefined {
|
|
24
|
+
return Array.isArray(raw) &&
|
|
25
|
+
raw.every((p): p is string => typeof p === "string")
|
|
26
|
+
? raw
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Returns `raw` if it is a positive integer; otherwise `undefined`. */
|
|
31
|
+
export function normalizeOptionalPositiveInt(raw: unknown): number | undefined {
|
|
32
|
+
return typeof raw === "number" && Number.isInteger(raw) && raw > 0
|
|
33
|
+
? raw
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isPermissionState(value: unknown): value is PermissionState {
|
|
38
|
+
return value === "allow" || value === "deny" || value === "ask";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Narrow type guard: a raw value representing a DenyWithReason object.
|
|
43
|
+
* Accepts `{ action: "deny" }` and `{ action: "deny", reason: "…" }`.
|
|
44
|
+
* Rejects a non-string `reason` to keep malformed config out of the rule set.
|
|
45
|
+
*/
|
|
46
|
+
export function isDenyWithReason(value: unknown): value is DenyWithReason {
|
|
47
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const record = value as Record<string, unknown>;
|
|
51
|
+
return (
|
|
52
|
+
record.action === "deny" &&
|
|
53
|
+
(record.reason === undefined || typeof record.reason === "string")
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type StackNode = { indent: number; target: Record<string, unknown> };
|
|
58
|
+
|
|
59
|
+
export function parseSimpleYamlMap(input: string): Record<string, unknown> {
|
|
60
|
+
const root: Record<string, unknown> = {};
|
|
61
|
+
const stack: StackNode[] = [{ indent: -1, target: root }];
|
|
62
|
+
|
|
63
|
+
const lines = input.split(/\r?\n/);
|
|
64
|
+
for (const rawLine of lines) {
|
|
65
|
+
if (!rawLine.trim() || rawLine.trimStart().startsWith("#")) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
70
|
+
const line = rawLine.trim();
|
|
71
|
+
const separatorIndex = line.indexOf(":");
|
|
72
|
+
if (separatorIndex <= 0) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const key = line
|
|
77
|
+
.slice(0, separatorIndex)
|
|
78
|
+
.trim()
|
|
79
|
+
.replace(/^['"]|['"]$/g, "");
|
|
80
|
+
const rawValue = line.slice(separatorIndex + 1).trim();
|
|
81
|
+
|
|
82
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
83
|
+
stack.pop();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const current = stack[stack.length - 1].target;
|
|
87
|
+
|
|
88
|
+
if (!rawValue) {
|
|
89
|
+
const child: Record<string, unknown> = {};
|
|
90
|
+
current[key] = child;
|
|
91
|
+
stack.push({ indent, target: child });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let scalar = rawValue;
|
|
96
|
+
if (
|
|
97
|
+
(scalar.startsWith('"') && scalar.endsWith('"')) ||
|
|
98
|
+
(scalar.startsWith("'") && scalar.endsWith("'"))
|
|
99
|
+
) {
|
|
100
|
+
scalar = scalar.slice(1, -1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
current[key] = scalar;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return root;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function extractFrontmatter(markdown: string): string {
|
|
110
|
+
const normalized = markdown.replace(/\r\n/g, "\n");
|
|
111
|
+
if (!normalized.startsWith("---\n")) {
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const end = normalized.indexOf("\n---", 4);
|
|
116
|
+
if (end === -1) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return normalized.slice(4, end);
|
|
121
|
+
}
|