decorated-pi 0.5.4 → 0.6.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/.codegraph/daemon.pid +6 -0
- package/AGENTS.md +92 -0
- package/README.md +53 -64
- package/commands/dp-model.ts +23 -0
- package/commands/dp-settings.ts +23 -0
- package/commands/mcp-status.ts +62 -0
- package/commands/retry.ts +19 -0
- package/commands/usage.ts +544 -0
- package/hooks/compaction.ts +204 -0
- package/hooks/externalize.ts +70 -0
- package/hooks/image-vision.ts +132 -0
- package/hooks/inject-agents-md.ts +164 -0
- package/hooks/mcp.ts +340 -0
- package/hooks/normalize-codeblocks.ts +88 -0
- package/hooks/pi-tool-filter.ts +28 -0
- package/{extensions/safety → hooks/redact}/types.ts +1 -8
- package/hooks/redact.ts +104 -0
- package/{extensions → hooks}/rtk.ts +92 -115
- package/hooks/session-title.ts +54 -0
- package/hooks/skeleton.ts +212 -0
- package/hooks/smart-at.ts +318 -0
- package/{extensions/file-times.ts → hooks/track-mtime.ts} +40 -38
- package/{extensions → hooks}/wakatime.ts +120 -122
- package/index.ts +155 -1
- package/package.json +5 -4
- package/{extensions/settings.ts → settings.ts} +32 -0
- package/{extensions → tools}/lsp/client.ts +0 -25
- package/{extensions → tools}/lsp/format.ts +2 -99
- package/{extensions → tools}/lsp/index.ts +1 -1
- package/{extensions → tools}/lsp/servers.ts +1 -1
- package/{extensions → tools}/lsp/tools.ts +1 -66
- package/{extensions → tools}/lsp/types.ts +0 -11
- package/tools/mcp/builtin/codegraph.ts +45 -0
- package/tools/mcp/builtin/context7.ts +10 -0
- package/tools/mcp/builtin/exa.ts +10 -0
- package/tools/mcp/builtin/index.ts +19 -0
- package/tools/mcp/cache.ts +124 -0
- package/{extensions → tools}/mcp/client.ts +2 -2
- package/{extensions/mcp/builtin.ts → tools/mcp/config.ts} +21 -181
- package/tools/mcp/index.ts +44 -0
- package/tools/mcp/tool-definition.ts +112 -0
- package/{extensions/patch.ts → tools/patch/core.ts} +336 -65
- package/{extensions/io.ts → tools/patch/index.ts} +29 -205
- package/tsconfig.json +10 -1
- package/ui/mcp-status.ts +202 -0
- package/ui/model-picker.ts +162 -0
- package/ui/module-settings.ts +83 -0
- package/ui/usage.ts +396 -0
- package/extensions/index.ts +0 -154
- package/extensions/io-tool-output.ts +0 -33
- package/extensions/mcp/index.ts +0 -435
- package/extensions/model-integration.ts +0 -531
- package/extensions/providers/ark-coding.ts +0 -75
- package/extensions/providers/index.ts +0 -9
- package/extensions/providers/ollama-cloud.ts +0 -101
- package/extensions/providers/qianfan-coding.ts +0 -71
- package/extensions/safety/index.ts +0 -102
- package/extensions/session-title.ts +0 -40
- package/extensions/slash.ts +0 -458
- package/extensions/smart-at.ts +0 -481
- package/extensions/subdir-agents.ts +0 -151
- /package/{extensions/safety → hooks/redact}/detect.ts +0 -0
- /package/{extensions/safety → hooks/redact}/entropy.ts +0 -0
- /package/{extensions/safety → hooks/redact}/patterns.ts +0 -0
- /package/{extensions → tools}/lsp/env.ts +0 -0
- /package/{extensions → tools}/lsp/manager.ts +0 -0
- /package/{extensions → tools}/lsp/prompt.ts +0 -0
- /package/{extensions → tools}/lsp/protocol.ts +0 -0
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from "./protocol.js";
|
|
9
9
|
import type {
|
|
10
10
|
LspDiagnostic,
|
|
11
|
-
LspDocumentSymbol,
|
|
12
11
|
LspHover,
|
|
13
12
|
LspLocation,
|
|
14
13
|
LspPosition,
|
|
@@ -199,14 +198,6 @@ export class LspClient {
|
|
|
199
198
|
);
|
|
200
199
|
}
|
|
201
200
|
|
|
202
|
-
async documentSymbols(uri: string, timeoutMs?: number): Promise<LspDocumentSymbol[]> {
|
|
203
|
-
return normalizeDocumentSymbols(
|
|
204
|
-
await this.#request("textDocument/documentSymbol", {
|
|
205
|
-
textDocument: { uri },
|
|
206
|
-
}, timeoutMs),
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
201
|
async rename(
|
|
211
202
|
uri: string,
|
|
212
203
|
position: LspPosition,
|
|
@@ -260,22 +251,6 @@ function normalizeLocations(result: unknown): LspLocation[] {
|
|
|
260
251
|
});
|
|
261
252
|
}
|
|
262
253
|
|
|
263
|
-
function normalizeDocumentSymbols(result: unknown): LspDocumentSymbol[] {
|
|
264
|
-
if (!result || !Array.isArray(result) || result.length === 0) return [];
|
|
265
|
-
const first = result[0];
|
|
266
|
-
if ("range" in first && "selectionRange" in first) {
|
|
267
|
-
return result as LspDocumentSymbol[];
|
|
268
|
-
}
|
|
269
|
-
return result.map((entry: any) => ({
|
|
270
|
-
name: entry.name,
|
|
271
|
-
kind: entry.kind,
|
|
272
|
-
range: entry.location.range,
|
|
273
|
-
selectionRange: entry.location.range,
|
|
274
|
-
containerName: entry.containerName,
|
|
275
|
-
uri: entry.location.uri,
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
254
|
function uriToPath(uri: string): string {
|
|
280
255
|
try {
|
|
281
256
|
return uri.startsWith("file:") ? new URL(uri).pathname : uri;
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Runtime tools.ts has its own inline copies.
|
|
6
6
|
*/
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import type { LspDiagnostic, LspHover, LspLocation
|
|
8
|
+
import type { LspDiagnostic, LspHover, LspLocation } from "./types.js";
|
|
9
9
|
import { LspClientStartError } from "./client.js";
|
|
10
10
|
|
|
11
|
-
export type { LspDiagnostic, LspHover, LspLocation
|
|
11
|
+
export type { LspDiagnostic, LspHover, LspLocation } from "./types.js";
|
|
12
12
|
export { LspClientStartError } from "./client.js";
|
|
13
13
|
|
|
14
14
|
// ─── Error formatting ────────────────────────────────────────────────────
|
|
@@ -112,103 +112,6 @@ function fileUrlToPath(uri: string): string {
|
|
|
112
112
|
try { return uri.startsWith("file:") ? fileURLToPath(uri) : uri; } catch { return uri; }
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// ─── Symbols ──────────────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
const SYMBOL_KIND_LABELS: Record<number, string> = {
|
|
118
|
-
2: "module", 3: "namespace", 5: "class", 6: "method", 7: "property",
|
|
119
|
-
8: "field", 9: "constructor", 11: "interface", 12: "function",
|
|
120
|
-
13: "variable", 14: "constant", 23: "struct", 24: "event",
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export function symbol_kind_label(kind: number): string {
|
|
124
|
-
return SYMBOL_KIND_LABELS[kind] ?? "symbol";
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function format_document_symbols(file: string, symbols: LspDocumentSymbol[]): string {
|
|
128
|
-
if (symbols.length === 0) return `${file}: no symbols`;
|
|
129
|
-
const lines = [`${file}: ${symbols.length} top-level symbol(s)`];
|
|
130
|
-
appendSymbols(lines, symbols, 1);
|
|
131
|
-
return lines.join("\n");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function appendSymbols(lines: string[], symbols: LspDocumentSymbol[], depth: number) {
|
|
135
|
-
for (const s of symbols) {
|
|
136
|
-
const indent = " ".repeat(depth);
|
|
137
|
-
const detail = s.detail ? ` — ${s.detail}` : "";
|
|
138
|
-
const range = `${s.range.start.line + 1}:${s.range.start.character + 1}`;
|
|
139
|
-
lines.push(`${indent}${symbol_kind_label(s.kind)} ${s.name}${detail} @ ${range}`);
|
|
140
|
-
if (s.children?.length) appendSymbols(lines, s.children, depth + 1);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
interface SymbolMatchOptions {
|
|
145
|
-
max_results: number;
|
|
146
|
-
top_level_only: boolean;
|
|
147
|
-
exact_match: boolean;
|
|
148
|
-
kinds: Set<string>;
|
|
149
|
-
language: string;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export interface SymbolMatch { symbol: LspDocumentSymbol; depth: number }
|
|
153
|
-
|
|
154
|
-
export function find_symbol_matches(
|
|
155
|
-
symbols: LspDocumentSymbol[],
|
|
156
|
-
query: string,
|
|
157
|
-
options: SymbolMatchOptions,
|
|
158
|
-
): SymbolMatch[] {
|
|
159
|
-
const normalized = query.trim().toLowerCase();
|
|
160
|
-
if (!normalized) return [];
|
|
161
|
-
const matches: SymbolMatch[] = [];
|
|
162
|
-
const expandName = (name: string): string[] => {
|
|
163
|
-
const trimmed = name.trim().toLowerCase();
|
|
164
|
-
if (!trimmed) return [];
|
|
165
|
-
const expanded = new Set([trimmed]);
|
|
166
|
-
if (options.language === "cpp" && trimmed.includes("::")) {
|
|
167
|
-
const parts = trimmed.split("::").map(p => p.trim()).filter(Boolean);
|
|
168
|
-
if (parts.length > 0) expanded.add(parts[parts.length - 1]!);
|
|
169
|
-
}
|
|
170
|
-
return [...expanded];
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const matchesQuery = (s: LspDocumentSymbol): boolean => {
|
|
174
|
-
const name = s.name.trim().toLowerCase();
|
|
175
|
-
const detail = (s.detail ?? "").trim().toLowerCase();
|
|
176
|
-
if (options.exact_match) {
|
|
177
|
-
const exactValues = [...expandName(s.name), ...(detail ? [detail] : [])];
|
|
178
|
-
return exactValues.some(v => v === normalized);
|
|
179
|
-
}
|
|
180
|
-
return name.includes(normalized) || detail.includes(normalized);
|
|
181
|
-
};
|
|
182
|
-
const matchesKind = (s: LspDocumentSymbol): boolean =>
|
|
183
|
-
options.kinds.size === 0 || options.kinds.has(symbol_kind_label(s.kind));
|
|
184
|
-
const visit = (entries: LspDocumentSymbol[], depth: number) => {
|
|
185
|
-
for (const symbol of entries) {
|
|
186
|
-
if (matchesKind(symbol) && matchesQuery(symbol)) {
|
|
187
|
-
matches.push({ symbol, depth });
|
|
188
|
-
if (matches.length >= options.max_results) return;
|
|
189
|
-
}
|
|
190
|
-
if (!options.top_level_only && symbol.children?.length) {
|
|
191
|
-
visit(symbol.children, depth + 1);
|
|
192
|
-
if (matches.length >= options.max_results) return;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
visit(symbols, 1);
|
|
197
|
-
return matches;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function format_symbol_matches(file: string, query: string, matches: SymbolMatch[]): string {
|
|
201
|
-
if (matches.length === 0) return `${file}: no symbols matching "${query}"`;
|
|
202
|
-
const lines = [`${file}: ${matches.length} symbol match(es) for "${query}"`];
|
|
203
|
-
for (const { symbol, depth } of matches) {
|
|
204
|
-
const indent = " ".repeat(depth);
|
|
205
|
-
const detail = symbol.detail ? ` — ${symbol.detail}` : "";
|
|
206
|
-
const range = `${symbol.range.start.line + 1}:${symbol.range.start.character + 1}`;
|
|
207
|
-
lines.push(`${indent}${symbol_kind_label(symbol.kind)} ${symbol.name}${detail} @ ${range}`);
|
|
208
|
-
}
|
|
209
|
-
return lines.join("\n");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
115
|
// ─── Collapse (for tools test) ────────────────────────────────────────────
|
|
213
116
|
|
|
214
117
|
export function collapse_lsp_text(text: string, maxLines = 20) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LSP Extension — language server integration for Pi.
|
|
3
3
|
*
|
|
4
|
-
* Provides: lsp_diagnostics
|
|
4
|
+
* Provides: lsp_diagnostics.
|
|
5
5
|
*/
|
|
6
6
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { LspServerManager } from "./manager.js";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { existsSync, readdirSync, type Dirent } from "node:fs";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
-
import type { DependencyStatus } from "
|
|
7
|
+
import type { DependencyStatus } from "../../hooks/skeleton.js";
|
|
8
8
|
|
|
9
9
|
// ─── File extension → language mapping ────────────────────────────────────
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { keyHint, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
6
|
import { Type } from "typebox";
|
|
7
|
-
import { LspServerManager,
|
|
7
|
+
import { LspServerManager, formatToolError } from "./manager.js";
|
|
8
8
|
|
|
9
9
|
// ─── TUI rendering ─────────────────────────────────────────────────────────
|
|
10
10
|
|
|
@@ -53,24 +53,6 @@ function err(details: any): ToolResult {
|
|
|
53
53
|
return ok(formatToolError(details), { ok: false, error: details });
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
async function withFile(
|
|
57
|
-
manager: LspServerManager,
|
|
58
|
-
file: string,
|
|
59
|
-
fn: (s: { abs: string; uri: string; state: any }) => Promise<string>,
|
|
60
|
-
options: { timeoutMs?: number } = {},
|
|
61
|
-
): Promise<ToolResult> {
|
|
62
|
-
const resolved = await manager.resolveFileState(file, { timeoutMs: options.timeoutMs });
|
|
63
|
-
if (!resolved.ok) return err(resolved.error);
|
|
64
|
-
const { result } = resolved;
|
|
65
|
-
try {
|
|
66
|
-
const text = await fn(result);
|
|
67
|
-
return ok(text, { ok: true, language: result.state.language, workspace_root: result.state.workspaceRoot });
|
|
68
|
-
} catch (error) {
|
|
69
|
-
if (error instanceof LspToolError) return err(error.details);
|
|
70
|
-
return err({ kind: "tool_execution_failed", file: result.abs, language: result.state.language, workspace_root: result.state.workspaceRoot, command: result.state.command, install_hint: result.state.installHint, message: error instanceof Error ? error.message : String(error) });
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
56
|
function withTimeout(promise: Promise<ToolResult>, ms: number, label: string): Promise<ToolResult> {
|
|
75
57
|
return Promise.race([
|
|
76
58
|
promise,
|
|
@@ -84,25 +66,6 @@ function severityLabel(s: number): string {
|
|
|
84
66
|
return s === 1 ? "error" : s === 2 ? "warning" : s === 3 ? "info" : "hint";
|
|
85
67
|
}
|
|
86
68
|
|
|
87
|
-
function symbolKindLabel(kind: number): string {
|
|
88
|
-
const labels: Record<number, string> = { 2:"module",3:"namespace",5:"class",6:"method",7:"property",8:"field",9:"constructor",11:"interface",12:"function",13:"variable",14:"constant",23:"struct",24:"event" };
|
|
89
|
-
return labels[kind] ?? "symbol";
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function formatDocumentSymbols(file: string, symbols: any[]): string {
|
|
93
|
-
if (symbols.length === 0) return `${file}: no symbols`;
|
|
94
|
-
const lines = [`${file}: ${symbols.length} top-level symbol(s)`];
|
|
95
|
-
const append = (syms: any[], depth: number) => {
|
|
96
|
-
for (const s of syms) {
|
|
97
|
-
const detail = s.detail ? ` — ${s.detail}` : "";
|
|
98
|
-
lines.push(`${" ".repeat(depth)}${symbolKindLabel(s.kind)} ${s.name}${detail} @ ${s.range.start.line + 1}:${s.range.start.character + 1}`);
|
|
99
|
-
if (s.children?.length) append(s.children, depth + 1);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
append(symbols, 1);
|
|
103
|
-
return lines.join("\n");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
69
|
// ─── Register tools ───────────────────────────────────────────────────────
|
|
107
70
|
|
|
108
71
|
export function registerLspTools(pi: ExtensionAPI, manager: LspServerManager) {
|
|
@@ -156,34 +119,6 @@ export function registerLspTools(pi: ExtensionAPI, manager: LspServerManager) {
|
|
|
156
119
|
})(), totalTimeout, "LSP diagnostics");
|
|
157
120
|
},
|
|
158
121
|
});
|
|
159
|
-
|
|
160
|
-
// ── lsp_document_symbols ───────────────────────────────────────────────
|
|
161
|
-
pi.registerTool({
|
|
162
|
-
name: "lsp_document_symbols",
|
|
163
|
-
label: "LSP: document symbols",
|
|
164
|
-
description: "List symbols in a file (functions, classes, variables) using the language server.",
|
|
165
|
-
promptSnippet: "List functions, classes, and variables in a file",
|
|
166
|
-
promptGuidelines: [
|
|
167
|
-
"Prefer lsp_document_symbols to find functions, classes, or variables in code instead of grep.",
|
|
168
|
-
"Supported languages: typescript, c, cpp, python, rust, go, ruby, java, lua, svelte.",
|
|
169
|
-
],
|
|
170
|
-
renderResult: renderLspResult,
|
|
171
|
-
parameters: Type.Object({
|
|
172
|
-
path: Type.String({ description: "Path to the file (relative or absolute)." }),
|
|
173
|
-
timeout_ms: Type.Optional(Type.Number({ description: "Overall max ms including server startup. Default 10000." })),
|
|
174
|
-
}),
|
|
175
|
-
execute: async (_id, params, _signal, _update, _ctx): Promise<ToolResult> => {
|
|
176
|
-
const timeoutMs = params.timeout_ms ?? 10_000;
|
|
177
|
-
return withTimeout(
|
|
178
|
-
withFile(manager, params.path, async (result) => {
|
|
179
|
-
const symbols = await result.state.client.documentSymbols(result.uri, timeoutMs);
|
|
180
|
-
return formatDocumentSymbols(result.abs, symbols);
|
|
181
|
-
}, { timeoutMs }),
|
|
182
|
-
timeoutMs,
|
|
183
|
-
"LSP document symbols",
|
|
184
|
-
);
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
122
|
}
|
|
188
123
|
|
|
189
124
|
// ─── Formatting ────────────────────────────────────────────────────────────
|
|
@@ -29,14 +29,3 @@ export interface LspHover {
|
|
|
29
29
|
contents: unknown;
|
|
30
30
|
range?: LspRange;
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
export interface LspDocumentSymbol {
|
|
34
|
-
name: string;
|
|
35
|
-
kind: number;
|
|
36
|
-
range: LspRange;
|
|
37
|
-
selectionRange?: LspRange;
|
|
38
|
-
containerName?: string;
|
|
39
|
-
detail?: string;
|
|
40
|
-
children?: LspDocumentSymbol[];
|
|
41
|
-
uri?: string;
|
|
42
|
-
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codegraph builtin MCP server — config + system-prompt guidance.
|
|
3
|
+
*
|
|
4
|
+
* Injected by `index.ts` → `buildGuidelines()` when the codegraph
|
|
5
|
+
* module is enabled (see `isModuleEnabled("codegraph")` in settings).
|
|
6
|
+
*/
|
|
7
|
+
import type { McpServerConfig } from "../config.js";
|
|
8
|
+
import { isCodegraphModuleEnabled } from "../../../settings.js";
|
|
9
|
+
|
|
10
|
+
export const CODEGRAPH_BUILTIN: Omit<McpServerConfig, "source"> = {
|
|
11
|
+
name: "codegraph",
|
|
12
|
+
command: "codegraph",
|
|
13
|
+
args: ["serve", "--mcp"],
|
|
14
|
+
enabled: false, // overridden by isCodegraphModuleEnabled() at resolve time
|
|
15
|
+
description:
|
|
16
|
+
"Local code knowledge graph (colbymchenry/codegraph). Enable via /dp-settings.",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Predicate for `resolveMcpConfigs` to gate the codegraph server. */
|
|
20
|
+
export function codegraphEnabled(): boolean {
|
|
21
|
+
return isCodegraphModuleEnabled();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const CODEGRAPH_GUIDANCE = [
|
|
25
|
+
"### CodeGraph, code source map",
|
|
26
|
+
"- This project's `codegraph_*` MCP tools are enabled. The graph is a pre-built index; grep/glob/Read of source code is repeating work the index already did.",
|
|
27
|
+
"",
|
|
28
|
+
"#### When to reach for it",
|
|
29
|
+
'- Starting any task that touches code → `codegraph_explore("how does X work")` or `codegraph_files`',
|
|
30
|
+
"- Looking for where a symbol is defined → `codegraph_search <name>`",
|
|
31
|
+
"- Reading a function's body → `codegraph_node <name>` (or `codegraph_explore`)",
|
|
32
|
+
"- Tracing call flow → `codegraph_callers` / `codegraph_callees`",
|
|
33
|
+
"- Assessing refactor risk → `codegraph_impact <name>`",
|
|
34
|
+
"",
|
|
35
|
+
"#### Do NOT do this",
|
|
36
|
+
"- `ls`, `find`, `grep -rn`, `rg` to discover symbols → use `codegraph_search`",
|
|
37
|
+
"- `read` of an entire file to find a function → use `codegraph_explore` first",
|
|
38
|
+
'- Reading 3+ files to understand a module → use `codegraph_explore("how does X work")`',
|
|
39
|
+
"- `bash` with `cat`, `head`, `sed` to view source → use `codegraph_node` or `read` (single file only)",
|
|
40
|
+
"",
|
|
41
|
+
"#### If it errors",
|
|
42
|
+
'- "Project not initialized" → ask the user to run `codegraph init -i` in their terminal',
|
|
43
|
+
"- Empty results → fall back to grep/Read (the index is best-effort, not authoritative)",
|
|
44
|
+
"- Tool timeout → `codegraph_status` to check; if indexer is dead, fall back",
|
|
45
|
+
].join("\n");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builtin MCP servers — aggregate. Each server has its own file; this
|
|
3
|
+
* module re-exports them and the all-server list.
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServerConfig } from "../config.js";
|
|
6
|
+
import { CONTEXT7_BUILTIN } from "./context7.js";
|
|
7
|
+
import { EXA_BUILTIN } from "./exa.js";
|
|
8
|
+
import { CODEGRAPH_BUILTIN, CODEGRAPH_GUIDANCE } from "./codegraph.js";
|
|
9
|
+
|
|
10
|
+
export { CONTEXT7_BUILTIN } from "./context7.js";
|
|
11
|
+
export { EXA_BUILTIN } from "./exa.js";
|
|
12
|
+
export { CODEGRAPH_BUILTIN, CODEGRAPH_GUIDANCE, codegraphEnabled } from "./codegraph.js";
|
|
13
|
+
|
|
14
|
+
/** All builtin servers — flat list for `resolveMcpConfigs` to merge. */
|
|
15
|
+
export const BUILTIN_MCP_SERVERS: Omit<McpServerConfig, "source">[] = [
|
|
16
|
+
CONTEXT7_BUILTIN,
|
|
17
|
+
EXA_BUILTIN,
|
|
18
|
+
CODEGRAPH_BUILTIN,
|
|
19
|
+
];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server metadata cache — persisted tool descriptions and schemas.
|
|
3
|
+
* Stored at `~/.pi/agent/mcp-cache.json` (global) and `<cwd>/.pi/agent/mcp-cache.json` (project).
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import type { McpServerConfig } from "./config.js";
|
|
9
|
+
|
|
10
|
+
export interface McpToolCache {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
inputSchema: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface McpServerCache {
|
|
17
|
+
description?: string;
|
|
18
|
+
tools: McpToolCache[];
|
|
19
|
+
cachedAt: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface McpCache {
|
|
23
|
+
servers: Record<string, McpServerCache>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function globalCachePath(): string {
|
|
27
|
+
return path.join(os.homedir(), ".pi/agent/mcp-cache.json");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function projectCachePath(cwd: string): string {
|
|
31
|
+
return path.join(cwd, ".pi/agent/mcp-cache.json");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function scopedCachePath(scope: "global" | "project", cwd?: string): string {
|
|
35
|
+
return scope === "project" && cwd ? projectCachePath(cwd) : globalCachePath();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readCacheFile(p: string): McpCache | null {
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(p)) return null;
|
|
41
|
+
return JSON.parse(fs.readFileSync(p, "utf-8")) as McpCache;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeCacheFile(p: string, cache: McpCache): void {
|
|
48
|
+
const dir = path.dirname(p);
|
|
49
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
const tmp = `${p}.tmp`;
|
|
51
|
+
fs.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
52
|
+
fs.renameSync(tmp, p);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Load merged cache: global + project. */
|
|
56
|
+
export function loadMcpCache(cwd?: string): McpCache | null {
|
|
57
|
+
const merged: McpCache = { servers: {} };
|
|
58
|
+
|
|
59
|
+
const globalCache = readCacheFile(globalCachePath());
|
|
60
|
+
if (globalCache) {
|
|
61
|
+
merged.servers = { ...merged.servers, ...globalCache.servers };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (cwd) {
|
|
65
|
+
const projectCache = readCacheFile(projectCachePath(cwd));
|
|
66
|
+
if (projectCache) {
|
|
67
|
+
merged.servers = { ...merged.servers, ...projectCache.servers };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return merged;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadScopedMcpCache(scope: "global" | "project", cwd?: string): McpCache | null {
|
|
75
|
+
return readCacheFile(scopedCachePath(scope, cwd));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Save cache to global or project scope. */
|
|
79
|
+
export function saveMcpCache(cache: McpCache, scope: "global" | "project", cwd?: string): void {
|
|
80
|
+
writeCacheFile(scopedCachePath(scope, cwd), cache);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Update a single server's entry in the appropriate cache. */
|
|
84
|
+
export function updateServerCache(
|
|
85
|
+
serverName: string,
|
|
86
|
+
entry: McpServerCache,
|
|
87
|
+
scope: "global" | "project",
|
|
88
|
+
cwd?: string,
|
|
89
|
+
): void {
|
|
90
|
+
const p = scopedCachePath(scope, cwd);
|
|
91
|
+
const existing = readCacheFile(p) || { servers: {} };
|
|
92
|
+
existing.servers[serverName] = entry;
|
|
93
|
+
writeCacheFile(p, existing);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cleanupOneCache(p: string, names: Set<string>): void {
|
|
97
|
+
const cache = readCacheFile(p);
|
|
98
|
+
if (!cache) return;
|
|
99
|
+
let changed = false;
|
|
100
|
+
for (const name of Object.keys(cache.servers)) {
|
|
101
|
+
if (!names.has(name)) {
|
|
102
|
+
delete cache.servers[name];
|
|
103
|
+
changed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (changed) writeCacheFile(p, cache);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function cleanupStaleCache(configs: McpServerConfig[], cwd?: string): void {
|
|
110
|
+
const names = new Set(configs.map(c => c.name));
|
|
111
|
+
cleanupOneCache(globalCachePath(), names);
|
|
112
|
+
if (cwd) {
|
|
113
|
+
const projectCache = projectCachePath(cwd);
|
|
114
|
+
const projectMcpJson = path.join(cwd, ".pi/agent/mcp.json");
|
|
115
|
+
// If project mcp.json doesn't exist, remove project cache entirely
|
|
116
|
+
if (!fs.existsSync(projectMcpJson)) {
|
|
117
|
+
if (fs.existsSync(projectCache)) {
|
|
118
|
+
fs.unlinkSync(projectCache);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
cleanupOneCache(projectCache, names);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -2,8 +2,8 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
2
2
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
3
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
4
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
|
-
import type { McpServerConfig } from "./
|
|
6
|
-
import { isSseUrl } from "./
|
|
5
|
+
import type { McpServerConfig } from "./config.js";
|
|
6
|
+
import { isSseUrl } from "./config.js";
|
|
7
7
|
|
|
8
8
|
export interface McpToolSpec {
|
|
9
9
|
name: string;
|