my-pi 0.0.8 → 0.0.9

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.
@@ -1,11 +1,13 @@
1
1
  import { InteractiveMode as InteractiveMode$1, SessionManager, SettingsManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, defineTool, getAgentDir, parseFrontmatter, runPrintMode as runPrintMode$1 } from "@mariozechner/pi-coding-agent";
2
2
  import { cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { basename, dirname, join, relative, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
3
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { Type } from "@sinclair/typebox";
6
6
  import { execFileSync, 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
+ import { readFile } from "node:fs/promises";
10
+ import { EventEmitter } from "node:events";
9
11
  import { createHash } from "node:crypto";
10
12
  //#region src/extensions/chain.ts
11
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -399,6 +401,13 @@ const BUILTIN_EXTENSIONS = [
399
401
  "preset",
400
402
  "presets"
401
403
  ]
404
+ },
405
+ {
406
+ key: "lsp",
407
+ label: "LSP",
408
+ description: "Language Server Protocol tools (diagnostics, hover, definition, references)",
409
+ cli_flag: "--no-lsp",
410
+ aliases: ["lsp", "language-server"]
402
411
  }
403
412
  ];
404
413
  function get_builtin_extensions_config_path() {
@@ -814,6 +823,739 @@ ${task || "Continue from where the previous session left off."}
814
823
  });
815
824
  }
816
825
  //#endregion
