my-pi 0.0.7 → 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.
- package/README.md +26 -3
- package/dist/{api-DtthDVIa.js → api-Dcq2JalY.js} +1111 -52
- package/dist/api-Dcq2JalY.js.map +1 -0
- package/dist/api.js +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/extensions/config.test.ts +3 -0
- package/src/extensions/config.ts +10 -1
- package/src/extensions/lsp.test.ts +190 -0
- package/src/extensions/lsp.ts +642 -0
- package/src/extensions/prompt-presets.test.ts +38 -0
- package/src/extensions/prompt-presets.ts +385 -10
- package/src/mcp/client.test.ts +217 -0
- package/src/mcp/client.ts +219 -45
- package/src/mcp/config.test.ts +141 -93
- package/src/mcp/config.ts +110 -22
- package/dist/api-DtthDVIa.js.map +0 -1
|
@@ -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
|
-
import { Container, SettingsList, Text } from "@mariozechner/pi-tui";
|
|
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,47 +823,752 @@ ${task || "Continue from where the previous session left off."}
|
|
|
814
823
|
});
|
|
815
824
|
}
|
|
816
825
|
//#endregion
|
|
817
|
-
//#region src/
|
|
818
|
-
var
|
|
826
|
+
//#region src/lsp/client.ts
|
|
827
|
+
var LspClient = class extends EventEmitter {
|
|
819
828
|
#proc = null;
|
|
820
|
-
#
|
|
821
|
-
#
|
|
829
|
+
#options;
|
|
830
|
+
#next_id = 1;
|
|
822
831
|
#pending = /* @__PURE__ */ new Map();
|
|
823
|
-
#buffer =
|
|
824
|
-
|
|
825
|
-
|
|
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;
|
|
826
839
|
}
|
|
827
|
-
async
|
|
828
|
-
|
|
829
|
-
this.#proc = spawn(command, args, {
|
|
840
|
+
async start() {
|
|
841
|
+
this.#proc = spawn(this.#options.command, this.#options.args, {
|
|
830
842
|
stdio: [
|
|
831
843
|
"pipe",
|
|
832
844
|
"pipe",
|
|
833
845
|
"pipe"
|
|
834
846
|
],
|
|
835
|
-
env:
|
|
836
|
-
|
|
837
|
-
|
|
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"));
|
|
838
856
|
}
|
|
857
|
+
this.#pending.clear();
|
|
858
|
+
this.#initialized = false;
|
|
859
|
+
this.#proc = null;
|
|
839
860
|
});
|
|
840
|
-
this.#proc.
|
|
861
|
+
this.#proc.stderr?.on("data", () => {});
|
|
841
862
|
this.#proc.stdout.on("data", (chunk) => {
|
|
842
|
-
this.#buffer
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
}
|
|
855
|
-
}
|
|
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);
|
|
856
1009
|
}
|
|
857
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
|
|
1559
|
+
//#region src/mcp/client.ts
|
|
1560
|
+
var McpClient = class {
|
|
1561
|
+
#proc = null;
|
|
1562
|
+
#config;
|
|
1563
|
+
#nextId = 1;
|
|
1564
|
+
#pending = /* @__PURE__ */ new Map();
|
|
1565
|
+
#buffer = "";
|
|
1566
|
+
#sessionId;
|
|
1567
|
+
constructor(config) {
|
|
1568
|
+
this.#config = config;
|
|
1569
|
+
}
|
|
1570
|
+
async connect() {
|
|
1571
|
+
if (this.#config.transport === "stdio") await this.#connect_stdio();
|
|
858
1572
|
await this.#request("initialize", {
|
|
859
1573
|
protocolVersion: "2024-11-05",
|
|
860
1574
|
capabilities: {},
|
|
@@ -863,7 +1577,7 @@ var McpClient = class {
|
|
|
863
1577
|
version: "0.0.1"
|
|
864
1578
|
}
|
|
865
1579
|
});
|
|
866
|
-
this.#send({
|
|
1580
|
+
await this.#send({
|
|
867
1581
|
jsonrpc: "2.0",
|
|
868
1582
|
method: "notifications/initialized"
|
|
869
1583
|
});
|
|
@@ -878,12 +1592,39 @@ var McpClient = class {
|
|
|
878
1592
|
});
|
|
879
1593
|
}
|
|
880
1594
|
async disconnect() {
|
|
1595
|
+
if (this.#config.transport === "http") await this.#disconnect_http();
|
|
881
1596
|
if (this.#proc) {
|
|
882
1597
|
this.#proc.kill();
|
|
883
1598
|
this.#proc = null;
|
|
884
1599
|
}
|
|
885
1600
|
this.#pending.clear();
|
|
886
1601
|
}
|
|
1602
|
+
async #connect_stdio() {
|
|
1603
|
+
const { command, args = [], env } = this.#config;
|
|
1604
|
+
this.#proc = spawn(command, args, {
|
|
1605
|
+
stdio: [
|
|
1606
|
+
"pipe",
|
|
1607
|
+
"pipe",
|
|
1608
|
+
"pipe"
|
|
1609
|
+
],
|
|
1610
|
+
env: {
|
|
1611
|
+
...process.env,
|
|
1612
|
+
...env
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
this.#proc.stdout.setEncoding("utf8");
|
|
1616
|
+
this.#proc.stdout.on("data", (chunk) => {
|
|
1617
|
+
this.#buffer += chunk;
|
|
1618
|
+
const lines = this.#buffer.split("\n");
|
|
1619
|
+
this.#buffer = lines.pop() || "";
|
|
1620
|
+
for (const line of lines) {
|
|
1621
|
+
if (!line.trim()) continue;
|
|
1622
|
+
try {
|
|
1623
|
+
this.#handle_message(JSON.parse(line));
|
|
1624
|
+
} catch {}
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
887
1628
|
#request(method, params) {
|
|
888
1629
|
return new Promise((resolve, reject) => {
|
|
889
1630
|
const id = this.#nextId++;
|
|
@@ -896,6 +1637,11 @@ var McpClient = class {
|
|
|
896
1637
|
id,
|
|
897
1638
|
method,
|
|
898
1639
|
params
|
|
1640
|
+
}).catch((error) => {
|
|
1641
|
+
if (this.#pending.has(id)) {
|
|
1642
|
+
this.#pending.delete(id);
|
|
1643
|
+
reject(error);
|
|
1644
|
+
}
|
|
899
1645
|
});
|
|
900
1646
|
setTimeout(() => {
|
|
901
1647
|
if (this.#pending.has(id)) {
|
|
@@ -905,13 +1651,156 @@ var McpClient = class {
|
|
|
905
1651
|
}, 3e4);
|
|
906
1652
|
});
|
|
907
1653
|
}
|
|
908
|
-
#send(msg) {
|
|
1654
|
+
async #send(msg) {
|
|
1655
|
+
if (this.#config.transport === "http") {
|
|
1656
|
+
await this.#send_http(msg);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
909
1659
|
if (!this.#proc?.stdin?.writable) throw new Error("MCP server not connected");
|
|
910
1660
|
this.#proc.stdin.write(JSON.stringify(msg) + "\n");
|
|
911
1661
|
}
|
|
1662
|
+
async #send_http(msg) {
|
|
1663
|
+
const config = this.#config;
|
|
1664
|
+
const headers = new Headers(config.headers ?? {});
|
|
1665
|
+
headers.set("content-type", "application/json");
|
|
1666
|
+
headers.set("accept", "application/json, text/event-stream");
|
|
1667
|
+
if (this.#sessionId) headers.set("mcp-session-id", this.#sessionId);
|
|
1668
|
+
const response = await fetch(config.url, {
|
|
1669
|
+
method: "POST",
|
|
1670
|
+
headers,
|
|
1671
|
+
body: JSON.stringify(msg)
|
|
1672
|
+
});
|
|
1673
|
+
const sessionId = response.headers.get("mcp-session-id");
|
|
1674
|
+
if (sessionId) this.#sessionId = sessionId;
|
|
1675
|
+
if (!response.ok) {
|
|
1676
|
+
const body = await response.text().catch(() => "");
|
|
1677
|
+
throw new Error(`MCP HTTP ${response.status}${body ? `: ${body}` : ""}`);
|
|
1678
|
+
}
|
|
1679
|
+
if (response.status === 204) return;
|
|
1680
|
+
if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
1681
|
+
await this.#consume_sse_response(response, config.name);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const body = await response.text();
|
|
1685
|
+
if (!body.trim()) return;
|
|
1686
|
+
let parsed;
|
|
1687
|
+
try {
|
|
1688
|
+
parsed = JSON.parse(body);
|
|
1689
|
+
} catch {
|
|
1690
|
+
throw new Error(`Invalid MCP HTTP response from ${config.name}: ${body.slice(0, 200)}`);
|
|
1691
|
+
}
|
|
1692
|
+
this.#dispatch_message(parsed);
|
|
1693
|
+
}
|
|
1694
|
+
async #disconnect_http() {
|
|
1695
|
+
const config = this.#config;
|
|
1696
|
+
if (!this.#sessionId) return;
|
|
1697
|
+
const headers = new Headers(config.headers ?? {});
|
|
1698
|
+
headers.set("mcp-session-id", this.#sessionId);
|
|
1699
|
+
const response = await fetch(config.url, {
|
|
1700
|
+
method: "DELETE",
|
|
1701
|
+
headers
|
|
1702
|
+
});
|
|
1703
|
+
if (response.status !== 405 && !response.ok) {
|
|
1704
|
+
const body = await response.text().catch(() => "");
|
|
1705
|
+
throw new Error(`MCP HTTP disconnect ${response.status}${body ? `: ${body}` : ""}`);
|
|
1706
|
+
}
|
|
1707
|
+
this.#sessionId = void 0;
|
|
1708
|
+
}
|
|
1709
|
+
async #consume_sse_response(response, server_name) {
|
|
1710
|
+
if (!response.body) return;
|
|
1711
|
+
const reader = response.body.getReader();
|
|
1712
|
+
const decoder = new TextDecoder();
|
|
1713
|
+
let buffer = "";
|
|
1714
|
+
let event_lines = [];
|
|
1715
|
+
const flush_event = () => {
|
|
1716
|
+
if (event_lines.length === 0) return;
|
|
1717
|
+
const data_lines = event_lines.filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
|
|
1718
|
+
event_lines = [];
|
|
1719
|
+
if (data_lines.length === 0) return;
|
|
1720
|
+
const payload = data_lines.join("\n").trim();
|
|
1721
|
+
if (!payload) return;
|
|
1722
|
+
try {
|
|
1723
|
+
this.#dispatch_message(JSON.parse(payload));
|
|
1724
|
+
} catch {
|
|
1725
|
+
throw new Error(`Invalid MCP SSE payload from ${server_name}: ${payload.slice(0, 200)}`);
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
while (true) {
|
|
1729
|
+
const { done, value } = await reader.read();
|
|
1730
|
+
buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
|
|
1731
|
+
const lines = buffer.replace(/\r\n/g, "\n").split("\n");
|
|
1732
|
+
buffer = lines.pop() ?? "";
|
|
1733
|
+
for (const line of lines) {
|
|
1734
|
+
if (line === "") {
|
|
1735
|
+
flush_event();
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
if (line.startsWith(":")) continue;
|
|
1739
|
+
event_lines.push(line);
|
|
1740
|
+
}
|
|
1741
|
+
if (done) break;
|
|
1742
|
+
}
|
|
1743
|
+
if (buffer.trim()) event_lines.push(buffer.trim());
|
|
1744
|
+
flush_event();
|
|
1745
|
+
}
|
|
1746
|
+
#dispatch_message(message) {
|
|
1747
|
+
if (Array.isArray(message)) {
|
|
1748
|
+
for (const item of message) this.#dispatch_message(item);
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (!message || typeof message !== "object") return;
|
|
1752
|
+
this.#handle_message(message);
|
|
1753
|
+
}
|
|
1754
|
+
#handle_message(msg) {
|
|
1755
|
+
if (msg.id == null || !this.#pending.has(msg.id)) return;
|
|
1756
|
+
const pending = this.#pending.get(msg.id);
|
|
1757
|
+
this.#pending.delete(msg.id);
|
|
1758
|
+
if (msg.error) {
|
|
1759
|
+
pending.reject(/* @__PURE__ */ new Error(`MCP error ${msg.error.code}: ${msg.error.message}`));
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
pending.resolve(msg.result);
|
|
1763
|
+
}
|
|
912
1764
|
};
|
|
913
1765
|
//#endregion
|
|
914
1766
|
//#region src/mcp/config.ts
|
|
1767
|
+
function is_string_record(value, label, name) {
|
|
1768
|
+
if (value === void 0) return true;
|
|
1769
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid MCP server "${name}": ${label} must be an object of string values`);
|
|
1770
|
+
for (const [key, entry] of Object.entries(value)) if (typeof entry !== "string") throw new Error(`Invalid MCP server "${name}": ${label}.${key} must be a string`);
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
function parse_server(name, entry) {
|
|
1774
|
+
const type = typeof entry.type === "string" ? entry.type.trim().toLowerCase() : "";
|
|
1775
|
+
if (type && ![
|
|
1776
|
+
"stdio",
|
|
1777
|
+
"http",
|
|
1778
|
+
"streamable-http"
|
|
1779
|
+
].includes(type)) throw new Error(`Invalid MCP server "${name}": unsupported transport type "${type}"`);
|
|
1780
|
+
if (type === "http" || type === "streamable-http" || entry.url !== void 0) {
|
|
1781
|
+
if (typeof entry.url !== "string" || !entry.url.trim()) throw new Error(`Invalid MCP server "${name}": http transport requires a url`);
|
|
1782
|
+
is_string_record(entry.headers, "headers", name);
|
|
1783
|
+
const headers = entry.headers;
|
|
1784
|
+
return {
|
|
1785
|
+
name,
|
|
1786
|
+
transport: "http",
|
|
1787
|
+
url: entry.url.trim(),
|
|
1788
|
+
...headers ? { headers } : {}
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
if (typeof entry.command !== "string" || !entry.command.trim()) throw new Error(`Invalid MCP server "${name}": stdio transport requires a command`);
|
|
1792
|
+
if (entry.args !== void 0 && (!Array.isArray(entry.args) || entry.args.some((value) => typeof value !== "string"))) throw new Error(`Invalid MCP server "${name}": args must be an array of strings`);
|
|
1793
|
+
is_string_record(entry.env, "env", name);
|
|
1794
|
+
const args = entry.args;
|
|
1795
|
+
const env = entry.env;
|
|
1796
|
+
return {
|
|
1797
|
+
name,
|
|
1798
|
+
transport: "stdio",
|
|
1799
|
+
command: entry.command.trim(),
|
|
1800
|
+
...args ? { args } : {},
|
|
1801
|
+
...env ? { env } : {}
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
915
1804
|
function read_config(path) {
|
|
916
1805
|
if (!existsSync(path)) return {};
|
|
917
1806
|
const raw = readFileSync(path, "utf-8");
|
|
@@ -924,12 +1813,7 @@ function load_mcp_config(cwd) {
|
|
|
924
1813
|
...global_servers,
|
|
925
1814
|
...project_servers
|
|
926
1815
|
};
|
|
927
|
-
return Object.entries(merged).map(([name, server]) => (
|
|
928
|
-
name,
|
|
929
|
-
command: server.command,
|
|
930
|
-
args: server.args,
|
|
931
|
-
env: server.env
|
|
932
|
-
}));
|
|
1816
|
+
return Object.entries(merged).map(([name, server]) => parse_server(name, server));
|
|
933
1817
|
}
|
|
934
1818
|
//#endregion
|
|
935
1819
|
//#region src/extensions/mcp.ts
|
|
@@ -1144,6 +2028,9 @@ function get_global_presets_path() {
|
|
|
1144
2028
|
function get_project_presets_path(cwd) {
|
|
1145
2029
|
return join(cwd, ".pi", "presets.json");
|
|
1146
2030
|
}
|
|
2031
|
+
function get_persisted_prompt_state_path() {
|
|
2032
|
+
return join(getAgentDir(), "prompt-preset-state.json");
|
|
2033
|
+
}
|
|
1147
2034
|
function read_prompt_presets_file(path) {
|
|
1148
2035
|
if (!existsSync(path)) return {};
|
|
1149
2036
|
try {
|
|
@@ -1195,6 +2082,61 @@ function remove_project_prompt_preset(cwd, name) {
|
|
|
1195
2082
|
remaining
|
|
1196
2083
|
};
|
|
1197
2084
|
}
|
|
2085
|
+
function normalize_prompt_preset_state(input) {
|
|
2086
|
+
if (!input || typeof input !== "object") return void 0;
|
|
2087
|
+
const candidate = input;
|
|
2088
|
+
return {
|
|
2089
|
+
base_name: typeof candidate.base_name === "string" && candidate.base_name.trim() ? candidate.base_name.trim() : null,
|
|
2090
|
+
layer_names: Array.isArray(candidate.layer_names) ? [...new Set(candidate.layer_names.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()))].sort() : []
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
function read_persisted_prompt_states(path = get_persisted_prompt_state_path()) {
|
|
2094
|
+
if (!existsSync(path)) return {
|
|
2095
|
+
version: 1,
|
|
2096
|
+
projects: {}
|
|
2097
|
+
};
|
|
2098
|
+
try {
|
|
2099
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
2100
|
+
const raw_projects = parsed.projects && typeof parsed.projects === "object" ? parsed.projects : {};
|
|
2101
|
+
const projects = {};
|
|
2102
|
+
for (const [cwd, value] of Object.entries(raw_projects)) {
|
|
2103
|
+
const normalized = normalize_prompt_preset_state(value);
|
|
2104
|
+
if (!normalized) continue;
|
|
2105
|
+
projects[cwd] = normalized;
|
|
2106
|
+
}
|
|
2107
|
+
return {
|
|
2108
|
+
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
2109
|
+
projects
|
|
2110
|
+
};
|
|
2111
|
+
} catch {
|
|
2112
|
+
return {
|
|
2113
|
+
version: 1,
|
|
2114
|
+
projects: {}
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
function load_persisted_prompt_state(cwd, path = get_persisted_prompt_state_path()) {
|
|
2119
|
+
return read_persisted_prompt_states(path).projects[cwd];
|
|
2120
|
+
}
|
|
2121
|
+
function save_persisted_prompt_state(cwd, state, path = get_persisted_prompt_state_path()) {
|
|
2122
|
+
const persisted = read_persisted_prompt_states(path);
|
|
2123
|
+
persisted.projects[cwd] = normalize_prompt_preset_state(state) ?? {
|
|
2124
|
+
base_name: null,
|
|
2125
|
+
layer_names: []
|
|
2126
|
+
};
|
|
2127
|
+
const dir = dirname(path);
|
|
2128
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
2129
|
+
recursive: true,
|
|
2130
|
+
mode: 448
|
|
2131
|
+
});
|
|
2132
|
+
const tmp = `${path}.tmp-${Date.now()}`;
|
|
2133
|
+
writeFileSync(tmp, JSON.stringify({
|
|
2134
|
+
version: 1,
|
|
2135
|
+
projects: Object.fromEntries(Object.entries(persisted.projects).sort(([a], [b]) => a.localeCompare(b)))
|
|
2136
|
+
}, null, " ") + "\n", { mode: 384 });
|
|
2137
|
+
renameSync(tmp, path);
|
|
2138
|
+
return path;
|
|
2139
|
+
}
|
|
1198
2140
|
function get_last_preset_state(ctx) {
|
|
1199
2141
|
const entries = ctx.sessionManager.getEntries();
|
|
1200
2142
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
@@ -1258,16 +2200,129 @@ function format_active_details(active_base_name, active_layers, presets) {
|
|
|
1258
2200
|
}
|
|
1259
2201
|
return parts.join("\n") || "No preset or layers active";
|
|
1260
2202
|
}
|
|
2203
|
+
function get_footer_prompt_status(active_base_name, active_layers) {
|
|
2204
|
+
if (!active_base_name && active_layers.size === 0) return;
|
|
2205
|
+
return `prompt:${active_base_name ?? "none"}${active_layers.size > 0 ? ` +${active_layers.size}` : ""}`;
|
|
2206
|
+
}
|
|
2207
|
+
function sanitize_status_text(text) {
|
|
2208
|
+
return text.replace(/[\r\n\t]/g, " ").replace(/ +/g, " ").trim();
|
|
2209
|
+
}
|
|
2210
|
+
function format_token_count(count) {
|
|
2211
|
+
if (count < 1e3) return count.toString();
|
|
2212
|
+
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
2213
|
+
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
2214
|
+
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
2215
|
+
return `${Math.round(count / 1e6)}M`;
|
|
2216
|
+
}
|
|
2217
|
+
function get_current_thinking_level(ctx) {
|
|
2218
|
+
const entries = ctx.sessionManager.getEntries();
|
|
2219
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
2220
|
+
const entry = entries[i];
|
|
2221
|
+
if (entry.type === "thinking_level_change" && typeof entry.thinkingLevel === "string") return entry.thinkingLevel;
|
|
2222
|
+
}
|
|
2223
|
+
return ctx.model?.reasoning ? "high" : "off";
|
|
2224
|
+
}
|
|
2225
|
+
function render_footer_lines(ctx, theme, footer_data, width, active_base_name, active_layers) {
|
|
2226
|
+
let total_input = 0;
|
|
2227
|
+
let total_output = 0;
|
|
2228
|
+
let total_cache_read = 0;
|
|
2229
|
+
let total_cache_write = 0;
|
|
2230
|
+
let total_cost = 0;
|
|
2231
|
+
for (const entry of ctx.sessionManager.getEntries()) if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2232
|
+
total_input += entry.message.usage.input;
|
|
2233
|
+
total_output += entry.message.usage.output;
|
|
2234
|
+
total_cache_read += entry.message.usage.cacheRead;
|
|
2235
|
+
total_cache_write += entry.message.usage.cacheWrite;
|
|
2236
|
+
total_cost += entry.message.usage.cost.total;
|
|
2237
|
+
}
|
|
2238
|
+
const context_usage = ctx.getContextUsage();
|
|
2239
|
+
const context_window = context_usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
2240
|
+
const context_percent_value = context_usage?.percent ?? 0;
|
|
2241
|
+
const context_percent = context_usage?.percent !== null ? context_percent_value.toFixed(1) : "?";
|
|
2242
|
+
let pwd = ctx.cwd;
|
|
2243
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
2244
|
+
if (home && pwd.startsWith(home)) pwd = `~${pwd.slice(home.length)}`;
|
|
2245
|
+
const branch = footer_data.getGitBranch();
|
|
2246
|
+
if (branch) pwd = `${pwd} (${branch})`;
|
|
2247
|
+
const session_name = ctx.sessionManager.getSessionName();
|
|
2248
|
+
if (session_name) pwd = `${pwd} • ${session_name}`;
|
|
2249
|
+
const stats_parts = [];
|
|
2250
|
+
if (total_input) stats_parts.push(`↑${format_token_count(total_input)}`);
|
|
2251
|
+
if (total_output) stats_parts.push(`↓${format_token_count(total_output)}`);
|
|
2252
|
+
if (total_cache_read) stats_parts.push(`R${format_token_count(total_cache_read)}`);
|
|
2253
|
+
if (total_cache_write) stats_parts.push(`W${format_token_count(total_cache_write)}`);
|
|
2254
|
+
const using_subscription = ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
|
|
2255
|
+
if (total_cost || using_subscription) stats_parts.push(`$${total_cost.toFixed(3)}${using_subscription ? " (sub)" : ""}`);
|
|
2256
|
+
const context_percent_display = context_percent === "?" ? `?/${format_token_count(context_window)}` : `${context_percent}%/${format_token_count(context_window)}`;
|
|
2257
|
+
let context_percent_str = context_percent_display;
|
|
2258
|
+
if (context_percent_value > 90) context_percent_str = theme.fg("error", context_percent_display);
|
|
2259
|
+
else if (context_percent_value > 70) context_percent_str = theme.fg("warning", context_percent_display);
|
|
2260
|
+
stats_parts.push(context_percent_str);
|
|
2261
|
+
let stats_left = stats_parts.join(" ");
|
|
2262
|
+
let stats_left_width = visibleWidth(stats_left);
|
|
2263
|
+
if (stats_left_width > width) {
|
|
2264
|
+
stats_left = truncateToWidth(stats_left, width, "...");
|
|
2265
|
+
stats_left_width = visibleWidth(stats_left);
|
|
2266
|
+
}
|
|
2267
|
+
const model_name = ctx.model?.id || "no-model";
|
|
2268
|
+
const thinking_level = get_current_thinking_level(ctx);
|
|
2269
|
+
let right_side_without_provider = model_name;
|
|
2270
|
+
if (ctx.model?.reasoning) right_side_without_provider = thinking_level === "off" ? `${model_name} • thinking off` : `${model_name} • ${thinking_level}`;
|
|
2271
|
+
let right_side = right_side_without_provider;
|
|
2272
|
+
if (footer_data.getAvailableProviderCount() > 1 && ctx.model) {
|
|
2273
|
+
right_side = `(${ctx.model.provider}) ${right_side_without_provider}`;
|
|
2274
|
+
if (stats_left_width + 2 + visibleWidth(right_side) > width) right_side = right_side_without_provider;
|
|
2275
|
+
}
|
|
2276
|
+
const right_side_width = visibleWidth(right_side);
|
|
2277
|
+
const total_needed = stats_left_width + 2 + right_side_width;
|
|
2278
|
+
let stats_line;
|
|
2279
|
+
if (total_needed <= width) {
|
|
2280
|
+
const padding = " ".repeat(width - stats_left_width - right_side_width);
|
|
2281
|
+
stats_line = stats_left + padding + right_side;
|
|
2282
|
+
} else {
|
|
2283
|
+
const available_for_right = width - stats_left_width - 2;
|
|
2284
|
+
if (available_for_right > 0) {
|
|
2285
|
+
const truncated_right = truncateToWidth(right_side, available_for_right, "");
|
|
2286
|
+
const truncated_right_width = visibleWidth(truncated_right);
|
|
2287
|
+
const padding = " ".repeat(Math.max(0, width - stats_left_width - truncated_right_width));
|
|
2288
|
+
stats_line = stats_left + padding + truncated_right;
|
|
2289
|
+
} else stats_line = stats_left;
|
|
2290
|
+
}
|
|
2291
|
+
const dim_stats_left = theme.fg("dim", stats_left);
|
|
2292
|
+
const remainder = stats_line.slice(stats_left.length);
|
|
2293
|
+
const dim_remainder = theme.fg("dim", remainder);
|
|
2294
|
+
const lines = [truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "...")), dim_stats_left + dim_remainder];
|
|
2295
|
+
const prompt_status = get_footer_prompt_status(active_base_name, active_layers);
|
|
2296
|
+
if (prompt_status) {
|
|
2297
|
+
const themed_status = theme.fg("dim", prompt_status);
|
|
2298
|
+
const status_width = visibleWidth(themed_status);
|
|
2299
|
+
const aligned_status = status_width >= width ? truncateToWidth(themed_status, width, theme.fg("dim", "...")) : `${" ".repeat(width - status_width)}${themed_status}`;
|
|
2300
|
+
lines.push(aligned_status);
|
|
2301
|
+
}
|
|
2302
|
+
const other_statuses = Array.from(footer_data.getExtensionStatuses().entries()).filter(([key]) => key !== "preset").sort(([a], [b]) => a.localeCompare(b)).map(([, text]) => sanitize_status_text(text));
|
|
2303
|
+
if (other_statuses.length > 0) lines.push(truncateToWidth(other_statuses.join(" "), width, theme.fg("dim", "...")));
|
|
2304
|
+
return lines;
|
|
2305
|
+
}
|
|
1261
2306
|
function set_status(ctx, active_base_name, active_layers) {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
ctx.ui.
|
|
2307
|
+
ctx.ui.setStatus("preset", void 0);
|
|
2308
|
+
if (!ctx.hasUI) return;
|
|
2309
|
+
ctx.ui.setFooter((tui, theme, footer_data) => {
|
|
2310
|
+
return {
|
|
2311
|
+
dispose: footer_data.onBranchChange(() => tui.requestRender()),
|
|
2312
|
+
invalidate() {},
|
|
2313
|
+
render(width) {
|
|
2314
|
+
return render_footer_lines(ctx, theme, footer_data, width, active_base_name, active_layers);
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
});
|
|
1265
2318
|
}
|
|
1266
|
-
function persist_state(pi, active_base_name, active_layers) {
|
|
1267
|
-
|
|
2319
|
+
function persist_state(pi, ctx, active_base_name, active_layers) {
|
|
2320
|
+
const state = {
|
|
1268
2321
|
base_name: active_base_name ?? null,
|
|
1269
2322
|
layer_names: [...active_layers].sort()
|
|
1270
|
-
}
|
|
2323
|
+
};
|
|
2324
|
+
pi.appendEntry(PRESET_STATE_TYPE, state);
|
|
2325
|
+
save_persisted_prompt_state(ctx.cwd, state);
|
|
1271
2326
|
}
|
|
1272
2327
|
function normalize_active_state(presets, active_base_name, active_layers) {
|
|
1273
2328
|
return {
|
|
@@ -1308,7 +2363,7 @@ async function prompt_presets(pi) {
|
|
|
1308
2363
|
active_base_name = next_base_name;
|
|
1309
2364
|
active_layers = new Set(next_layers);
|
|
1310
2365
|
set_status(ctx, active_base_name, active_layers);
|
|
1311
|
-
if (options?.persist !== false) persist_state(pi, active_base_name, active_layers);
|
|
2366
|
+
if (options?.persist !== false) persist_state(pi, ctx, active_base_name, active_layers);
|
|
1312
2367
|
if (options?.notify) ctx.ui.notify(options.notify, "info");
|
|
1313
2368
|
}
|
|
1314
2369
|
function activate_base(name, ctx, options) {
|
|
@@ -1384,7 +2439,7 @@ async function prompt_presets(pi) {
|
|
|
1384
2439
|
active_base_name = normalized.active_base_name;
|
|
1385
2440
|
active_layers = normalized.active_layers;
|
|
1386
2441
|
set_status(ctx, active_base_name, active_layers);
|
|
1387
|
-
persist_state(pi, active_base_name, active_layers);
|
|
2442
|
+
persist_state(pi, ctx, active_base_name, active_layers);
|
|
1388
2443
|
const fallback = presets[name];
|
|
1389
2444
|
if (mode === "reset" && fallback) {
|
|
1390
2445
|
ctx.ui.notify(`Reset "${name}" to ${get_prompt_source_label(fallback.source)} preset`, "info");
|
|
@@ -1681,7 +2736,7 @@ async function prompt_presets(pi) {
|
|
|
1681
2736
|
set_status(ctx, active_base_name, active_layers);
|
|
1682
2737
|
return;
|
|
1683
2738
|
}
|
|
1684
|
-
const restored = get_last_preset_state(ctx);
|
|
2739
|
+
const restored = get_last_preset_state(ctx) ?? load_persisted_prompt_state(ctx.cwd);
|
|
1685
2740
|
if (restored) {
|
|
1686
2741
|
active_base_name = restored.base_name ?? void 0;
|
|
1687
2742
|
active_layers = new Set(restored.layer_names ?? []);
|
|
@@ -1702,6 +2757,7 @@ async function prompt_presets(pi) {
|
|
|
1702
2757
|
});
|
|
1703
2758
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1704
2759
|
ctx.ui.setStatus("preset", void 0);
|
|
2760
|
+
ctx.ui.setFooter(void 0);
|
|
1705
2761
|
});
|
|
1706
2762
|
}
|
|
1707
2763
|
//#endregion
|
|
@@ -2500,7 +3556,8 @@ const BUILTIN_EXTENSION_FACTORIES = {
|
|
|
2500
3556
|
"filter-output": filter_output,
|
|
2501
3557
|
handoff,
|
|
2502
3558
|
recall,
|
|
2503
|
-
"prompt-presets": prompt_presets
|
|
3559
|
+
"prompt-presets": prompt_presets,
|
|
3560
|
+
lsp: lsp_default
|
|
2504
3561
|
};
|
|
2505
3562
|
const PACKAGE_THEME_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "themes");
|
|
2506
3563
|
function get_force_disabled_builtins(options) {
|
|
@@ -2512,6 +3569,7 @@ function get_force_disabled_builtins(options) {
|
|
|
2512
3569
|
if (!options.handoff) force_disabled.add("handoff");
|
|
2513
3570
|
if (!options.recall) force_disabled.add("recall");
|
|
2514
3571
|
if (!options.prompt_presets) force_disabled.add("prompt-presets");
|
|
3572
|
+
if (!options.lsp) force_disabled.add("lsp");
|
|
2515
3573
|
return force_disabled;
|
|
2516
3574
|
}
|
|
2517
3575
|
function create_builtin_extension_factory(key, extension, force_disabled) {
|
|
@@ -2533,7 +3591,7 @@ function create_extensions_override(managed_inline_paths) {
|
|
|
2533
3591
|
};
|
|
2534
3592
|
}
|
|
2535
3593
|
async function create_my_pi(options = {}) {
|
|
2536
|
-
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;
|
|
2537
3595
|
const resolved_extensions = extensions.map((p) => resolve(cwd, p));
|
|
2538
3596
|
const force_disabled = get_force_disabled_builtins({
|
|
2539
3597
|
mcp,
|
|
@@ -2542,7 +3600,8 @@ async function create_my_pi(options = {}) {
|
|
|
2542
3600
|
filter_output,
|
|
2543
3601
|
handoff,
|
|
2544
3602
|
recall,
|
|
2545
|
-
prompt_presets
|
|
3603
|
+
prompt_presets,
|
|
3604
|
+
lsp
|
|
2546
3605
|
});
|
|
2547
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))];
|
|
2548
3607
|
const managed_inline_paths = managed_extension_factories.map((_, index) => `<inline:${index + 1}>`);
|
|
@@ -2591,4 +3650,4 @@ async function create_my_pi(options = {}) {
|
|
|
2591
3650
|
//#endregion
|
|
2592
3651
|
export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
|
|
2593
3652
|
|
|
2594
|
-
//# sourceMappingURL=api-
|
|
3653
|
+
//# sourceMappingURL=api-Dcq2JalY.js.map
|