my-pi 0.0.9 → 0.0.10
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/dist/{api-Dcq2JalY.js → api-7LVj6kUV.js} +594 -275
- package/dist/api-7LVj6kUV.js.map +1 -0
- package/dist/api.js +1 -1
- package/dist/index.js +20 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/lsp.test.ts +361 -1
- package/src/extensions/lsp.ts +617 -215
- package/src/extensions/mcp.ts +169 -74
- package/src/extensions/recall.ts +17 -13
- package/dist/api-Dcq2JalY.js.map +0 -1
|
@@ -3,7 +3,7 @@ import { cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, ren
|
|
|
3
3
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import {
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { Container, SettingsList, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
@@ -824,6 +824,18 @@ ${task || "Continue from where the previous session left off."}
|
|
|
824
824
|
}
|
|
825
825
|
//#endregion
|
|
826
826
|
//#region src/lsp/client.ts
|
|
827
|
+
var LspClientStartError = class extends Error {
|
|
828
|
+
command;
|
|
829
|
+
args;
|
|
830
|
+
code;
|
|
831
|
+
constructor(message, options) {
|
|
832
|
+
super(message, options.cause ? { cause: options.cause } : void 0);
|
|
833
|
+
this.name = "LspClientStartError";
|
|
834
|
+
this.command = options.command;
|
|
835
|
+
this.args = options.args;
|
|
836
|
+
this.code = options.code;
|
|
837
|
+
}
|
|
838
|
+
};
|
|
827
839
|
var LspClient = class extends EventEmitter {
|
|
828
840
|
#proc = null;
|
|
829
841
|
#options;
|
|
@@ -833,6 +845,7 @@ var LspClient = class extends EventEmitter {
|
|
|
833
845
|
#initialized = false;
|
|
834
846
|
#open_docs = /* @__PURE__ */ new Map();
|
|
835
847
|
#diagnostics_by_uri = /* @__PURE__ */ new Map();
|
|
848
|
+
#diagnostic_waiters = /* @__PURE__ */ new Set();
|
|
836
849
|
constructor(options) {
|
|
837
850
|
super();
|
|
838
851
|
this.#options = options;
|
|
@@ -846,10 +859,29 @@ var LspClient = class extends EventEmitter {
|
|
|
846
859
|
],
|
|
847
860
|
env: process.env
|
|
848
861
|
});
|
|
862
|
+
let start_reject = null;
|
|
863
|
+
const start_failure = new Promise((_, reject) => {
|
|
864
|
+
start_reject = reject;
|
|
865
|
+
});
|
|
866
|
+
const reject_start = (error) => {
|
|
867
|
+
if (!start_reject) return false;
|
|
868
|
+
const reject = start_reject;
|
|
869
|
+
start_reject = null;
|
|
870
|
+
reject(error);
|
|
871
|
+
return true;
|
|
872
|
+
};
|
|
873
|
+
const start_error = (message, cause, code) => new LspClientStartError(message, {
|
|
874
|
+
command: this.#options.command,
|
|
875
|
+
args: this.#options.args,
|
|
876
|
+
cause,
|
|
877
|
+
code
|
|
878
|
+
});
|
|
849
879
|
this.#proc.on("error", (err) => {
|
|
850
|
-
this.
|
|
880
|
+
const wrapped = start_error(`Failed to spawn ${this.#options.command}`, err, error_code(err));
|
|
881
|
+
if (!reject_start(wrapped)) this.#emit_error(wrapped);
|
|
851
882
|
});
|
|
852
883
|
this.#proc.on("close", () => {
|
|
884
|
+
if (!this.#initialized) reject_start(start_error(`LSP server ${this.#options.command} closed before initialization`));
|
|
853
885
|
for (const pending of this.#pending.values()) {
|
|
854
886
|
clearTimeout(pending.timer);
|
|
855
887
|
pending.reject(/* @__PURE__ */ new Error("LSP server closed"));
|
|
@@ -863,29 +895,35 @@ var LspClient = class extends EventEmitter {
|
|
|
863
895
|
this.#buffer = Buffer.concat([this.#buffer, chunk]);
|
|
864
896
|
this.#drain_buffer();
|
|
865
897
|
});
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
898
|
+
try {
|
|
899
|
+
await Promise.race([this.#request("initialize", {
|
|
900
|
+
processId: process.pid,
|
|
901
|
+
rootUri: this.#options.root_uri,
|
|
902
|
+
capabilities: {
|
|
903
|
+
textDocument: {
|
|
904
|
+
publishDiagnostics: { relatedInformation: true },
|
|
905
|
+
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
906
|
+
definition: { linkSupport: false },
|
|
907
|
+
references: {},
|
|
908
|
+
documentSymbol: { hierarchicalDocumentSymbolSupport: true }
|
|
909
|
+
},
|
|
910
|
+
workspace: {
|
|
911
|
+
workspaceFolders: true,
|
|
912
|
+
symbol: {}
|
|
913
|
+
}
|
|
876
914
|
},
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
},
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
915
|
+
workspaceFolders: [{
|
|
916
|
+
uri: this.#options.root_uri,
|
|
917
|
+
name: "workspace"
|
|
918
|
+
}]
|
|
919
|
+
}), start_failure]);
|
|
920
|
+
this.#notify("initialized", {});
|
|
921
|
+
this.#initialized = true;
|
|
922
|
+
start_reject = null;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
await this.stop();
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
889
927
|
}
|
|
890
928
|
is_ready() {
|
|
891
929
|
return this.#initialized;
|
|
@@ -945,23 +983,25 @@ var LspClient = class extends EventEmitter {
|
|
|
945
983
|
get_diagnostics(uri) {
|
|
946
984
|
return this.#diagnostics_by_uri.get(uri) ?? [];
|
|
947
985
|
}
|
|
948
|
-
get_all_diagnostics() {
|
|
949
|
-
return new Map(this.#diagnostics_by_uri);
|
|
950
|
-
}
|
|
951
986
|
async wait_for_diagnostics(uri, timeout_ms = 1500) {
|
|
952
987
|
if (this.#diagnostics_by_uri.has(uri)) return this.get_diagnostics(uri);
|
|
953
988
|
return new Promise((resolve) => {
|
|
954
|
-
|
|
955
|
-
|
|
989
|
+
let active = true;
|
|
990
|
+
const cleanup = () => {
|
|
991
|
+
if (!active) return;
|
|
992
|
+
active = false;
|
|
956
993
|
this.off("diagnostics", handler);
|
|
957
994
|
clearTimeout(timer);
|
|
995
|
+
this.#diagnostic_waiters.delete(cleanup);
|
|
958
996
|
resolve(this.get_diagnostics(uri));
|
|
959
997
|
};
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
998
|
+
const handler = (event_uri) => {
|
|
999
|
+
if (event_uri !== uri) return;
|
|
1000
|
+
cleanup();
|
|
1001
|
+
};
|
|
1002
|
+
const timer = setTimeout(cleanup, timeout_ms);
|
|
964
1003
|
this.on("diagnostics", handler);
|
|
1004
|
+
this.#diagnostic_waiters.add(cleanup);
|
|
965
1005
|
});
|
|
966
1006
|
}
|
|
967
1007
|
async stop() {
|
|
@@ -974,6 +1014,7 @@ var LspClient = class extends EventEmitter {
|
|
|
974
1014
|
pending.reject(/* @__PURE__ */ new Error("LSP client stopped"));
|
|
975
1015
|
}
|
|
976
1016
|
this.#pending.clear();
|
|
1017
|
+
for (const cleanup of Array.from(this.#diagnostic_waiters)) cleanup();
|
|
977
1018
|
if (this.#proc) {
|
|
978
1019
|
this.#proc.kill();
|
|
979
1020
|
this.#proc = null;
|
|
@@ -1022,6 +1063,9 @@ var LspClient = class extends EventEmitter {
|
|
|
1022
1063
|
const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "ascii");
|
|
1023
1064
|
this.#proc.stdin.write(Buffer.concat([header, body]));
|
|
1024
1065
|
}
|
|
1066
|
+
#emit_error(error) {
|
|
1067
|
+
if (this.listenerCount("error") > 0) this.emit("error", error);
|
|
1068
|
+
}
|
|
1025
1069
|
#drain_buffer() {
|
|
1026
1070
|
while (true) {
|
|
1027
1071
|
const header_end = this.#buffer.indexOf("\r\n\r\n");
|
|
@@ -1039,14 +1083,15 @@ var LspClient = class extends EventEmitter {
|
|
|
1039
1083
|
try {
|
|
1040
1084
|
this.#handle_message(JSON.parse(body.toString("utf8")));
|
|
1041
1085
|
} catch (error) {
|
|
1042
|
-
this
|
|
1086
|
+
this.#emit_error(error);
|
|
1043
1087
|
}
|
|
1044
1088
|
}
|
|
1045
1089
|
}
|
|
1046
1090
|
#handle_message(message) {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
this.#pending.
|
|
1091
|
+
const numeric_id = typeof message.id === "number" ? message.id : typeof message.id === "string" && /^-?\d+$/.test(message.id) ? Number(message.id) : null;
|
|
1092
|
+
if (numeric_id != null && this.#pending.has(numeric_id)) {
|
|
1093
|
+
const pending = this.#pending.get(numeric_id);
|
|
1094
|
+
this.#pending.delete(numeric_id);
|
|
1050
1095
|
clearTimeout(pending.timer);
|
|
1051
1096
|
if (message.error) pending.reject(/* @__PURE__ */ new Error(`LSP error ${message.error.code}: ${message.error.message}`));
|
|
1052
1097
|
else pending.resolve(message.result);
|
|
@@ -1090,6 +1135,9 @@ function normalize_document_symbol_result(result) {
|
|
|
1090
1135
|
function file_path_to_uri(file_path) {
|
|
1091
1136
|
return pathToFileURL(file_path).href;
|
|
1092
1137
|
}
|
|
1138
|
+
function error_code(error) {
|
|
1139
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1140
|
+
}
|
|
1093
1141
|
//#endregion
|
|
1094
1142
|
//#region src/lsp/servers.ts
|
|
1095
1143
|
const EXTENSION_LANGUAGES = {
|
|
@@ -1113,44 +1161,74 @@ const LANGUAGE_SERVERS = {
|
|
|
1113
1161
|
typescript: {
|
|
1114
1162
|
language: "typescript",
|
|
1115
1163
|
command: "typescript-language-server",
|
|
1116
|
-
args: ["--stdio"]
|
|
1164
|
+
args: ["--stdio"],
|
|
1165
|
+
install_hint: "Install TypeScript LSP with: pnpm add -D typescript typescript-language-server"
|
|
1117
1166
|
},
|
|
1118
1167
|
python: {
|
|
1119
1168
|
language: "python",
|
|
1120
1169
|
command: "pylsp",
|
|
1121
|
-
args: []
|
|
1170
|
+
args: [],
|
|
1171
|
+
install_hint: "Install Python LSP with: pip install python-lsp-server"
|
|
1122
1172
|
},
|
|
1123
1173
|
rust: {
|
|
1124
1174
|
language: "rust",
|
|
1125
1175
|
command: "rust-analyzer",
|
|
1126
|
-
args: []
|
|
1176
|
+
args: [],
|
|
1177
|
+
install_hint: "Install Rust Analyzer and ensure the rust-analyzer binary is on PATH."
|
|
1127
1178
|
},
|
|
1128
1179
|
go: {
|
|
1129
1180
|
language: "go",
|
|
1130
1181
|
command: "gopls",
|
|
1131
|
-
args: ["serve"]
|
|
1182
|
+
args: ["serve"],
|
|
1183
|
+
install_hint: "Install Go LSP with: go install golang.org/x/tools/gopls@latest"
|
|
1132
1184
|
},
|
|
1133
1185
|
ruby: {
|
|
1134
1186
|
language: "ruby",
|
|
1135
1187
|
command: "solargraph",
|
|
1136
|
-
args: ["stdio"]
|
|
1188
|
+
args: ["stdio"],
|
|
1189
|
+
install_hint: "Install Ruby LSP with: gem install solargraph"
|
|
1137
1190
|
},
|
|
1138
1191
|
java: {
|
|
1139
1192
|
language: "java",
|
|
1140
1193
|
command: "jdtls",
|
|
1141
|
-
args: []
|
|
1194
|
+
args: [],
|
|
1195
|
+
install_hint: "Install Eclipse JDT Language Server and ensure the jdtls binary is on PATH."
|
|
1142
1196
|
},
|
|
1143
1197
|
lua: {
|
|
1144
1198
|
language: "lua",
|
|
1145
1199
|
command: "lua-language-server",
|
|
1146
|
-
args: []
|
|
1200
|
+
args: [],
|
|
1201
|
+
install_hint: "Install Lua LSP and ensure the lua-language-server binary is on PATH."
|
|
1147
1202
|
},
|
|
1148
1203
|
svelte: {
|
|
1149
1204
|
language: "svelte",
|
|
1150
1205
|
command: "svelteserver",
|
|
1151
|
-
args: ["--stdio"]
|
|
1206
|
+
args: ["--stdio"],
|
|
1207
|
+
install_hint: "Install Svelte LSP with: pnpm add -D svelte-language-server (or volta install svelte-language-server)"
|
|
1152
1208
|
}
|
|
1153
1209
|
};
|
|
1210
|
+
const WORKSPACE_MARKERS = [
|
|
1211
|
+
"svelte.config.js",
|
|
1212
|
+
"svelte.config.ts",
|
|
1213
|
+
"tsconfig.json",
|
|
1214
|
+
"jsconfig.json",
|
|
1215
|
+
"package.json",
|
|
1216
|
+
"pyproject.toml",
|
|
1217
|
+
"Cargo.toml",
|
|
1218
|
+
"go.mod",
|
|
1219
|
+
"Gemfile",
|
|
1220
|
+
"pom.xml",
|
|
1221
|
+
"build.gradle",
|
|
1222
|
+
"build.gradle.kts"
|
|
1223
|
+
];
|
|
1224
|
+
const REPOSITORY_MARKERS = [
|
|
1225
|
+
"pnpm-workspace.yaml",
|
|
1226
|
+
"package-lock.json",
|
|
1227
|
+
"yarn.lock",
|
|
1228
|
+
"bun.lockb",
|
|
1229
|
+
"bun.lock",
|
|
1230
|
+
".git"
|
|
1231
|
+
];
|
|
1154
1232
|
function detect_language(file_path) {
|
|
1155
1233
|
return EXTENSION_LANGUAGES[extname(file_path).toLowerCase()];
|
|
1156
1234
|
}
|
|
@@ -1177,6 +1255,17 @@ function get_server_config(language, cwd = process.cwd()) {
|
|
|
1177
1255
|
function language_id_for_file(file_path) {
|
|
1178
1256
|
return detect_language(file_path);
|
|
1179
1257
|
}
|
|
1258
|
+
function find_workspace_root(file_path, fallback = process.cwd()) {
|
|
1259
|
+
const start = resolve(dirname(file_path));
|
|
1260
|
+
const project_root = find_nearest_marker_directory(start, WORKSPACE_MARKERS);
|
|
1261
|
+
if (project_root) return project_root;
|
|
1262
|
+
const repo_root = find_nearest_marker_directory(start, REPOSITORY_MARKERS);
|
|
1263
|
+
if (repo_root) return repo_root;
|
|
1264
|
+
return resolve(fallback);
|
|
1265
|
+
}
|
|
1266
|
+
function find_nearest_marker_directory(start, markers) {
|
|
1267
|
+
for (const dir of ancestor_directories(start)) if (markers.some((marker) => existsSync(join(dir, marker)))) return dir;
|
|
1268
|
+
}
|
|
1180
1269
|
function ancestor_directories(start) {
|
|
1181
1270
|
const dirs = [];
|
|
1182
1271
|
let current = resolve(start);
|
|
@@ -1193,60 +1282,129 @@ function resolve_local_binary(directory, command) {
|
|
|
1193
1282
|
}
|
|
1194
1283
|
//#endregion
|
|
1195
1284
|
//#region src/extensions/lsp.ts
|
|
1285
|
+
var LspToolError = class extends Error {
|
|
1286
|
+
details;
|
|
1287
|
+
constructor(details) {
|
|
1288
|
+
super(details.message);
|
|
1289
|
+
this.name = "LspToolError";
|
|
1290
|
+
this.details = details;
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
var LspStartupCancelledError = class extends Error {
|
|
1294
|
+
constructor(language, workspace_root) {
|
|
1295
|
+
super(`Startup cancelled for ${language} LSP in ${workspace_root}`);
|
|
1296
|
+
this.name = "LspStartupCancelledError";
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
const SYMBOL_KIND_LABELS = {
|
|
1300
|
+
2: "module",
|
|
1301
|
+
3: "namespace",
|
|
1302
|
+
5: "class",
|
|
1303
|
+
6: "method",
|
|
1304
|
+
7: "property",
|
|
1305
|
+
8: "field",
|
|
1306
|
+
9: "constructor",
|
|
1307
|
+
11: "interface",
|
|
1308
|
+
12: "function",
|
|
1309
|
+
13: "variable",
|
|
1310
|
+
14: "constant",
|
|
1311
|
+
23: "struct",
|
|
1312
|
+
24: "event"
|
|
1313
|
+
};
|
|
1314
|
+
const SYMBOL_KIND_NAMES = Object.values(SYMBOL_KIND_LABELS);
|
|
1315
|
+
const SYMBOL_KIND_SCHEMA = Type.Union(SYMBOL_KIND_NAMES.map((name) => Type.Literal(name)));
|
|
1196
1316
|
function create_lsp_extension(options = {}) {
|
|
1197
1317
|
const create_client = options.create_client ?? ((client_options) => new LspClient(client_options));
|
|
1198
1318
|
const read_file = options.read_file ?? ((path) => readFile(path, "utf-8"));
|
|
1199
1319
|
return async function lsp(pi) {
|
|
1200
1320
|
const cwd = options.cwd?.() ?? process.cwd();
|
|
1201
|
-
const
|
|
1202
|
-
const
|
|
1203
|
-
const
|
|
1321
|
+
const clients_by_server = /* @__PURE__ */ new Map();
|
|
1322
|
+
const failed_servers = /* @__PURE__ */ new Map();
|
|
1323
|
+
const starting_servers = /* @__PURE__ */ new Map();
|
|
1204
1324
|
const resolve_abs = (file) => isAbsolute(file) ? file : resolve(cwd, file);
|
|
1325
|
+
const server_key = (language, workspace_root) => `${language}\u0000${workspace_root}`;
|
|
1326
|
+
const make_tool_result = (text, details = {}) => ({
|
|
1327
|
+
content: [{
|
|
1328
|
+
type: "text",
|
|
1329
|
+
text
|
|
1330
|
+
}],
|
|
1331
|
+
details
|
|
1332
|
+
});
|
|
1333
|
+
const make_tool_error = (details) => make_tool_result(format_tool_error(details), {
|
|
1334
|
+
ok: false,
|
|
1335
|
+
error: details
|
|
1336
|
+
});
|
|
1205
1337
|
const clear_language_state = async (language) => {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1338
|
+
const states = language ? Array.from(clients_by_server.entries()).filter(([, state]) => state.language === language) : Array.from(clients_by_server.entries());
|
|
1339
|
+
const starting = language ? Array.from(starting_servers.entries()).filter(([key]) => key.startsWith(`${language}\u0000`)) : Array.from(starting_servers.entries());
|
|
1340
|
+
for (const [key, startup] of starting) {
|
|
1341
|
+
startup.cancelled = true;
|
|
1342
|
+
starting_servers.delete(key);
|
|
1343
|
+
}
|
|
1344
|
+
await Promise.allSettled(states.map(([, state]) => state.client.stop()));
|
|
1345
|
+
for (const [key] of states) clients_by_server.delete(key);
|
|
1346
|
+
if (!language) {
|
|
1347
|
+
failed_servers.clear();
|
|
1213
1348
|
return;
|
|
1214
1349
|
}
|
|
1215
|
-
|
|
1216
|
-
clients_by_language.clear();
|
|
1217
|
-
failed_languages.clear();
|
|
1350
|
+
for (const [key, failure] of failed_servers.entries()) if (failure.language === language) failed_servers.delete(key);
|
|
1218
1351
|
};
|
|
1219
1352
|
const get_or_start_client = async (file_path) => {
|
|
1220
1353
|
const language = detect_language(file_path);
|
|
1221
1354
|
if (!language) return void 0;
|
|
1222
|
-
const
|
|
1355
|
+
const workspace_root = find_workspace_root(file_path, cwd);
|
|
1356
|
+
const key = server_key(language, workspace_root);
|
|
1357
|
+
const existing = clients_by_server.get(key);
|
|
1223
1358
|
if (existing) return existing;
|
|
1224
|
-
|
|
1225
|
-
|
|
1359
|
+
const failed = failed_servers.get(key);
|
|
1360
|
+
if (failed) throw new LspToolError(failed);
|
|
1361
|
+
const in_flight = starting_servers.get(key);
|
|
1362
|
+
if (in_flight) return in_flight.promise;
|
|
1363
|
+
const server_config = get_server_config(language, workspace_root);
|
|
1226
1364
|
if (!server_config) return void 0;
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1365
|
+
const root_uri = file_path_to_uri(workspace_root);
|
|
1366
|
+
const startup = {
|
|
1367
|
+
cancelled: false,
|
|
1368
|
+
promise: Promise.resolve(void 0)
|
|
1369
|
+
};
|
|
1370
|
+
const start_promise = (async () => {
|
|
1371
|
+
const client = create_client({
|
|
1372
|
+
command: server_config.command,
|
|
1373
|
+
args: server_config.args,
|
|
1374
|
+
root_uri,
|
|
1375
|
+
language_id_for_uri: (uri) => language_id_for_file(uri)
|
|
1376
|
+
});
|
|
1377
|
+
try {
|
|
1378
|
+
await client.start();
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
if (startup.cancelled) throw new LspStartupCancelledError(language, workspace_root);
|
|
1381
|
+
const failure = to_lsp_tool_error(file_path, language, workspace_root, server_config.command, server_config.install_hint, error);
|
|
1382
|
+
failed_servers.set(key, failure);
|
|
1383
|
+
throw new LspToolError(failure);
|
|
1384
|
+
}
|
|
1385
|
+
if (startup.cancelled) {
|
|
1386
|
+
await Promise.allSettled([client.stop()]);
|
|
1387
|
+
throw new LspStartupCancelledError(language, workspace_root);
|
|
1388
|
+
}
|
|
1389
|
+
const state = {
|
|
1390
|
+
client,
|
|
1391
|
+
language,
|
|
1392
|
+
workspace_root,
|
|
1393
|
+
root_uri,
|
|
1394
|
+
command: server_config.command,
|
|
1395
|
+
install_hint: server_config.install_hint
|
|
1396
|
+
};
|
|
1397
|
+
clients_by_server.set(key, state);
|
|
1398
|
+
failed_servers.delete(key);
|
|
1399
|
+
return state;
|
|
1400
|
+
})();
|
|
1401
|
+
startup.promise = start_promise;
|
|
1402
|
+
starting_servers.set(key, startup);
|
|
1233
1403
|
try {
|
|
1234
|
-
await
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const failure = `Failed to start ${server_config.command} for ${language}: ${message}. Install the language server and ensure it is available.`;
|
|
1238
|
-
failed_languages.set(language, failure);
|
|
1239
|
-
throw new Error(failure);
|
|
1404
|
+
return await start_promise;
|
|
1405
|
+
} finally {
|
|
1406
|
+
if (starting_servers.get(key) === startup) starting_servers.delete(key);
|
|
1240
1407
|
}
|
|
1241
|
-
const state = {
|
|
1242
|
-
client,
|
|
1243
|
-
language,
|
|
1244
|
-
root_uri,
|
|
1245
|
-
command: server_config.command
|
|
1246
|
-
};
|
|
1247
|
-
clients_by_language.set(language, state);
|
|
1248
|
-
failed_languages.delete(language);
|
|
1249
|
-
return state;
|
|
1250
1408
|
};
|
|
1251
1409
|
const open_file = async (state, abs_path) => {
|
|
1252
1410
|
const text = await read_file(abs_path);
|
|
@@ -1264,6 +1422,52 @@ function create_lsp_extension(options = {}) {
|
|
|
1264
1422
|
state
|
|
1265
1423
|
};
|
|
1266
1424
|
};
|
|
1425
|
+
const resolve_file_state = async (file) => {
|
|
1426
|
+
const abs = resolve_abs(file);
|
|
1427
|
+
try {
|
|
1428
|
+
const result = await get_file_state(abs);
|
|
1429
|
+
if (!result) return {
|
|
1430
|
+
ok: false,
|
|
1431
|
+
error: {
|
|
1432
|
+
kind: "unsupported_language",
|
|
1433
|
+
file: abs,
|
|
1434
|
+
message: `No language server configured for ${abs}`
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
return {
|
|
1438
|
+
ok: true,
|
|
1439
|
+
result
|
|
1440
|
+
};
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
if (error instanceof LspToolError) return {
|
|
1443
|
+
ok: false,
|
|
1444
|
+
error: error.details
|
|
1445
|
+
};
|
|
1446
|
+
return {
|
|
1447
|
+
ok: false,
|
|
1448
|
+
error: {
|
|
1449
|
+
kind: "tool_execution_failed",
|
|
1450
|
+
file: abs,
|
|
1451
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
const with_file_state = async (file, run) => {
|
|
1457
|
+
const resolved = await resolve_file_state(file);
|
|
1458
|
+
if (!resolved.ok) return make_tool_error(resolved.error);
|
|
1459
|
+
const { result } = resolved;
|
|
1460
|
+
try {
|
|
1461
|
+
return make_tool_result(await run(result), {
|
|
1462
|
+
ok: true,
|
|
1463
|
+
language: result.state.language,
|
|
1464
|
+
command: result.state.command,
|
|
1465
|
+
workspace_root: result.state.workspace_root
|
|
1466
|
+
});
|
|
1467
|
+
} catch (error) {
|
|
1468
|
+
return make_tool_error(to_lsp_tool_error(result.abs, result.state.language, result.state.workspace_root, result.state.command, result.state.install_hint, error));
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1267
1471
|
pi.registerTool(defineTool({
|
|
1268
1472
|
name: "lsp_diagnostics",
|
|
1269
1473
|
label: "LSP: diagnostics",
|
|
@@ -1272,25 +1476,86 @@ function create_lsp_extension(options = {}) {
|
|
|
1272
1476
|
file: Type.String({ description: "Path to the file to check (relative to cwd or absolute)." }),
|
|
1273
1477
|
wait_ms: Type.Optional(Type.Number({ description: "Max ms to wait for diagnostics after opening the file. Default 1500." }))
|
|
1274
1478
|
}),
|
|
1275
|
-
execute: async (_id, params) => {
|
|
1276
|
-
const result = await get_file_state(params.file);
|
|
1277
|
-
if (!result) return {
|
|
1278
|
-
content: [{
|
|
1279
|
-
type: "text",
|
|
1280
|
-
text: `No language server configured for ${resolve_abs(params.file)}`
|
|
1281
|
-
}],
|
|
1282
|
-
details: {}
|
|
1283
|
-
};
|
|
1479
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1284
1480
|
const diagnostics = await result.state.client.wait_for_diagnostics(result.uri, params.wait_ms ?? 1500);
|
|
1285
|
-
return
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1481
|
+
return format_diagnostics(result.abs, diagnostics);
|
|
1482
|
+
})
|
|
1483
|
+
}));
|
|
1484
|
+
pi.registerTool(defineTool({
|
|
1485
|
+
name: "lsp_diagnostics_many",
|
|
1486
|
+
label: "LSP: diagnostics many",
|
|
1487
|
+
description: "Get language server diagnostics for multiple files in one call. Useful for package-level sweeps and summarization.",
|
|
1488
|
+
parameters: Type.Object({
|
|
1489
|
+
files: Type.Array(Type.String(), {
|
|
1490
|
+
minItems: 1,
|
|
1491
|
+
maxItems: 100,
|
|
1492
|
+
description: "Files to check (relative to cwd or absolute)."
|
|
1493
|
+
}),
|
|
1494
|
+
wait_ms: Type.Optional(Type.Number({ description: "Max ms to wait for diagnostics after opening each file. Default 1500." }))
|
|
1495
|
+
}),
|
|
1496
|
+
execute: async (_id, params) => {
|
|
1497
|
+
const results = await Promise.all(params.files.map((file) => resolve_file_state(file)));
|
|
1498
|
+
const wait_ms = params.wait_ms ?? 1500;
|
|
1499
|
+
const lines_with_stats = await Promise.all(results.map(async (resolved) => {
|
|
1500
|
+
if (!resolved.ok) return {
|
|
1501
|
+
line: format_tool_error(resolved.error),
|
|
1502
|
+
diagnostics: 0,
|
|
1503
|
+
error: true
|
|
1504
|
+
};
|
|
1505
|
+
const diagnostics = await resolved.result.state.client.wait_for_diagnostics(resolved.result.uri, wait_ms);
|
|
1506
|
+
return {
|
|
1507
|
+
line: format_diagnostics(resolved.result.abs, diagnostics),
|
|
1508
|
+
diagnostics: diagnostics.length,
|
|
1509
|
+
error: false
|
|
1510
|
+
};
|
|
1511
|
+
}));
|
|
1512
|
+
let diagnostic_count = 0;
|
|
1513
|
+
let clean_count = 0;
|
|
1514
|
+
let error_count = 0;
|
|
1515
|
+
const lines = [];
|
|
1516
|
+
for (const entry of lines_with_stats) {
|
|
1517
|
+
lines.push(entry.line);
|
|
1518
|
+
if (entry.error) error_count += 1;
|
|
1519
|
+
else {
|
|
1520
|
+
diagnostic_count += entry.diagnostics;
|
|
1521
|
+
if (entry.diagnostics === 0) clean_count += 1;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return make_tool_result([`Checked ${params.files.length} file(s): ${diagnostic_count} diagnostic(s), ${clean_count} clean, ${error_count} error(s)`, ...lines].join("\n\n"), {
|
|
1525
|
+
ok: error_count === 0,
|
|
1526
|
+
checked: params.files.length,
|
|
1527
|
+
diagnostic_count,
|
|
1528
|
+
clean_count,
|
|
1529
|
+
error_count
|
|
1530
|
+
});
|
|
1292
1531
|
}
|
|
1293
1532
|
}));
|
|
1533
|
+
pi.registerTool(defineTool({
|
|
1534
|
+
name: "lsp_find_symbol",
|
|
1535
|
+
label: "LSP: find symbol",
|
|
1536
|
+
description: "Find symbols in a file by name or detail text using document symbols. Supports exact matching, kind filters, and top-level-only mode.",
|
|
1537
|
+
parameters: Type.Object({
|
|
1538
|
+
file: Type.String(),
|
|
1539
|
+
query: Type.String({ description: "Substring to match against symbol names/details." }),
|
|
1540
|
+
max_results: Type.Optional(Type.Number({ description: "Max number of matches to return. Default 20." })),
|
|
1541
|
+
top_level_only: Type.Optional(Type.Boolean({ description: "Only match top-level symbols. Default false." })),
|
|
1542
|
+
exact_match: Type.Optional(Type.Boolean({ description: "Match whole symbol names/details exactly instead of substring matching. Default false." })),
|
|
1543
|
+
kinds: Type.Optional(Type.Array(SYMBOL_KIND_SCHEMA, {
|
|
1544
|
+
minItems: 1,
|
|
1545
|
+
maxItems: SYMBOL_KIND_NAMES.length,
|
|
1546
|
+
description: "Restrict matches to these symbol kinds."
|
|
1547
|
+
}))
|
|
1548
|
+
}),
|
|
1549
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1550
|
+
const matches = find_symbol_matches(await result.state.client.document_symbols(result.uri), params.query, {
|
|
1551
|
+
max_results: params.max_results ?? 20,
|
|
1552
|
+
top_level_only: params.top_level_only ?? false,
|
|
1553
|
+
exact_match: params.exact_match ?? false,
|
|
1554
|
+
kinds: new Set(params.kinds ?? [])
|
|
1555
|
+
});
|
|
1556
|
+
return format_symbol_matches(result.abs, params.query, matches);
|
|
1557
|
+
})
|
|
1558
|
+
}));
|
|
1294
1559
|
pi.registerTool(defineTool({
|
|
1295
1560
|
name: "lsp_hover",
|
|
1296
1561
|
label: "LSP: hover",
|
|
@@ -1300,26 +1565,12 @@ function create_lsp_extension(options = {}) {
|
|
|
1300
1565
|
line: Type.Number({ description: "Zero-based line number." }),
|
|
1301
1566
|
character: Type.Number({ description: "Zero-based character offset." })
|
|
1302
1567
|
}),
|
|
1303
|
-
execute: async (_id, params) => {
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
}],
|
|
1310
|
-
details: {}
|
|
1311
|
-
};
|
|
1312
|
-
return {
|
|
1313
|
-
content: [{
|
|
1314
|
-
type: "text",
|
|
1315
|
-
text: format_hover(await result.state.client.hover(result.uri, {
|
|
1316
|
-
line: params.line,
|
|
1317
|
-
character: params.character
|
|
1318
|
-
}))
|
|
1319
|
-
}],
|
|
1320
|
-
details: {}
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1568
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1569
|
+
return format_hover(await result.state.client.hover(result.uri, {
|
|
1570
|
+
line: params.line,
|
|
1571
|
+
character: params.character
|
|
1572
|
+
}));
|
|
1573
|
+
})
|
|
1323
1574
|
}));
|
|
1324
1575
|
pi.registerTool(defineTool({
|
|
1325
1576
|
name: "lsp_definition",
|
|
@@ -1330,26 +1581,12 @@ function create_lsp_extension(options = {}) {
|
|
|
1330
1581
|
line: Type.Number(),
|
|
1331
1582
|
character: Type.Number()
|
|
1332
1583
|
}),
|
|
1333
|
-
execute: async (_id, params) => {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
}],
|
|
1340
|
-
details: {}
|
|
1341
|
-
};
|
|
1342
|
-
return {
|
|
1343
|
-
content: [{
|
|
1344
|
-
type: "text",
|
|
1345
|
-
text: format_locations(await result.state.client.definition(result.uri, {
|
|
1346
|
-
line: params.line,
|
|
1347
|
-
character: params.character
|
|
1348
|
-
}), "No definition found.")
|
|
1349
|
-
}],
|
|
1350
|
-
details: {}
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1584
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1585
|
+
return format_locations(await result.state.client.definition(result.uri, {
|
|
1586
|
+
line: params.line,
|
|
1587
|
+
character: params.character
|
|
1588
|
+
}), "No definition found.");
|
|
1589
|
+
})
|
|
1353
1590
|
}));
|
|
1354
1591
|
pi.registerTool(defineTool({
|
|
1355
1592
|
name: "lsp_references",
|
|
@@ -1361,50 +1598,22 @@ function create_lsp_extension(options = {}) {
|
|
|
1361
1598
|
character: Type.Number(),
|
|
1362
1599
|
include_declaration: Type.Optional(Type.Boolean())
|
|
1363
1600
|
}),
|
|
1364
|
-
execute: async (_id, params) => {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
}],
|
|
1371
|
-
details: {}
|
|
1372
|
-
};
|
|
1373
|
-
return {
|
|
1374
|
-
content: [{
|
|
1375
|
-
type: "text",
|
|
1376
|
-
text: format_locations(await result.state.client.references(result.uri, {
|
|
1377
|
-
line: params.line,
|
|
1378
|
-
character: params.character
|
|
1379
|
-
}, params.include_declaration ?? true), "No references found.")
|
|
1380
|
-
}],
|
|
1381
|
-
details: {}
|
|
1382
|
-
};
|
|
1383
|
-
}
|
|
1601
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1602
|
+
return format_locations(await result.state.client.references(result.uri, {
|
|
1603
|
+
line: params.line,
|
|
1604
|
+
character: params.character
|
|
1605
|
+
}, params.include_declaration ?? true), "No references found.");
|
|
1606
|
+
})
|
|
1384
1607
|
}));
|
|
1385
1608
|
pi.registerTool(defineTool({
|
|
1386
1609
|
name: "lsp_document_symbols",
|
|
1387
1610
|
label: "LSP: document symbols",
|
|
1388
1611
|
description: "List symbols in a file (functions, classes, variables) using the language server.",
|
|
1389
1612
|
parameters: Type.Object({ file: Type.String() }),
|
|
1390
|
-
execute: async (_id, params) => {
|
|
1391
|
-
const result = await get_file_state(params.file);
|
|
1392
|
-
if (!result) return {
|
|
1393
|
-
content: [{
|
|
1394
|
-
type: "text",
|
|
1395
|
-
text: `No language server configured for ${resolve_abs(params.file)}`
|
|
1396
|
-
}],
|
|
1397
|
-
details: {}
|
|
1398
|
-
};
|
|
1613
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1399
1614
|
const symbols = await result.state.client.document_symbols(result.uri);
|
|
1400
|
-
return
|
|
1401
|
-
|
|
1402
|
-
type: "text",
|
|
1403
|
-
text: format_document_symbols(result.abs, symbols)
|
|
1404
|
-
}],
|
|
1405
|
-
details: {}
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1615
|
+
return format_document_symbols(result.abs, symbols);
|
|
1616
|
+
})
|
|
1408
1617
|
}));
|
|
1409
1618
|
pi.registerCommand("lsp", {
|
|
1410
1619
|
description: "Show or manage language server state",
|
|
@@ -1433,7 +1642,7 @@ function create_lsp_extension(options = {}) {
|
|
|
1433
1642
|
return null;
|
|
1434
1643
|
},
|
|
1435
1644
|
handler: async (args, ctx) => {
|
|
1436
|
-
await handle_lsp_command(args, ctx, cwd,
|
|
1645
|
+
await handle_lsp_command(args, ctx, cwd, clients_by_server, failed_servers, clear_language_state);
|
|
1437
1646
|
}
|
|
1438
1647
|
});
|
|
1439
1648
|
pi.on("session_shutdown", async () => {
|
|
@@ -1442,12 +1651,12 @@ function create_lsp_extension(options = {}) {
|
|
|
1442
1651
|
};
|
|
1443
1652
|
}
|
|
1444
1653
|
var lsp_default = create_lsp_extension();
|
|
1445
|
-
async function handle_lsp_command(args, ctx, cwd,
|
|
1654
|
+
async function handle_lsp_command(args, ctx, cwd, clients_by_server, failed_servers, clear_language_state) {
|
|
1446
1655
|
const [subcommand = "status", target] = args.trim() ? args.trim().split(/\s+/, 2) : [];
|
|
1447
1656
|
switch (subcommand) {
|
|
1448
1657
|
case "status":
|
|
1449
1658
|
case "list":
|
|
1450
|
-
ctx.ui.notify(format_status_lines(cwd,
|
|
1659
|
+
ctx.ui.notify(format_status_lines(cwd, clients_by_server, failed_servers).join("\n"));
|
|
1451
1660
|
return;
|
|
1452
1661
|
case "restart":
|
|
1453
1662
|
if (!target || target === "all") {
|
|
@@ -1465,24 +1674,62 @@ async function handle_lsp_command(args, ctx, cwd, clients_by_language, failed_la
|
|
|
1465
1674
|
default: ctx.ui.notify(`Unknown subcommand: ${subcommand}. Use: status, list, restart`, "warning");
|
|
1466
1675
|
}
|
|
1467
1676
|
}
|
|
1468
|
-
function format_status_lines(cwd,
|
|
1677
|
+
function format_status_lines(cwd, clients_by_server, failed_servers) {
|
|
1469
1678
|
const lines = [];
|
|
1679
|
+
const active_languages = /* @__PURE__ */ new Set();
|
|
1680
|
+
const running_states = Array.from(clients_by_server.values()).sort((a, b) => a.language.localeCompare(b.language) || a.workspace_root.localeCompare(b.workspace_root));
|
|
1681
|
+
for (const running of running_states) {
|
|
1682
|
+
active_languages.add(running.language);
|
|
1683
|
+
lines.push(`${running.language}: running (ready=${running.client.is_ready()}) — ${running.command} [workspace ${running.workspace_root}]`);
|
|
1684
|
+
}
|
|
1685
|
+
const failures = Array.from(failed_servers.values()).sort((a, b) => (a.language ?? "").localeCompare(b.language ?? "") || (a.workspace_root ?? "").localeCompare(b.workspace_root ?? ""));
|
|
1686
|
+
for (const failure of failures) {
|
|
1687
|
+
if (failure.language) active_languages.add(failure.language);
|
|
1688
|
+
const workspace = failure.workspace_root ? ` [workspace ${failure.workspace_root}]` : "";
|
|
1689
|
+
const language = failure.language ?? "unknown";
|
|
1690
|
+
lines.push(`${language}: failed — ${failure.message}${workspace}`);
|
|
1691
|
+
}
|
|
1470
1692
|
for (const language of list_supported_languages()) {
|
|
1471
|
-
|
|
1472
|
-
if (running) {
|
|
1473
|
-
lines.push(`${language}: running (ready=${running.client.is_ready()}) — ${running.command}`);
|
|
1474
|
-
continue;
|
|
1475
|
-
}
|
|
1476
|
-
const failed = failed_languages.get(language);
|
|
1477
|
-
if (failed) {
|
|
1478
|
-
lines.push(`${language}: failed — ${failed}`);
|
|
1479
|
-
continue;
|
|
1480
|
-
}
|
|
1693
|
+
if (active_languages.has(language)) continue;
|
|
1481
1694
|
const config = get_server_config(language, cwd);
|
|
1482
1695
|
if (config) lines.push(`${language}: idle — ${config.command}`);
|
|
1483
1696
|
}
|
|
1484
1697
|
return lines.length > 0 ? lines : ["No language servers configured for this project."];
|
|
1485
1698
|
}
|
|
1699
|
+
function to_lsp_tool_error(file, language, workspace_root, command, install_hint, error) {
|
|
1700
|
+
if (error instanceof LspToolError) return error.details;
|
|
1701
|
+
if (error instanceof LspClientStartError) {
|
|
1702
|
+
const missing_binary = error.code === "ENOENT";
|
|
1703
|
+
return {
|
|
1704
|
+
kind: "server_start_failed",
|
|
1705
|
+
file,
|
|
1706
|
+
language,
|
|
1707
|
+
workspace_root,
|
|
1708
|
+
command,
|
|
1709
|
+
install_hint,
|
|
1710
|
+
code: error.code,
|
|
1711
|
+
message: missing_binary ? `command "${command}" not found` : error.message
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
return {
|
|
1715
|
+
kind: "tool_execution_failed",
|
|
1716
|
+
file,
|
|
1717
|
+
language,
|
|
1718
|
+
workspace_root,
|
|
1719
|
+
command,
|
|
1720
|
+
install_hint,
|
|
1721
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1722
|
+
code: typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function format_tool_error(details) {
|
|
1726
|
+
if (details.kind === "unsupported_language") return details.message;
|
|
1727
|
+
const lines = [details.language ? `${details.language} LSP unavailable for ${details.file}` : `LSP request failed for ${details.file}`, `Reason: ${details.message}`];
|
|
1728
|
+
if (details.command) lines.push(`Command: ${details.command}`);
|
|
1729
|
+
if (details.workspace_root) lines.push(`Workspace: ${details.workspace_root}`);
|
|
1730
|
+
if (details.install_hint) lines.push(`Hint: ${details.install_hint}`);
|
|
1731
|
+
return lines.join("\n");
|
|
1732
|
+
}
|
|
1486
1733
|
function severity_label(severity) {
|
|
1487
1734
|
switch (severity) {
|
|
1488
1735
|
case 1: return "error";
|
|
@@ -1522,6 +1769,47 @@ function format_document_symbols(file, symbols) {
|
|
|
1522
1769
|
append_symbol_lines(lines, symbols, 1);
|
|
1523
1770
|
return lines.join("\n");
|
|
1524
1771
|
}
|
|
1772
|
+
function find_symbol_matches(symbols, query, options) {
|
|
1773
|
+
const normalized = query.trim().toLowerCase();
|
|
1774
|
+
if (!normalized) return [];
|
|
1775
|
+
const matches = [];
|
|
1776
|
+
const matches_query = (symbol) => {
|
|
1777
|
+
const values = [symbol.name, symbol.detail ?? ""].map((value) => value.trim().toLowerCase());
|
|
1778
|
+
return options.exact_match ? values.some((value) => value === normalized) : values.some((value) => value.includes(normalized));
|
|
1779
|
+
};
|
|
1780
|
+
const matches_kind = (symbol) => {
|
|
1781
|
+
if (options.kinds.size === 0) return true;
|
|
1782
|
+
return options.kinds.has(symbol_kind_label(symbol.kind));
|
|
1783
|
+
};
|
|
1784
|
+
const visit = (entries, depth) => {
|
|
1785
|
+
for (const symbol of entries) {
|
|
1786
|
+
if (matches_kind(symbol) && matches_query(symbol)) {
|
|
1787
|
+
matches.push({
|
|
1788
|
+
symbol,
|
|
1789
|
+
depth
|
|
1790
|
+
});
|
|
1791
|
+
if (matches.length >= options.max_results) return;
|
|
1792
|
+
}
|
|
1793
|
+
if (!options.top_level_only && symbol.children?.length) {
|
|
1794
|
+
visit(symbol.children, depth + 1);
|
|
1795
|
+
if (matches.length >= options.max_results) return;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
visit(symbols, 1);
|
|
1800
|
+
return matches;
|
|
1801
|
+
}
|
|
1802
|
+
function format_symbol_matches(file, query, matches) {
|
|
1803
|
+
if (matches.length === 0) return `${file}: no symbols matching "${query}"`;
|
|
1804
|
+
const lines = [`${file}: ${matches.length} symbol match(es) for "${query}"`];
|
|
1805
|
+
for (const { symbol, depth } of matches) {
|
|
1806
|
+
const indent = " ".repeat(depth);
|
|
1807
|
+
const detail = symbol.detail ? ` — ${symbol.detail}` : "";
|
|
1808
|
+
const range = `${symbol.range.start.line + 1}:${symbol.range.start.character + 1}`;
|
|
1809
|
+
lines.push(`${indent}${symbol_kind_label(symbol.kind)} ${symbol.name}${detail} @ ${range}`);
|
|
1810
|
+
}
|
|
1811
|
+
return lines.join("\n");
|
|
1812
|
+
}
|
|
1525
1813
|
function append_symbol_lines(lines, symbols, depth) {
|
|
1526
1814
|
for (const symbol of symbols) {
|
|
1527
1815
|
const indent = " ".repeat(depth);
|
|
@@ -1532,21 +1820,7 @@ function append_symbol_lines(lines, symbols, depth) {
|
|
|
1532
1820
|
}
|
|
1533
1821
|
}
|
|
1534
1822
|
function symbol_kind_label(kind) {
|
|
1535
|
-
return
|
|
1536
|
-
2: "module",
|
|
1537
|
-
3: "namespace",
|
|
1538
|
-
5: "class",
|
|
1539
|
-
6: "method",
|
|
1540
|
-
7: "property",
|
|
1541
|
-
8: "field",
|
|
1542
|
-
9: "constructor",
|
|
1543
|
-
11: "interface",
|
|
1544
|
-
12: "function",
|
|
1545
|
-
13: "variable",
|
|
1546
|
-
14: "constant",
|
|
1547
|
-
23: "struct",
|
|
1548
|
-
24: "event"
|
|
1549
|
-
}[kind] ?? "symbol";
|
|
1823
|
+
return SYMBOL_KIND_LABELS[kind] ?? "symbol";
|
|
1550
1824
|
}
|
|
1551
1825
|
function file_url_to_path_or_value(uri) {
|
|
1552
1826
|
try {
|
|
@@ -1817,56 +2091,93 @@ function load_mcp_config(cwd) {
|
|
|
1817
2091
|
}
|
|
1818
2092
|
//#endregion
|
|
1819
2093
|
//#region src/extensions/mcp.ts
|
|
2094
|
+
function remove_server_tools_from_active(pi, tool_names) {
|
|
2095
|
+
const tool_set = new Set(tool_names);
|
|
2096
|
+
pi.setActiveTools(pi.getActiveTools().filter((tool) => !tool_set.has(tool)));
|
|
2097
|
+
}
|
|
2098
|
+
function format_server_status(state) {
|
|
2099
|
+
switch (state.status) {
|
|
2100
|
+
case "connected": return state.enabled ? "enabled" : "disabled";
|
|
2101
|
+
case "connecting": return state.enabled ? "connecting" : "connecting, disabled";
|
|
2102
|
+
case "failed": return state.enabled ? "failed" : "failed, disabled";
|
|
2103
|
+
default: return state.enabled ? "not connected yet" : "disabled";
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1820
2106
|
async function mcp(pi) {
|
|
1821
|
-
const
|
|
1822
|
-
const servers =
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
if (result.status === "rejected") {
|
|
1835
|
-
console.error(`MCP server failed: ${result.reason}`);
|
|
1836
|
-
continue;
|
|
2107
|
+
const configs = load_mcp_config(process.cwd());
|
|
2108
|
+
const servers = new Map(configs.map((config) => [config.name, {
|
|
2109
|
+
config,
|
|
2110
|
+
tool_names: [],
|
|
2111
|
+
enabled: true,
|
|
2112
|
+
status: "disconnected"
|
|
2113
|
+
}]));
|
|
2114
|
+
const registered_tool_names = /* @__PURE__ */ new Set();
|
|
2115
|
+
const connect_server = async (state) => {
|
|
2116
|
+
if (state.status === "connected") return;
|
|
2117
|
+
if (state.connect_promise) {
|
|
2118
|
+
await state.connect_promise;
|
|
2119
|
+
return;
|
|
1837
2120
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
const
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
2121
|
+
state.connect_promise = (async () => {
|
|
2122
|
+
state.status = "connecting";
|
|
2123
|
+
state.error = void 0;
|
|
2124
|
+
const client = new McpClient(state.config);
|
|
2125
|
+
try {
|
|
2126
|
+
await client.connect();
|
|
2127
|
+
state.client = client;
|
|
2128
|
+
const mcp_tools = await client.listTools();
|
|
2129
|
+
const tool_names = [];
|
|
2130
|
+
for (const mcp_tool of mcp_tools) {
|
|
2131
|
+
const tool_name = `mcp__${state.config.name}__${mcp_tool.name}`;
|
|
2132
|
+
tool_names.push(tool_name);
|
|
2133
|
+
if (registered_tool_names.has(tool_name)) continue;
|
|
2134
|
+
registered_tool_names.add(tool_name);
|
|
2135
|
+
pi.registerTool(defineTool({
|
|
2136
|
+
name: tool_name,
|
|
2137
|
+
label: `${state.config.name}: ${mcp_tool.name}`,
|
|
2138
|
+
description: mcp_tool.description || mcp_tool.name,
|
|
2139
|
+
parameters: mcp_tool.inputSchema || {
|
|
2140
|
+
type: "object",
|
|
2141
|
+
properties: {}
|
|
2142
|
+
},
|
|
2143
|
+
execute: async (_id, params) => {
|
|
2144
|
+
const result = await state.client.callTool(mcp_tool.name, params);
|
|
2145
|
+
return {
|
|
2146
|
+
content: [{
|
|
2147
|
+
type: "text",
|
|
2148
|
+
text: result?.content?.map((c) => c.text || "").join("\n") || JSON.stringify(result)
|
|
2149
|
+
}],
|
|
2150
|
+
details: {}
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
}));
|
|
1860
2154
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2155
|
+
state.tool_names = tool_names;
|
|
2156
|
+
state.status = "connected";
|
|
2157
|
+
if (!state.enabled) remove_server_tools_from_active(pi, state.tool_names);
|
|
2158
|
+
} catch (error) {
|
|
2159
|
+
state.status = "failed";
|
|
2160
|
+
state.error = error instanceof Error ? error.message : String(error);
|
|
2161
|
+
state.client = void 0;
|
|
2162
|
+
await client.disconnect().catch(() => {});
|
|
2163
|
+
console.error(`MCP server failed (${state.config.name}): ${state.error}`);
|
|
2164
|
+
throw error;
|
|
2165
|
+
} finally {
|
|
2166
|
+
state.connect_promise = void 0;
|
|
2167
|
+
}
|
|
2168
|
+
})();
|
|
2169
|
+
await state.connect_promise;
|
|
2170
|
+
};
|
|
2171
|
+
const connect_all_servers = async (options = {}) => {
|
|
2172
|
+
await Promise.allSettled(Array.from(servers.values()).filter((state) => state.enabled).filter((state) => options.include_failed || state.status !== "failed").map((state) => connect_server(state)));
|
|
2173
|
+
};
|
|
2174
|
+
pi.on("session_start", async () => {
|
|
2175
|
+
connect_all_servers();
|
|
2176
|
+
});
|
|
2177
|
+
pi.on("before_agent_start", async (event) => {
|
|
2178
|
+
await connect_all_servers();
|
|
2179
|
+
return event;
|
|
2180
|
+
});
|
|
1870
2181
|
pi.registerCommand("mcp", {
|
|
1871
2182
|
description: "Manage MCP servers (list, enable, disable)",
|
|
1872
2183
|
getArgumentCompletions: (prefix) => {
|
|
@@ -1898,10 +2209,7 @@ async function mcp(pi) {
|
|
|
1898
2209
|
return;
|
|
1899
2210
|
}
|
|
1900
2211
|
const lines = [];
|
|
1901
|
-
for (const [sname, state] of servers.entries()) {
|
|
1902
|
-
const status = state.enabled ? "enabled" : "disabled";
|
|
1903
|
-
lines.push(`${sname} (${status}) — ${state.tool_names.length} tools`);
|
|
1904
|
-
}
|
|
2212
|
+
for (const [sname, state] of servers.entries()) lines.push(`${sname} (${format_server_status(state)}) — ${state.tool_names.length} tools${state.error ? ` — ${state.error}` : ""}`);
|
|
1905
2213
|
ctx.ui.notify(lines.join("\n"));
|
|
1906
2214
|
break;
|
|
1907
2215
|
}
|
|
@@ -1911,14 +2219,23 @@ async function mcp(pi) {
|
|
|
1911
2219
|
ctx.ui.notify(`Unknown server: ${name}`, "warning");
|
|
1912
2220
|
return;
|
|
1913
2221
|
}
|
|
1914
|
-
if (server.enabled) {
|
|
2222
|
+
if (server.enabled && server.status !== "failed") {
|
|
1915
2223
|
ctx.ui.notify(`${name} already enabled`);
|
|
1916
2224
|
return;
|
|
1917
2225
|
}
|
|
1918
2226
|
server.enabled = true;
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2227
|
+
if (server.status === "connected") {
|
|
2228
|
+
const active = pi.getActiveTools();
|
|
2229
|
+
pi.setActiveTools([...active, ...server.tool_names]);
|
|
2230
|
+
ctx.ui.notify(`Enabled ${name}`);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (server.status === "failed") {
|
|
2234
|
+
server.status = "disconnected";
|
|
2235
|
+
server.error = void 0;
|
|
2236
|
+
}
|
|
2237
|
+
connect_server(server);
|
|
2238
|
+
ctx.ui.notify(`Enabling ${name} and connecting in background`);
|
|
1922
2239
|
break;
|
|
1923
2240
|
}
|
|
1924
2241
|
case "disable": {
|
|
@@ -1932,8 +2249,7 @@ async function mcp(pi) {
|
|
|
1932
2249
|
return;
|
|
1933
2250
|
}
|
|
1934
2251
|
server.enabled = false;
|
|
1935
|
-
|
|
1936
|
-
pi.setActiveTools(pi.getActiveTools().filter((t) => !tool_set.has(t)));
|
|
2252
|
+
remove_server_tools_from_active(pi, server.tool_names);
|
|
1937
2253
|
ctx.ui.notify(`Disabled ${name}`);
|
|
1938
2254
|
break;
|
|
1939
2255
|
}
|
|
@@ -1942,7 +2258,12 @@ async function mcp(pi) {
|
|
|
1942
2258
|
}
|
|
1943
2259
|
});
|
|
1944
2260
|
pi.on("session_shutdown", async () => {
|
|
1945
|
-
|
|
2261
|
+
await Promise.allSettled(Array.from(servers.values()).map(async (server) => {
|
|
2262
|
+
await server.connect_promise?.catch(() => {});
|
|
2263
|
+
await server.client?.disconnect();
|
|
2264
|
+
server.client = void 0;
|
|
2265
|
+
if (server.status !== "failed") server.status = "disconnected";
|
|
2266
|
+
}));
|
|
1946
2267
|
});
|
|
1947
2268
|
}
|
|
1948
2269
|
//#endregion
|
|
@@ -2763,22 +3084,20 @@ async function prompt_presets(pi) {
|
|
|
2763
3084
|
//#endregion
|
|
2764
3085
|
//#region src/extensions/recall.ts
|
|
2765
3086
|
const DEFAULT_DB_PATH = join(process.env.HOME, ".pi", "pirecall.db");
|
|
2766
|
-
|
|
2767
|
-
if (existsSync(DEFAULT_DB_PATH))
|
|
2768
|
-
|
|
3087
|
+
function sync_recall_db_in_background() {
|
|
3088
|
+
if (!existsSync(DEFAULT_DB_PATH)) return;
|
|
3089
|
+
try {
|
|
3090
|
+
spawn("npx", [
|
|
2769
3091
|
"pirecall",
|
|
2770
3092
|
"sync",
|
|
2771
3093
|
"--json"
|
|
2772
|
-
], {
|
|
2773
|
-
encoding: "utf-8",
|
|
2774
|
-
timeout: 3e4,
|
|
2775
|
-
stdio: [
|
|
2776
|
-
"pipe",
|
|
2777
|
-
"pipe",
|
|
2778
|
-
"pipe"
|
|
2779
|
-
]
|
|
2780
|
-
});
|
|
3094
|
+
], { stdio: "ignore" }).unref();
|
|
2781
3095
|
} catch {}
|
|
3096
|
+
}
|
|
3097
|
+
async function recall(pi) {
|
|
3098
|
+
pi.on("session_start", async () => {
|
|
3099
|
+
sync_recall_db_in_background();
|
|
3100
|
+
});
|
|
2782
3101
|
pi.on("before_agent_start", async (event) => {
|
|
2783
3102
|
return { systemPrompt: event.systemPrompt + `
|
|
2784
3103
|
|
|
@@ -3650,4 +3969,4 @@ async function create_my_pi(options = {}) {
|
|
|
3650
3969
|
//#endregion
|
|
3651
3970
|
export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
|
|
3652
3971
|
|
|
3653
|
-
//# sourceMappingURL=api-
|
|
3972
|
+
//# sourceMappingURL=api-7LVj6kUV.js.map
|