826
+ //#region src/lsp/client.ts
827
+ var LspClient = class extends EventEmitter {
828
+ #proc = null;
829
+ #options;
830
+ #next_id = 1;
831
+ #pending = /* @__PURE__ */ new Map();
832
+ #buffer = Buffer.alloc(0);
833
+ #initialized = false;
834
+ #open_docs = /* @__PURE__ */ new Map();
835
+ #diagnostics_by_uri = /* @__PURE__ */ new Map();
836
+ constructor(options) {
837
+ super();
838
+ this.#options = options;
839
+ }
840
+ async start() {
841
+ this.#proc = spawn(this.#options.command, this.#options.args, {
842
+ stdio: [
843
+ "pipe",
844
+ "pipe",
845
+ "pipe"
846
+ ],
847
+ env: process.env
848
+ });
849
+ this.#proc.on("error", (err) => {
850
+ this.emit("error", err);
851
+ });
852
+ this.#proc.on("close", () => {
853
+ for (const pending of this.#pending.values()) {
854
+ clearTimeout(pending.timer);
855
+ pending.reject(/* @__PURE__ */ new Error("LSP server closed"));
856
+ }
857
+ this.#pending.clear();
858
+ this.#initialized = false;
859
+ this.#proc = null;
860
+ });
861
+ this.#proc.stderr?.on("data", () => {});
862
+ this.#proc.stdout.on("data", (chunk) => {
863
+ this.#buffer = Buffer.concat([this.#buffer, chunk]);
864
+ this.#drain_buffer();
865
+ });
866
+ await this.#request("initialize", {
867
+ processId: process.pid,
868
+ rootUri: this.#options.root_uri,
869
+ capabilities: {
870
+ textDocument: {
871
+ publishDiagnostics: { relatedInformation: true },
872
+ hover: { contentFormat: ["markdown", "plaintext"] },
873
+ definition: { linkSupport: false },
874
+ references: {},
875
+ documentSymbol: { hierarchicalDocumentSymbolSupport: true }
876
+ },
877
+ workspace: {
878
+ workspaceFolders: true,
879
+ symbol: {}
880
+ }
881
+ },
882
+ workspaceFolders: [{
883
+ uri: this.#options.root_uri,
884
+ name: "workspace"
885
+ }]
886
+ });
887
+ this.#notify("initialized", {});
888
+ this.#initialized = true;
889
+ }
890
+ is_ready() {
891
+ return this.#initialized;
892
+ }
893
+ async ensure_document_open(uri, text) {
894
+ const existing = this.#open_docs.get(uri);
895
+ if (existing) {
896
+ if (existing.text === text) return;
897
+ const next_version = existing.version + 1;
898
+ this.#open_docs.set(uri, {
899
+ version: next_version,
900
+ text
901
+ });
902
+ this.#notify("textDocument/didChange", {
903
+ textDocument: {
904
+ uri,
905
+ version: next_version
906
+ },
907
+ contentChanges: [{ text }]
908
+ });
909
+ return;
910
+ }
911
+ const language_id = this.#options.language_id_for_uri(uri) ?? "plaintext";
912
+ this.#open_docs.set(uri, {
913
+ version: 1,
914
+ text
915
+ });
916
+ this.#notify("textDocument/didOpen", { textDocument: {
917
+ uri,
918
+ languageId: language_id,
919
+ version: 1,
920
+ text
921
+ } });
922
+ }
923
+ async hover(uri, position) {
924
+ return await this.#request("textDocument/hover", {
925
+ textDocument: { uri },
926
+ position
927
+ }) ?? null;
928
+ }
929
+ async definition(uri, position) {
930
+ return normalize_location_result(await this.#request("textDocument/definition", {
931
+ textDocument: { uri },
932
+ position
933
+ }));
934
+ }
935
+ async references(uri, position, include_declaration) {
936
+ return await this.#request("textDocument/references", {
937
+ textDocument: { uri },
938
+ position,
939
+ context: { includeDeclaration: include_declaration }
940
+ }) ?? [];
941
+ }
942
+ async document_symbols(uri) {
943
+ return normalize_document_symbol_result(await this.#request("textDocument/documentSymbol", { textDocument: { uri } }));
944
+ }
945
+ get_diagnostics(uri) {
946
+ return this.#diagnostics_by_uri.get(uri) ?? [];
947
+ }
948
+ get_all_diagnostics() {
949
+ return new Map(this.#diagnostics_by_uri);
950
+ }
951
+ async wait_for_diagnostics(uri, timeout_ms = 1500) {
952
+ if (this.#diagnostics_by_uri.has(uri)) return this.get_diagnostics(uri);
953
+ return new Promise((resolve) => {
954
+ const handler = (event_uri) => {
955
+ if (event_uri !== uri) return;
956
+ this.off("diagnostics", handler);
957
+ clearTimeout(timer);
958
+ resolve(this.get_diagnostics(uri));
959
+ };
960
+ const timer = setTimeout(() => {
961
+ this.off("diagnostics", handler);
962
+ resolve(this.get_diagnostics(uri));
963
+ }, timeout_ms);
964
+ this.on("diagnostics", handler);
965
+ });
966
+ }
967
+ async stop() {
968
+ if (this.#initialized) try {
969
+ await this.#request("shutdown", null, 1e3);
970
+ this.#notify("exit", null);
971
+ } catch {}
972
+ for (const pending of this.#pending.values()) {
973
+ clearTimeout(pending.timer);
974
+ pending.reject(/* @__PURE__ */ new Error("LSP client stopped"));
975
+ }
976
+ this.#pending.clear();
977
+ if (this.#proc) {
978
+ this.#proc.kill();
979
+ this.#proc = null;
980
+ }
981
+ this.#initialized = false;
982
+ }
983
+ #request(method, params, timeout_override) {
984
+ return new Promise((resolve, reject) => {
985
+ const id = this.#next_id++;
986
+ const timeout_ms = timeout_override ?? this.#options.request_timeout_ms ?? 3e4;
987
+ const timer = setTimeout(() => {
988
+ if (this.#pending.has(id)) {
989
+ this.#pending.delete(id);
990
+ reject(/* @__PURE__ */ new Error(`LSP request ${method} timed out`));
991
+ }
992
+ }, timeout_ms);
993
+ this.#pending.set(id, {
994
+ resolve,
995
+ reject,
996
+ timer
997
+ });
998
+ try {
999
+ this.#send({
1000
+ jsonrpc: "2.0",
1001
+ id,
1002
+ method,
1003
+ params
1004
+ });
1005
+ } catch (error) {
1006
+ clearTimeout(timer);
1007
+ this.#pending.delete(id);
1008
+ reject(error);
1009
+ }
1010
+ });
1011
+ }
1012
+ #notify(method, params) {
1013
+ this.#send({
1014
+ jsonrpc: "2.0",
1015
+ method,
1016
+ params
1017
+ });
1018
+ }
1019
+ #send(message) {
1020
+ if (!this.#proc?.stdin?.writable) throw new Error("LSP server not connected");
1021
+ const body = Buffer.from(JSON.stringify(message), "utf8");
1022
+ const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "ascii");
1023
+ this.#proc.stdin.write(Buffer.concat([header, body]));
1024
+ }
1025
+ #drain_buffer() {
1026
+ while (true) {
1027
+ const header_end = this.#buffer.indexOf("\r\n\r\n");
1028
+ if (header_end === -1) return;
1029
+ const match = this.#buffer.subarray(0, header_end).toString("ascii").match(/Content-Length:\s*(\d+)/i);
1030
+ if (!match) {
1031
+ this.#buffer = this.#buffer.subarray(header_end + 4);
1032
+ continue;
1033
+ }
1034
+ const length = Number(match[1]);
1035
+ const body_start = header_end + 4;
1036
+ if (this.#buffer.length < body_start + length) return;
1037
+ const body = this.#buffer.subarray(body_start, body_start + length);
1038
+ this.#buffer = this.#buffer.subarray(body_start + length);
1039
+ try {
1040
+ this.#handle_message(JSON.parse(body.toString("utf8")));
1041
+ } catch (error) {
1042
+ this.emit("error", error);
1043
+ }
1044
+ }
1045
+ }
1046
+ #handle_message(message) {
1047
+ if (message.id != null && this.#pending.has(Number(message.id))) {
1048
+ const pending = this.#pending.get(Number(message.id));
1049
+ this.#pending.delete(Number(message.id));
1050
+ clearTimeout(pending.timer);
1051
+ if (message.error) pending.reject(/* @__PURE__ */ new Error(`LSP error ${message.error.code}: ${message.error.message}`));
1052
+ else pending.resolve(message.result);
1053
+ return;
1054
+ }
1055
+ if (message.method === "textDocument/publishDiagnostics" && message.params) {
1056
+ const params = message.params;
1057
+ this.#diagnostics_by_uri.set(params.uri, params.diagnostics);
1058
+ this.emit("diagnostics", params.uri);
1059
+ return;
1060
+ }
1061
+ if (message.method && message.id != null) this.#send({
1062
+ jsonrpc: "2.0",
1063
+ id: message.id,
1064
+ result: null
1065
+ });
1066
+ }
1067
+ };
1068
+ function normalize_location_result(result) {
1069
+ if (!result) return [];
1070
+ return (Array.isArray(result) ? result : [result]).map((entry) => {
1071
+ if ("uri" in entry) return entry;
1072
+ return {
1073
+ uri: entry.targetUri,
1074
+ range: entry.targetSelectionRange ?? entry.targetRange
1075
+ };
1076
+ });
1077
+ }
1078
+ function normalize_document_symbol_result(result) {
1079
+ if (!result) return [];
1080
+ if (result.length === 0 || "range" in result[0] && "selectionRange" in result[0]) return result;
1081
+ return result.map((entry) => ({
1082
+ name: entry.name,
1083
+ kind: entry.kind,
1084
+ range: entry.location.range,
1085
+ selectionRange: entry.location.range,
1086
+ containerName: entry.containerName,
1087
+ uri: entry.location.uri
1088
+ }));
1089
+ }
1090
+ function file_path_to_uri(file_path) {
1091
+ return pathToFileURL(file_path).href;
1092
+ }
1093
+ //#endregion
1094
+ //#region src/lsp/servers.ts
1095
+ const EXTENSION_LANGUAGES = {
1096
+ ".ts": "typescript",
1097
+ ".tsx": "typescript",
1098
+ ".mts": "typescript",
1099
+ ".cts": "typescript",
1100
+ ".js": "typescript",
1101
+ ".jsx": "typescript",
1102
+ ".mjs": "typescript",
1103
+ ".cjs": "typescript",
1104
+ ".py": "python",
1105
+ ".rs": "rust",
1106
+ ".go": "go",
1107
+ ".rb": "ruby",
1108
+ ".java": "java",
1109
+ ".lua": "lua",
1110
+ ".svelte": "svelte"
1111
+ };
1112
+ const LANGUAGE_SERVERS = {
1113
+ typescript: {
1114
+ language: "typescript",
1115
+ command: "typescript-language-server",
1116
+ args: ["--stdio"]
1117
+ },
1118
+ python: {
1119
+ language: "python",
1120
+ command: "pylsp",
1121
+ args: []
1122
+ },
1123
+ rust: {
1124
+ language: "rust",
1125
+ command: "rust-analyzer",
1126
+ args: []
1127
+ },
1128
+ go: {
1129
+ language: "go",
1130
+ command: "gopls",
1131
+ args: ["serve"]
1132
+ },
1133
+ ruby: {
1134
+ language: "ruby",
1135
+ command: "solargraph",
1136
+ args: ["stdio"]
1137
+ },
1138
+ java: {
1139
+ language: "java",
1140
+ command: "jdtls",
1141
+ args: []
1142
+ },
1143
+ lua: {
1144
+ language: "lua",
1145
+ command: "lua-language-server",
1146
+ args: []
1147
+ },
1148
+ svelte: {
1149
+ language: "svelte",
1150
+ command: "svelteserver",
1151
+ args: ["--stdio"]
1152
+ }
1153
+ };
1154
+ function detect_language(file_path) {
1155
+ return EXTENSION_LANGUAGES[extname(file_path).toLowerCase()];
1156
+ }
1157
+ function list_supported_languages() {
1158
+ return Object.keys(LANGUAGE_SERVERS).sort();
1159
+ }
1160
+ function resolve_server_command(command, cwd = process.cwd()) {
1161
+ if (!command) return command;
1162
+ if (isAbsolute(command) || command.includes("/") || command.includes("\\")) return command;
1163
+ for (const dir of ancestor_directories(cwd)) {
1164
+ const local_bin = resolve_local_binary(dir, command);
1165
+ if (local_bin) return local_bin;
1166
+ }
1167
+ return command;
1168
+ }
1169
+ function get_server_config(language, cwd = process.cwd()) {
1170
+ const base = LANGUAGE_SERVERS[language];
1171
+ if (!base) return void 0;
1172
+ return {
1173
+ ...base,
1174
+ command: resolve_server_command(base.command, cwd)
1175
+ };
1176
+ }
1177
+ function language_id_for_file(file_path) {
1178
+ return detect_language(file_path);
1179
+ }
1180
+ function ancestor_directories(start) {
1181
+ const dirs = [];
1182
+ let current = resolve(start);
1183
+ while (true) {
1184
+ dirs.push(current);
1185
+ const parent = dirname(current);
1186
+ if (parent === current) break;
1187
+ current = parent;
1188
+ }
1189
+ return dirs;
1190
+ }
1191
+ function resolve_local_binary(directory, command) {
1192
+ return [join(directory, "node_modules", ".bin", command), join(directory, "node_modules", ".bin", `${command}.cmd`)].find((candidate) => existsSync(candidate));
1193
+ }
1194
+ //#endregion
1195
+ //#region src/extensions/lsp.ts
1196
+ function create_lsp_extension(options = {}) {
1197
+ const create_client = options.create_client ?? ((client_options) => new LspClient(client_options));
1198
+ const read_file = options.read_file ?? ((path) => readFile(path, "utf-8"));
1199
+ return async function lsp(pi) {
1200
+ const cwd = options.cwd?.() ?? process.cwd();
1201
+ const root_uri = file_path_to_uri(cwd);
1202
+ const clients_by_language = /* @__PURE__ */ new Map();
1203
+ const failed_languages = /* @__PURE__ */ new Map();
1204
+ const resolve_abs = (file) => isAbsolute(file) ? file : resolve(cwd, file);
1205
+ const clear_language_state = async (language) => {
1206
+ if (language) {
1207
+ const state = clients_by_language.get(language);
1208
+ if (state) {
1209
+ await state.client.stop();
1210
+ clients_by_language.delete(language);
1211
+ }
1212
+ failed_languages.delete(language);
1213
+ return;
1214
+ }
1215
+ await Promise.allSettled(Array.from(clients_by_language.values()).map((state) => state.client.stop()));
1216
+ clients_by_language.clear();
1217
+ failed_languages.clear();
1218
+ };
1219
+ const get_or_start_client = async (file_path) => {
1220
+ const language = detect_language(file_path);
1221
+ if (!language) return void 0;
1222
+ const existing = clients_by_language.get(language);
1223
+ if (existing) return existing;
1224
+ if (failed_languages.has(language)) throw new Error(failed_languages.get(language));
1225
+ const server_config = get_server_config(language, cwd);
1226
+ if (!server_config) return void 0;
1227
+ const client = create_client({
1228
+ command: server_config.command,
1229
+ args: server_config.args,
1230
+ root_uri,
1231
+ language_id_for_uri: (uri) => language_id_for_file(uri)
1232
+ });
1233
+ try {
1234
+ await client.start();
1235
+ } catch (error) {
1236
+ const message = error instanceof Error ? error.message : String(error);
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);
1240
+ }
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
+ };
1251
+ const open_file = async (state, abs_path) => {
1252
+ const text = await read_file(abs_path);
1253
+ const uri = file_path_to_uri(abs_path);
1254
+ await state.client.ensure_document_open(uri, text);
1255
+ return uri;
1256
+ };
1257
+ const get_file_state = async (file) => {
1258
+ const abs = resolve_abs(file);
1259
+ const state = await get_or_start_client(abs);
1260
+ if (!state) return void 0;
1261
+ return {
1262
+ abs,
1263
+ uri: await open_file(state, abs),
1264
+ state
1265
+ };
1266
+ };
1267
+ pi.registerTool(defineTool({
1268
+ name: "lsp_diagnostics",
1269
+ label: "LSP: diagnostics",
1270
+ description: "Get language server diagnostics (errors, warnings, hints) for a file. Uses the project language server and returns empty output if the file is clean.",
1271
+ parameters: Type.Object({
1272
+ file: Type.String({ description: "Path to the file to check (relative to cwd or absolute)." }),
1273
+ wait_ms: Type.Optional(Type.Number({ description: "Max ms to wait for diagnostics after opening the file. Default 1500." }))
1274
+ }),
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
+ };
1284
+ const diagnostics = await result.state.client.wait_for_diagnostics(result.uri, params.wait_ms ?? 1500);
1285
+ return {
1286
+ content: [{
1287
+ type: "text",
1288
+ text: format_diagnostics(result.abs, diagnostics)
1289
+ }],
1290
+ details: {}
1291
+ };
1292
+ }
1293
+ }));
1294
+ pi.registerTool(defineTool({
1295
+ name: "lsp_hover",
1296
+ label: "LSP: hover",
1297
+ description: "Get hover info (types, docs) at a position in a file. Positions are zero-based.",
1298
+ parameters: Type.Object({
1299
+ file: Type.String(),
1300
+ line: Type.Number({ description: "Zero-based line number." }),
1301
+ character: Type.Number({ description: "Zero-based character offset." })
1302
+ }),
1303
+ execute: async (_id, params) => {
1304
+ const result = await get_file_state(params.file);
1305
+ if (!result) return {
1306
+ content: [{
1307
+ type: "text",
1308
+ text: `No language server configured for ${resolve_abs(params.file)}`
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
+ }
1323
+ }));
1324
+ pi.registerTool(defineTool({
1325
+ name: "lsp_definition",
1326
+ label: "LSP: go to definition",
1327
+ description: "Find definition locations for the symbol at a position. Positions are zero-based.",
1328
+ parameters: Type.Object({
1329
+ file: Type.String(),
1330
+ line: Type.Number(),
1331
+ character: Type.Number()
1332
+ }),
1333
+ execute: async (_id, params) => {
1334
+ const result = await get_file_state(params.file);
1335
+ if (!result) return {
1336
+ content: [{
1337
+ type: "text",
1338
+ text: `No language server configured for ${resolve_abs(params.file)}`
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
+ }
1353
+ }));
1354
+ pi.registerTool(defineTool({
1355
+ name: "lsp_references",
1356
+ label: "LSP: find references",
1357
+ description: "Find references to the symbol at a position. Positions are zero-based.",
1358
+ parameters: Type.Object({
1359
+ file: Type.String(),
1360
+ line: Type.Number(),
1361
+ character: Type.Number(),
1362
+ include_declaration: Type.Optional(Type.Boolean())
1363
+ }),
1364
+ execute: async (_id, params) => {
1365
+ const result = await get_file_state(params.file);
1366
+ if (!result) return {
1367
+ content: [{
1368
+ type: "text",
1369
+ text: `No language server configured for ${resolve_abs(params.file)}`
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
+ }
1384
+ }));
1385
+ pi.registerTool(defineTool({
1386
+ name: "lsp_document_symbols",
1387
+ label: "LSP: document symbols",
1388
+ description: "List symbols in a file (functions, classes, variables) using the language server.",
1389
+ 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
+ };
1399
+ const symbols = await result.state.client.document_symbols(result.uri);
1400
+ return {
1401
+ content: [{
1402
+ type: "text",
1403
+ text: format_document_symbols(result.abs, symbols)
1404
+ }],
1405
+ details: {}
1406
+ };
1407
+ }
1408
+ }));
1409
+ pi.registerCommand("lsp", {
1410
+ description: "Show or manage language server state",
1411
+ getArgumentCompletions: (prefix) => {
1412
+ const parts = prefix.trim().split(/\s+/);
1413
+ const subcommands = [
1414
+ "status",
1415
+ "list",
1416
+ "restart"
1417
+ ];
1418
+ if (!prefix.trim()) return subcommands.map((value) => ({
1419
+ value,
1420
+ label: value
1421
+ }));
1422
+ if (parts.length <= 1) return subcommands.filter((value) => value.startsWith(parts[0])).map((value) => ({
1423
+ value,
1424
+ label: value
1425
+ }));
1426
+ if (parts[0] === "restart") {
1427
+ const candidate = parts[1] ?? "";
1428
+ return ["all", ...list_supported_languages()].filter((value) => value.startsWith(candidate)).map((value) => ({
1429
+ value: `restart ${value}`,
1430
+ label: value
1431
+ }));
1432
+ }
1433
+ return null;
1434
+ },
1435
+ handler: async (args, ctx) => {
1436
+ await handle_lsp_command(args, ctx, cwd, clients_by_language, failed_languages, clear_language_state);
1437
+ }
1438
+ });
1439
+ pi.on("session_shutdown", async () => {
1440
+ await clear_language_state();
1441
+ });
1442
+ };
1443
+ }
1444
+ var lsp_default = create_lsp_extension();
1445
+ async function handle_lsp_command(args, ctx, cwd, clients_by_language, failed_languages, clear_language_state) {
1446
+ const [subcommand = "status", target] = args.trim() ? args.trim().split(/\s+/, 2) : [];
1447
+ switch (subcommand) {
1448
+ case "status":
1449
+ case "list":
1450
+ ctx.ui.notify(format_status_lines(cwd, clients_by_language, failed_languages).join("\n"));
1451
+ return;
1452
+ case "restart":
1453
+ if (!target || target === "all") {
1454
+ await clear_language_state();
1455
+ ctx.ui.notify("Restarted all language server state.");
1456
+ return;
1457
+ }
1458
+ if (!list_supported_languages().includes(target)) {
1459
+ ctx.ui.notify(`Unknown language: ${target}. Use one of: ${list_supported_languages().join(", ")}`, "warning");
1460
+ return;
1461
+ }
1462
+ await clear_language_state(target);
1463
+ ctx.ui.notify(`Restarted ${target} language server state.`);
1464
+ return;
1465
+ default: ctx.ui.notify(`Unknown subcommand: ${subcommand}. Use: status, list, restart`, "warning");
1466
+ }
1467
+ }
1468
+ function format_status_lines(cwd, clients_by_language, failed_languages) {
1469
+ const lines = [];
1470
+ for (const language of list_supported_languages()) {
1471
+ const running = clients_by_language.get(language);
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
+ }
1481
+ const config = get_server_config(language, cwd);
1482
+ if (config) lines.push(`${language}: idle — ${config.command}`);
1483
+ }
1484
+ return lines.length > 0 ? lines : ["No language servers configured for this project."];
1485
+ }
1486
+ function severity_label(severity) {
1487
+ switch (severity) {
1488
+ case 1: return "error";
1489
+ case 2: return "warning";
1490
+ case 3: return "info";
1491
+ case 4: return "hint";
1492
+ default: return "info";
1493
+ }
1494
+ }
1495
+ function format_diagnostics(file, diagnostics) {
1496
+ if (diagnostics.length === 0) return `${file}: no diagnostics`;
1497
+ const lines = [`${file}: ${diagnostics.length} diagnostic(s)`];
1498
+ for (const d of diagnostics) {
1499
+ const position = `${d.range.start.line + 1}:${d.range.start.character + 1}`;
1500
+ const source = d.source ? ` [${d.source}]` : "";
1501
+ const code = d.code != null ? ` (${d.code})` : "";
1502
+ lines.push(` ${position} ${severity_label(d.severity)}${source}${code}: ${d.message}`);
1503
+ }
1504
+ return lines.join("\n");
1505
+ }
1506
+ function format_hover(hover) {
1507
+ if (!hover) return "No hover info.";
1508
+ const contents = hover.contents;
1509
+ const extract = (item) => typeof item === "string" ? item : item.value ?? "";
1510
+ if (Array.isArray(contents)) return contents.map(extract).join("\n\n").trim() || "No hover info.";
1511
+ return extract(contents).trim() || "No hover info.";
1512
+ }
1513
+ function format_locations(locations, empty_message) {
1514
+ if (locations.length === 0) return empty_message;
1515
+ return locations.map((loc) => {
1516
+ return `${file_url_to_path_or_value(loc.uri)}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`;
1517
+ }).join("\n");
1518
+ }
1519
+ function format_document_symbols(file, symbols) {
1520
+ if (symbols.length === 0) return `${file}: no symbols`;
1521
+ const lines = [`${file}: ${symbols.length} top-level symbol(s)`];
1522
+ append_symbol_lines(lines, symbols, 1);
1523
+ return lines.join("\n");
1524
+ }
1525
+ function append_symbol_lines(lines, symbols, depth) {
1526
+ for (const symbol of symbols) {
1527
+ const indent = " ".repeat(depth);
1528
+ const detail = symbol.detail ? ` — ${symbol.detail}` : "";
1529
+ const range = `${symbol.range.start.line + 1}:${symbol.range.start.character + 1}`;
1530
+ lines.push(`${indent}${symbol_kind_label(symbol.kind)} ${symbol.name}${detail} @ ${range}`);
1531
+ if (symbol.children?.length) append_symbol_lines(lines, symbol.children, depth + 1);
1532
+ }
1533
+ }
1534
+ 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";
1550
+ }
1551
+ function file_url_to_path_or_value(uri) {
1552
+ try {
1553
+ return uri.startsWith("file:") ? fileURLToPath(uri) : uri;
1554
+ } catch {
1555
+ return uri;
1556
+ }
1557
+ }
1558
+ //#endregion
817
1559
  //#region src/mcp/client.ts
818
1560
  var McpClient = class {
819
1561
  #proc = null;
@@ -2814,7 +3556,8 @@ const BUILTIN_EXTENSION_FACTORIES = {
2814
3556
  "filter-output": filter_output,
2815
3557
  handoff,
2816
3558
  recall,
2817
- "prompt-presets": prompt_presets
3559
+ "prompt-presets": prompt_presets,
3560
+ lsp: lsp_default
2818
3561
  };
2819
3562
  const PACKAGE_THEME_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "themes");
2820
3563
  function get_force_disabled_builtins(options) {
@@ -2826,6 +3569,7 @@ function get_force_disabled_builtins(options) {
2826
3569
  if (!options.handoff) force_disabled.add("handoff");
2827
3570
  if (!options.recall) force_disabled.add("recall");
2828
3571
  if (!options.prompt_presets) force_disabled.add("prompt-presets");
3572
+ if (!options.lsp) force_disabled.add("lsp");
2829
3573
  return force_disabled;
2830
3574
  }
2831
3575
  function create_builtin_extension_factory(key, extension, force_disabled) {
@@ -2847,7 +3591,7 @@ function create_extensions_override(managed_inline_paths) {
2847
3591
  };
2848
3592
  }
2849
3593
  async function create_my_pi(options = {}) {
2850
- const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, model, system_prompt, append_system_prompt } = options;
3594
+ const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, lsp = true, model, system_prompt, append_system_prompt } = options;
2851
3595
  const resolved_extensions = extensions.map((p) => resolve(cwd, p));
2852
3596
  const force_disabled = get_force_disabled_builtins({
2853
3597
  mcp,
@@ -2856,7 +3600,8 @@ async function create_my_pi(options = {}) {
2856
3600
  filter_output,
2857
3601
  handoff,
2858
3602
  recall,
2859
- prompt_presets
3603
+ prompt_presets,
3604
+ lsp
2860
3605
  });
2861
3606
  const managed_extension_factories = [create_extensions_extension({ force_disabled }), ...BUILTIN_EXTENSIONS.map((extension) => create_builtin_extension_factory(extension.key, BUILTIN_EXTENSION_FACTORIES[extension.key], force_disabled))];
2862
3607
  const managed_inline_paths = managed_extension_factories.map((_, index) => `<inline:${index + 1}>`);
@@ -2905,4 +3650,4 @@ async function create_my_pi(options = {}) {
2905
3650
  //#endregion
2906
3651
  export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
2907
3652
 
2908
- //# sourceMappingURL=api-BA6XuDOE.js.map
3653
+ //# sourceMappingURL=api-Dcq2JalY.js.map