@isac322/pi-codegraph 0.2.0 → 0.3.1
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/.mcp.json +1 -2
- package/CONTRIBUTING.md +63 -0
- package/README.md +35 -2
- package/dist/bin/codegraph-mcp.d.ts +3 -0
- package/dist/bin/codegraph-mcp.d.ts.map +1 -0
- package/dist/bin/codegraph-mcp.js +199 -0
- package/dist/bin/codegraph-mcp.js.map +1 -0
- package/dist/extensions/omp.d.ts +14 -0
- package/dist/extensions/omp.d.ts.map +1 -0
- package/dist/extensions/omp.js +19 -0
- package/dist/extensions/omp.js.map +1 -0
- package/dist/extensions/pi.d.ts +3 -0
- package/dist/extensions/pi.d.ts.map +1 -0
- package/dist/extensions/pi.js +162 -0
- package/dist/extensions/pi.js.map +1 -0
- package/dist/lib/codegraph.d.ts +65 -0
- package/dist/lib/codegraph.d.ts.map +1 -0
- package/dist/lib/codegraph.js +567 -0
- package/dist/lib/codegraph.js.map +1 -0
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +131 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/jsonrpc.d.ts +19 -0
- package/dist/lib/jsonrpc.d.ts.map +1 -0
- package/dist/lib/jsonrpc.js +116 -0
- package/dist/lib/jsonrpc.js.map +1 -0
- package/dist/lib/pi-mcp-client.d.ts +15 -0
- package/dist/lib/pi-mcp-client.d.ts.map +1 -0
- package/dist/lib/pi-mcp-client.js +84 -0
- package/dist/lib/pi-mcp-client.js.map +1 -0
- package/dist/lib/prompt.d.ts +7 -0
- package/dist/lib/prompt.d.ts.map +1 -0
- package/dist/lib/prompt.js +16 -0
- package/dist/lib/prompt.js.map +1 -0
- package/dist/lib/tool-metadata.d.ts +10 -0
- package/dist/lib/tool-metadata.d.ts.map +1 -0
- package/dist/lib/tool-metadata.js +163 -0
- package/dist/lib/tool-metadata.js.map +1 -0
- package/dist/lib/types.d.ts +69 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/worker-pool.d.ts +19 -0
- package/dist/lib/worker-pool.d.ts.map +1 -0
- package/dist/lib/worker-pool.js +221 -0
- package/dist/lib/worker-pool.js.map +1 -0
- package/package.json +30 -9
- package/bin/codegraph-mcp.mjs +0 -146
- package/extensions/omp.mjs +0 -14
- package/extensions/pi.mjs +0 -127
- package/lib/codegraph.mjs +0 -415
- package/lib/config.mjs +0 -103
- package/lib/jsonrpc.mjs +0 -90
- package/lib/pi-mcp-client.mjs +0 -68
- package/lib/prompt.mjs +0 -15
- package/lib/tool-metadata.mjs +0 -97
- package/lib/worker-pool.mjs +0 -107
package/lib/jsonrpc.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
export class JsonRpcPeer {
|
|
2
|
-
constructor(readable, writable, options = {}) {
|
|
3
|
-
this.readable = readable;
|
|
4
|
-
this.writable = writable;
|
|
5
|
-
this.name = options.name || "JSON-RPC peer";
|
|
6
|
-
this.nextId = 1;
|
|
7
|
-
this.pending = new Map();
|
|
8
|
-
this.buffer = "";
|
|
9
|
-
this.closed = false;
|
|
10
|
-
readable.on("data", (chunk) => this.#onData(chunk));
|
|
11
|
-
readable.on("error", (error) => this.close(error));
|
|
12
|
-
readable.on("end", () => this.close(new Error(`${this.name} closed`)));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
request(method, params = {}, options = {}) {
|
|
16
|
-
if (this.closed) return Promise.reject(new Error(`${this.name} is closed`));
|
|
17
|
-
const id = this.nextId++;
|
|
18
|
-
const timeoutMs = options.timeoutMs || 30_000;
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
let timer;
|
|
21
|
-
const finish = (error, value) => {
|
|
22
|
-
clearTimeout(timer);
|
|
23
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
24
|
-
this.pending.delete(id);
|
|
25
|
-
error ? reject(error) : resolve(value);
|
|
26
|
-
};
|
|
27
|
-
const onAbort = () => {
|
|
28
|
-
this.notify("notifications/cancelled", { requestId: id, reason: "aborted" });
|
|
29
|
-
const error = new Error(`${method} aborted`);
|
|
30
|
-
error.name = "AbortError";
|
|
31
|
-
finish(error);
|
|
32
|
-
};
|
|
33
|
-
timer = setTimeout(() => {
|
|
34
|
-
this.notify("notifications/cancelled", { requestId: id, reason: "timeout" });
|
|
35
|
-
const error = new Error(`${method} timed out after ${timeoutMs}ms`);
|
|
36
|
-
error.code = "ETIMEDOUT";
|
|
37
|
-
finish(error);
|
|
38
|
-
}, timeoutMs);
|
|
39
|
-
timer.unref?.();
|
|
40
|
-
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
41
|
-
this.pending.set(id, { finish });
|
|
42
|
-
this.#write({ jsonrpc: "2.0", id, method, params });
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
notify(method, params = {}) {
|
|
47
|
-
if (!this.closed) this.#write({ jsonrpc: "2.0", method, params });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
close(error = new Error(`${this.name} closed`)) {
|
|
51
|
-
if (this.closed) return;
|
|
52
|
-
this.closed = true;
|
|
53
|
-
for (const pending of this.pending.values()) pending.finish(error);
|
|
54
|
-
this.pending.clear();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#write(message) {
|
|
58
|
-
try {
|
|
59
|
-
this.writable.write(`${JSON.stringify(message)}\n`);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
this.close(error);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
#onData(chunk) {
|
|
66
|
-
this.buffer += chunk.toString("utf8");
|
|
67
|
-
let newline;
|
|
68
|
-
while ((newline = this.buffer.indexOf("\n")) !== -1) {
|
|
69
|
-
const line = this.buffer.slice(0, newline).trim();
|
|
70
|
-
this.buffer = this.buffer.slice(newline + 1);
|
|
71
|
-
if (!line) continue;
|
|
72
|
-
let message;
|
|
73
|
-
try {
|
|
74
|
-
message = JSON.parse(line);
|
|
75
|
-
} catch {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (message.id === undefined || (!Object.hasOwn(message, "result") && !Object.hasOwn(message, "error"))) continue;
|
|
79
|
-
const pending = this.pending.get(message.id);
|
|
80
|
-
if (!pending) continue;
|
|
81
|
-
if (message.error) {
|
|
82
|
-
const error = new Error(message.error.message || JSON.stringify(message.error));
|
|
83
|
-
error.code = message.error.code;
|
|
84
|
-
pending.finish(error);
|
|
85
|
-
} else {
|
|
86
|
-
pending.finish(undefined, message.result);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
package/lib/pi-mcp-client.mjs
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { JsonRpcPeer } from "./jsonrpc.mjs";
|
|
4
|
-
import { sanitizeDiagnostic } from "./codegraph.mjs";
|
|
5
|
-
import { settingsEnvironment } from "./config.mjs";
|
|
6
|
-
|
|
7
|
-
const serverPath = fileURLToPath(new URL("../bin/codegraph-mcp.mjs", import.meta.url));
|
|
8
|
-
|
|
9
|
-
export class PiCodeGraphClient {
|
|
10
|
-
constructor(settings, baseRoot) {
|
|
11
|
-
this.settings = settings;
|
|
12
|
-
this.baseRoot = baseRoot;
|
|
13
|
-
this.startPromise = undefined;
|
|
14
|
-
this.child = undefined;
|
|
15
|
-
this.peer = undefined;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async start() {
|
|
19
|
-
if (this.peer) return;
|
|
20
|
-
if (this.startPromise) return this.startPromise;
|
|
21
|
-
this.startPromise = this.#start().finally(() => { this.startPromise = undefined; });
|
|
22
|
-
return this.startPromise;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async #start() {
|
|
26
|
-
const child = spawn(process.execPath, [serverPath], {
|
|
27
|
-
cwd: this.baseRoot,
|
|
28
|
-
env: { ...process.env, ...settingsEnvironment(this.settings, this.baseRoot, true) },
|
|
29
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
30
|
-
});
|
|
31
|
-
let stderr = "";
|
|
32
|
-
child.stderr.on("data", (chunk) => { stderr = `${stderr}${chunk.toString("utf8")}`.slice(-8_000); });
|
|
33
|
-
const peer = new JsonRpcPeer(child.stdout, child.stdin, { name: "pi-codegraph MCP facade" });
|
|
34
|
-
child.on("error", (error) => peer.close(error));
|
|
35
|
-
child.on("exit", (code) => peer.close(new Error(sanitizeDiagnostic(stderr) || `pi-codegraph MCP facade exited with code ${code}`)));
|
|
36
|
-
this.child = child;
|
|
37
|
-
this.peer = peer;
|
|
38
|
-
try {
|
|
39
|
-
await peer.request("initialize", {
|
|
40
|
-
protocolVersion: "2024-11-05",
|
|
41
|
-
capabilities: {},
|
|
42
|
-
clientInfo: { name: "pi-codegraph-pi", version: "0.2.0" },
|
|
43
|
-
}, { timeoutMs: this.settings.requestTimeoutMs });
|
|
44
|
-
peer.notify("initialized", {});
|
|
45
|
-
} catch (error) {
|
|
46
|
-
await this.close();
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async request(method, params = {}, signal) {
|
|
52
|
-
await this.start();
|
|
53
|
-
return this.peer.request(method, params, { signal, timeoutMs: this.settings.requestTimeoutMs });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async callTool(name, args, signal) {
|
|
57
|
-
return this.request("tools/call", { name, arguments: args }, signal);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async close() {
|
|
61
|
-
const peer = this.peer;
|
|
62
|
-
const child = this.child;
|
|
63
|
-
this.peer = undefined;
|
|
64
|
-
this.child = undefined;
|
|
65
|
-
peer?.close(new Error("Pi session closed"));
|
|
66
|
-
if (child && !child.killed) child.kill();
|
|
67
|
-
}
|
|
68
|
-
}
|
package/lib/prompt.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function buildCodeGraphPrompt({ runtime, cwd, status }) {
|
|
2
|
-
const projectRule = runtime === "omp"
|
|
3
|
-
? `Always pass projectPath=\"${cwd}\" so parent and child agents query the correct worktree.`
|
|
4
|
-
: `The extension automatically binds omitted projectPath to \"${cwd}\".`;
|
|
5
|
-
const state = status.identityMatches === false ? "identity-mismatch" : status.state;
|
|
6
|
-
return [
|
|
7
|
-
"CodeGraph structural tools are available as codegraph_* tools.",
|
|
8
|
-
`Active project: ${cwd}`,
|
|
9
|
-
`Index state: ${state}${status.lastSyncAt ? `; last sync: ${status.lastSyncAt}` : ""}`,
|
|
10
|
-
projectRule,
|
|
11
|
-
"For architecture, execution flow, symbol location, dependency impact, and project navigation, use CodeGraph before grep/read.",
|
|
12
|
-
"Use codegraph_explore for broad flows, codegraph_search for symbol names, codegraph_node for a known symbol, codegraph_files for structure, and codegraph_callers/codegraph_impact before shared API changes.",
|
|
13
|
-
"Use grep/read for literal text, generated names, or when CodeGraph is insufficient.",
|
|
14
|
-
].join("\n");
|
|
15
|
-
}
|
package/lib/tool-metadata.mjs
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
const projectPath = {
|
|
2
|
-
type: "string",
|
|
3
|
-
description: "Absolute path to the target project or worktree. Pi fills this with the active cwd when omitted; OMP child agents should pass their exact worktree path.",
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
const kind = {
|
|
7
|
-
type: "string",
|
|
8
|
-
enum: ["function", "method", "class", "interface", "type", "variable", "route", "component"],
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function object(properties, required = []) {
|
|
12
|
-
return { type: "object", properties, required, additionalProperties: false };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const codegraphTools = Object.freeze([
|
|
16
|
-
{
|
|
17
|
-
name: "codegraph_search",
|
|
18
|
-
label: "CodeGraph Search",
|
|
19
|
-
description: "Search indexed declarations by symbol name. Use this before text search when you know all or part of a symbol name.",
|
|
20
|
-
promptSnippet: "Find declarations and symbol locations by name in the active CodeGraph index.",
|
|
21
|
-
promptGuidelines: ["Use for symbol names, not literal strings.", "Follow a result with codegraph_node when implementation or relationships are needed."],
|
|
22
|
-
inputSchema: object({ query: { type: "string", description: "Symbol name or partial name." }, kind, limit: { type: "number", default: 10 }, projectPath }, ["query"]),
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: "codegraph_node",
|
|
26
|
-
label: "CodeGraph Node",
|
|
27
|
-
description: "Inspect one known symbol, including signature, location, source, callers, and callees.",
|
|
28
|
-
promptSnippet: "Inspect a known symbol and its immediate relationships.",
|
|
29
|
-
promptGuidelines: ["Use after codegraph_search identifies the symbol.", "Set includeCode only when source is necessary."],
|
|
30
|
-
inputSchema: object({ symbol: { type: "string", description: "Exact or unambiguous symbol name." }, includeCode: { type: "boolean", default: false }, projectPath }, ["symbol"]),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: "codegraph_files",
|
|
34
|
-
label: "CodeGraph Files",
|
|
35
|
-
description: "Read the indexed project file tree. Paths are normalized to repo-relative POSIX prefixes.",
|
|
36
|
-
promptSnippet: "Inspect indexed project structure without filesystem traversal.",
|
|
37
|
-
promptGuidelines: ["Use before read/glob for architectural navigation.", "Pass a repo-relative directory prefix such as src/components."],
|
|
38
|
-
inputSchema: object({ path: { type: "string", description: "Repo-relative path prefix." }, pattern: { type: "string" }, format: { type: "string", enum: ["tree", "flat", "grouped"], default: "tree" }, includeMetadata: { type: "boolean", default: true }, maxDepth: { type: "number" }, projectPath }),
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: "codegraph_callers",
|
|
42
|
-
label: "CodeGraph Callers",
|
|
43
|
-
description: "Find functions and methods that call a symbol.",
|
|
44
|
-
promptSnippet: "Trace inbound calls to a known symbol.",
|
|
45
|
-
promptGuidelines: ["Use for inbound flow and direct impact."],
|
|
46
|
-
inputSchema: object({ symbol: { type: "string" }, limit: { type: "number", default: 20 }, projectPath }, ["symbol"]),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "codegraph_callees",
|
|
50
|
-
label: "CodeGraph Callees",
|
|
51
|
-
description: "Find functions and methods called by a symbol.",
|
|
52
|
-
promptSnippet: "Trace outbound calls from a known symbol.",
|
|
53
|
-
promptGuidelines: ["Use for downstream execution flow."],
|
|
54
|
-
inputSchema: object({ symbol: { type: "string" }, limit: { type: "number", default: 20 }, projectPath }, ["symbol"]),
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: "codegraph_impact",
|
|
58
|
-
label: "CodeGraph Impact",
|
|
59
|
-
description: "Analyze the transitive impact radius of changing a symbol.",
|
|
60
|
-
promptSnippet: "Estimate what a symbol change can affect.",
|
|
61
|
-
promptGuidelines: ["Use before editing shared APIs or heavily referenced symbols."],
|
|
62
|
-
inputSchema: object({ symbol: { type: "string" }, depth: { type: "number", default: 2 }, projectPath }, ["symbol"]),
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: "codegraph_explore",
|
|
66
|
-
label: "CodeGraph Explore",
|
|
67
|
-
description: "Explore several related symbols and source locations grouped by file. Best first tool for broad architecture and flow questions.",
|
|
68
|
-
promptSnippet: "Explore a feature, flow, or architectural concept across related symbols.",
|
|
69
|
-
promptGuidelines: ["Prefer this first for broad how-does-it-work questions.", "Narrow the query before increasing maxFiles."],
|
|
70
|
-
inputSchema: object({ query: { type: "string", description: "Specific symbols, files, or code terms to explore." }, maxFiles: { type: "number", default: 12 }, projectPath }, ["query"]),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "codegraph_status",
|
|
74
|
-
label: "CodeGraph Status",
|
|
75
|
-
description: "Report CodeGraph index health and pending synchronization state.",
|
|
76
|
-
promptSnippet: "Check whether the active project index is ready and current.",
|
|
77
|
-
promptGuidelines: ["Use when another CodeGraph tool reports an index or lock error."],
|
|
78
|
-
inputSchema: object({ projectPath }),
|
|
79
|
-
},
|
|
80
|
-
]);
|
|
81
|
-
|
|
82
|
-
export const codegraphToolNames = Object.freeze(codegraphTools.map((tool) => tool.name));
|
|
83
|
-
|
|
84
|
-
export function toolCallLabel(name, args = {}) {
|
|
85
|
-
const value = args.query || args.symbol || args.path || "status";
|
|
86
|
-
const project = typeof args.projectPath === "string" ? args.projectPath.split(/[\\/]/).filter(Boolean).at(-1) : "current";
|
|
87
|
-
return `${value} · ${project}`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function summarizeToolText(text) {
|
|
91
|
-
const lines = String(text || "").split("\n").filter((line) => line.trim());
|
|
92
|
-
return {
|
|
93
|
-
firstLine: lines[0] || "No output",
|
|
94
|
-
lineCount: lines.length,
|
|
95
|
-
truncated: String(text || "").includes("[pi-codegraph output truncated]"),
|
|
96
|
-
};
|
|
97
|
-
}
|
package/lib/worker-pool.mjs
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { pathToFileURL } from "node:url";
|
|
3
|
-
import { JsonRpcPeer } from "./jsonrpc.mjs";
|
|
4
|
-
import { resolveCodeGraphLaunch, sanitizeDiagnostic } from "./codegraph.mjs";
|
|
5
|
-
|
|
6
|
-
export class CodeGraphWorkerPool {
|
|
7
|
-
constructor(settings) {
|
|
8
|
-
this.settings = settings;
|
|
9
|
-
this.entries = new Map();
|
|
10
|
-
this.creating = new Map();
|
|
11
|
-
this.closed = false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
activeProjects() {
|
|
15
|
-
return new Set(this.entries.keys());
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async call(projectPath, toolName, args, signal) {
|
|
19
|
-
const entry = await this.#get(projectPath);
|
|
20
|
-
entry.busy += 1;
|
|
21
|
-
clearTimeout(entry.idleTimer);
|
|
22
|
-
try {
|
|
23
|
-
return await entry.peer.request("tools/call", { name: toolName, arguments: args }, { signal, timeoutMs: this.settings.requestTimeoutMs });
|
|
24
|
-
} catch (error) {
|
|
25
|
-
if (error?.name === "AbortError" || error?.code === "ETIMEDOUT") await this.closeProject(projectPath, error);
|
|
26
|
-
throw error;
|
|
27
|
-
} finally {
|
|
28
|
-
entry.busy -= 1;
|
|
29
|
-
entry.lastUsed = Date.now();
|
|
30
|
-
if (this.entries.get(projectPath) === entry) this.#scheduleIdle(entry);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async #get(projectPath) {
|
|
35
|
-
if (this.closed) throw new Error("CodeGraph worker pool is closed");
|
|
36
|
-
const existing = this.entries.get(projectPath);
|
|
37
|
-
if (existing) return existing;
|
|
38
|
-
const pending = this.creating.get(projectPath);
|
|
39
|
-
if (pending) return pending;
|
|
40
|
-
const creation = this.#create(projectPath).finally(() => this.creating.delete(projectPath));
|
|
41
|
-
this.creating.set(projectPath, creation);
|
|
42
|
-
return creation;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async #create(projectPath) {
|
|
46
|
-
await this.#evictForCapacity();
|
|
47
|
-
const launch = await resolveCodeGraphLaunch(this.settings, ["serve", "--mcp", "--path", projectPath]);
|
|
48
|
-
const child = spawn(launch.command, launch.args, { cwd: projectPath, env: process.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
49
|
-
let stderr = "";
|
|
50
|
-
child.stderr.on("data", (chunk) => { stderr = `${stderr}${chunk.toString("utf8")}`.slice(-8_000); });
|
|
51
|
-
const peer = new JsonRpcPeer(child.stdout, child.stdin, { name: `CodeGraph worker (${projectPath})` });
|
|
52
|
-
const entry = { projectPath, child, peer, busy: 0, lastUsed: Date.now(), idleTimer: undefined };
|
|
53
|
-
const onExit = (code) => {
|
|
54
|
-
const diagnostic = sanitizeDiagnostic(stderr) || `CodeGraph worker exited with code ${code}`;
|
|
55
|
-
peer.close(new Error(diagnostic));
|
|
56
|
-
if (this.entries.get(projectPath) === entry) this.entries.delete(projectPath);
|
|
57
|
-
};
|
|
58
|
-
child.on("error", (error) => peer.close(error));
|
|
59
|
-
child.on("exit", onExit);
|
|
60
|
-
try {
|
|
61
|
-
const rootUri = pathToFileURL(projectPath).href;
|
|
62
|
-
await peer.request("initialize", {
|
|
63
|
-
protocolVersion: "2024-11-05",
|
|
64
|
-
rootUri,
|
|
65
|
-
workspaceFolders: [{ uri: rootUri, name: projectPath.split(/[\\/]/).at(-1) || projectPath }],
|
|
66
|
-
capabilities: {},
|
|
67
|
-
clientInfo: { name: "pi-codegraph", version: "0.2.0" },
|
|
68
|
-
}, { timeoutMs: this.settings.requestTimeoutMs });
|
|
69
|
-
peer.notify("initialized", {});
|
|
70
|
-
this.entries.set(projectPath, entry);
|
|
71
|
-
this.#scheduleIdle(entry);
|
|
72
|
-
return entry;
|
|
73
|
-
} catch (error) {
|
|
74
|
-
child.kill();
|
|
75
|
-
peer.close(error);
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
#scheduleIdle(entry) {
|
|
81
|
-
clearTimeout(entry.idleTimer);
|
|
82
|
-
if (entry.busy > 0) return;
|
|
83
|
-
entry.idleTimer = setTimeout(() => this.closeProject(entry.projectPath), this.settings.workerIdleTimeoutMs);
|
|
84
|
-
entry.idleTimer.unref?.();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async #evictForCapacity() {
|
|
88
|
-
if (this.entries.size < this.settings.maxWorkers) return;
|
|
89
|
-
const idle = [...this.entries.values()].filter((entry) => entry.busy === 0).sort((a, b) => a.lastUsed - b.lastUsed);
|
|
90
|
-
if (!idle.length) throw new Error(`CodeGraph worker limit reached (${this.settings.maxWorkers}); all workers are busy.`);
|
|
91
|
-
await this.closeProject(idle[0].projectPath);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async closeProject(projectPath, error = new Error("CodeGraph worker closed")) {
|
|
95
|
-
const entry = this.entries.get(projectPath);
|
|
96
|
-
if (!entry) return;
|
|
97
|
-
this.entries.delete(projectPath);
|
|
98
|
-
clearTimeout(entry.idleTimer);
|
|
99
|
-
entry.peer.close(error);
|
|
100
|
-
if (!entry.child.killed) entry.child.kill();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async close() {
|
|
104
|
-
this.closed = true;
|
|
105
|
-
await Promise.all([...this.entries.keys()].map((projectPath) => this.closeProject(projectPath)));
|
|
106
|
-
}
|
|
107
|
-
}
|