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.
@@ -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 { execFileSync, spawn } from "node:child_process";
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.emit("error", err);
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
- 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 }
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
- 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;
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
- const handler = (event_uri) => {
955
- if (event_uri !== uri) return;
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 timer = setTimeout(() => {
961
- this.off("diagnostics", handler);
962
- resolve(this.get_diagnostics(uri));
963
- }, timeout_ms);
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.emit("error", error);
1086
+ this.#emit_error(error);
1043
1087
  }
1044
1088
  }
1045
1089
  }
1046
1090
  #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));
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 root_uri = file_path_to_uri(cwd);
1202
- const clients_by_language = /* @__PURE__ */ new Map();
1203
- const failed_languages = /* @__PURE__ */ new Map();
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
- 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);
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
- await Promise.allSettled(Array.from(clients_by_language.values()).map((state) => state.client.stop()));
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 existing = clients_by_language.get(language);
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
- if (failed_languages.has(language)) throw new Error(failed_languages.get(language));
1225
- const server_config = get_server_config(language, cwd);
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 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
- });
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 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);
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
- content: [{
1287
- type: "text",
1288
- text: format_diagnostics(result.abs, diagnostics)
1289
- }],
1290
- details: {}
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
- 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
- }
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
- 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
- }
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
- 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
- }
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
- content: [{
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, clients_by_language, failed_languages, clear_language_state);
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, clients_by_language, failed_languages, clear_language_state) {
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, clients_by_language, failed_languages).join("\n"));
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, clients_by_language, failed_languages) {
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
- 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
- }
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 cwd = process.cwd();
1822
- const servers = /* @__PURE__ */ new Map();
1823
- const configs = load_mcp_config(cwd);
1824
- const results = await Promise.allSettled(configs.map(async (config) => {
1825
- const client = new McpClient(config);
1826
- await client.connect();
1827
- return {
1828
- config,
1829
- client,
1830
- mcp_tools: await client.listTools()
1831
- };
1832
- }));
1833
- for (const result of results) {
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
- const { config, client, mcp_tools } = result.value;
1839
- const tool_names = [];
1840
- for (const mcp_tool of mcp_tools) {
1841
- const tool_name = `mcp__${config.name}__${mcp_tool.name}`;
1842
- tool_names.push(tool_name);
1843
- pi.registerTool(defineTool({
1844
- name: tool_name,
1845
- label: `${config.name}: ${mcp_tool.name}`,
1846
- description: mcp_tool.description || mcp_tool.name,
1847
- parameters: mcp_tool.inputSchema || {
1848
- type: "object",
1849
- properties: {}
1850
- },
1851
- execute: async (_id, params) => {
1852
- const result = await client.callTool(mcp_tool.name, params);
1853
- return {
1854
- content: [{
1855
- type: "text",
1856
- text: result?.content?.map((c) => c.text || "").join("\n") || JSON.stringify(result)
1857
- }],
1858
- details: {}
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
- servers.set(config.name, {
1864
- config,
1865
- client,
1866
- tool_names,
1867
- enabled: true
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
- const active = pi.getActiveTools();
1920
- pi.setActiveTools([...active, ...server.tool_names]);
1921
- ctx.ui.notify(`Enabled ${name}`);
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
- const tool_set = new Set(server.tool_names);
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
- for (const server of servers.values()) await server.client.disconnect();
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
- async function recall(pi) {
2767
- if (existsSync(DEFAULT_DB_PATH)) try {
2768
- execFileSync("npx", [
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-Dcq2JalY.js.map
3972
+ //# sourceMappingURL=api-7LVj6kUV.js.map