lsd-pi 1.3.9 → 1.3.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.
Files changed (46) hide show
  1. package/dist/resources/extensions/mcp-client/index.js +191 -83
  2. package/dist/resources/extensions/mcp-client/mcp-manager-component.js +220 -0
  3. package/dist/resources/extensions/slash-commands/plan.js +67 -13
  4. package/dist/resources/extensions/subagent/agents.js +7 -0
  5. package/dist/resources/extensions/subagent/index.js +25 -8
  6. package/dist/resources/extensions/subagent/model-resolution.js +1 -0
  7. package/package.json +1 -1
  8. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +104 -2
  9. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -1
  10. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +39 -2
  11. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  12. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +135 -18
  13. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  14. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +21 -2
  15. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -1
  16. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +146 -8
  17. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -1
  18. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +51 -13
  19. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -1
  20. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  21. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +75 -4
  22. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  23. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
  24. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  25. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  26. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  27. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  28. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +31 -2
  29. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  30. package/packages/pi-coding-agent/package.json +1 -1
  31. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +129 -2
  32. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +158 -18
  33. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +163 -9
  34. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +60 -13
  35. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +86 -5
  36. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
  37. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -2
  38. package/pkg/package.json +1 -1
  39. package/src/resources/extensions/mcp-client/index.ts +212 -90
  40. package/src/resources/extensions/mcp-client/mcp-manager-component.ts +256 -0
  41. package/src/resources/extensions/mcp-client/tests/mcp-manager-component.test.ts +141 -0
  42. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +18 -2
  43. package/src/resources/extensions/slash-commands/plan.ts +70 -13
  44. package/src/resources/extensions/subagent/agents.ts +9 -0
  45. package/src/resources/extensions/subagent/index.ts +30 -8
  46. package/src/resources/extensions/subagent/model-resolution.ts +1 -0
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * MCP Client Extension — Native MCP server integration for pi
3
3
  *
4
- * Provides on-demand access to MCP servers configured in project files
5
- * (.mcp.json, .lsd/mcp.json, with legacy .gsd/mcp.json fallback) using the
4
+ * Provides on-demand access to MCP servers configured in global (~/.lsd/mcp.json)
5
+ * and project files (.mcp.json, .lsd/mcp.json, with legacy .gsd/mcp.json fallback) using the
6
6
  * @modelcontextprotocol/sdk Client directly — no external CLI dependency
7
7
  * required.
8
8
  *
@@ -25,7 +25,9 @@ import { Client } from "@modelcontextprotocol/sdk/client";
25
25
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
26
26
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
27
27
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
28
+ import { homedir } from "node:os";
28
29
  import { basename, dirname, join } from "node:path";
30
+ import { McpManagerComponent, type McpManagerServerInfo } from "./mcp-manager-component.js";
29
31
 
30
32
  // ─── Types ────────────────────────────────────────────────────────────────────
31
33
 
@@ -154,6 +156,7 @@ function readConfigs(): McpServerConfig[] {
154
156
  join(process.cwd(), ".mcp.json"),
155
157
  join(process.cwd(), ".lsd", "mcp.json"),
156
158
  join(process.cwd(), ".gsd", "mcp.json"),
159
+ join(homedir(), ".lsd", "mcp.json"),
157
160
  ];
158
161
 
159
162
  for (const configPath of configPaths) {
@@ -241,8 +244,6 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client>
241
244
  if (!config) throw new Error(`Unknown MCP server: "${name}". Use mcp_servers to list available servers.`);
242
245
  if (!config.enabled) throw new Error(`Server "${config.name}" is disabled. Use /mcp enable ${config.name}.`);
243
246
 
244
- // Always use config.name as the canonical cache key so that variant
245
- // casing / whitespace still hits the same connection.
246
247
  const existing = connections.get(config.name);
247
248
  if (existing) return existing.client;
248
249
 
@@ -271,40 +272,118 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client>
271
272
  return client;
272
273
  }
273
274
 
275
+ function mapToolSchemas(tools: Array<{ name: string; description?: string; inputSchema?: unknown }>): McpToolSchema[] {
276
+ return tools.map((tool) => ({
277
+ name: tool.name,
278
+ description: tool.description ?? "",
279
+ inputSchema: tool.inputSchema as Record<string, unknown> | undefined,
280
+ }));
281
+ }
282
+
283
+ function shouldRetryMcpOperation(error: unknown): boolean {
284
+ const message = error instanceof Error ? error.message : String(error);
285
+ if (/unknown mcp server/i.test(message)) return false;
286
+ if (/is disabled/i.test(message)) return false;
287
+ if (/unsupported transport/i.test(message)) return false;
288
+ if (/abort|cancel/i.test(message)) return false;
289
+ return true;
290
+ }
291
+
292
+ async function listServerTools(
293
+ name: string,
294
+ signal?: AbortSignal,
295
+ options?: { forceReconnect?: boolean; useCache?: boolean },
296
+ ): Promise<{ canonicalName: string; tools: McpToolSchema[]; cached: boolean }> {
297
+ const canonicalName = getCanonicalServerName(name);
298
+ if (options?.useCache !== false) {
299
+ const cached = toolCache.get(canonicalName);
300
+ if (cached) {
301
+ return { canonicalName, tools: cached, cached: true };
302
+ }
303
+ }
304
+
305
+ let attempt = 0;
306
+ while (attempt < 2) {
307
+ try {
308
+ if (attempt === 0 && options?.forceReconnect) {
309
+ await closeServerConnection(canonicalName);
310
+ }
311
+ if (attempt > 0) {
312
+ await closeServerConnection(canonicalName);
313
+ }
314
+ const client = await getOrConnect(canonicalName, signal);
315
+ const result = await client.listTools(undefined, { signal, timeout: 30000 });
316
+ const tools = mapToolSchemas(result.tools ?? []);
317
+ toolCache.set(canonicalName, tools);
318
+ return { canonicalName, tools, cached: false };
319
+ } catch (error) {
320
+ attempt += 1;
321
+ await closeServerConnection(canonicalName);
322
+ if (attempt >= 2 || !shouldRetryMcpOperation(error)) {
323
+ throw error;
324
+ }
325
+ }
326
+ }
327
+
328
+ throw new Error(`Failed to list tools for ${canonicalName}`);
329
+ }
330
+
331
+ async function callServerTool(
332
+ serverName: string,
333
+ toolName: string,
334
+ args: Record<string, unknown>,
335
+ signal?: AbortSignal,
336
+ ): Promise<{ canonicalServer: string; result: Awaited<ReturnType<Client["callTool"]>> }> {
337
+ const canonicalServer = getCanonicalServerName(serverName);
338
+ let attempt = 0;
339
+ while (attempt < 2) {
340
+ try {
341
+ if (attempt > 0) {
342
+ await closeServerConnection(canonicalServer);
343
+ }
344
+ const client = await getOrConnect(canonicalServer, signal);
345
+ const result = await client.callTool(
346
+ { name: toolName, arguments: args },
347
+ undefined,
348
+ { signal, timeout: 60000 },
349
+ );
350
+ return { canonicalServer, result };
351
+ } catch (error) {
352
+ attempt += 1;
353
+ await closeServerConnection(canonicalServer);
354
+ if (attempt >= 2 || !shouldRetryMcpOperation(error)) {
355
+ throw error;
356
+ }
357
+ }
358
+ }
359
+
360
+ throw new Error(`Failed to call ${canonicalServer}.${toolName}`);
361
+ }
362
+
363
+ async function warmupServer(name: string, signal?: AbortSignal): Promise<McpWarmupResult> {
364
+ try {
365
+ const { canonicalName, tools } = await listServerTools(name, signal, { useCache: false });
366
+ return {
367
+ name: canonicalName,
368
+ status: "connected",
369
+ toolCount: tools.length,
370
+ };
371
+ } catch (error) {
372
+ return {
373
+ name: getCanonicalServerName(name),
374
+ status: "error",
375
+ error: error instanceof Error ? error.message : String(error),
376
+ };
377
+ }
378
+ }
379
+
274
380
  async function warmupEnabledServers(): Promise<McpWarmupResult[]> {
275
381
  if (warmupPromise) return warmupPromise;
276
382
 
277
383
  warmupPromise = (async () => {
278
384
  const enabledServers = readConfigs().filter((server) => server.enabled);
279
385
  if (enabledServers.length === 0) return [];
280
-
281
- const results = await Promise.allSettled(enabledServers.map(async (server) => {
282
- const client = await getOrConnect(server.name);
283
- const result = await client.listTools(undefined, { timeout: 30000 });
284
- const tools: McpToolSchema[] = (result.tools ?? []).map((tool) => ({
285
- name: tool.name,
286
- description: tool.description ?? "",
287
- inputSchema: tool.inputSchema as Record<string, unknown> | undefined,
288
- }));
289
- toolCache.set(server.name, tools);
290
- return {
291
- name: server.name,
292
- status: "connected" as const,
293
- toolCount: tools.length,
294
- };
295
- }));
296
-
297
- return results.map((result, index) => {
298
- if (result.status === "fulfilled") {
299
- return result.value;
300
- }
301
-
302
- return {
303
- name: enabledServers[index]?.name ?? `server-${index + 1}`,
304
- status: "error" as const,
305
- error: result.reason instanceof Error ? result.reason.message : String(result.reason),
306
- };
307
- });
386
+ return Promise.all(enabledServers.map((server) => warmupServer(server.name)));
308
387
  })();
309
388
 
310
389
  try {
@@ -333,10 +412,43 @@ async function reloadMcpState(): Promise<void> {
333
412
  configCache = null;
334
413
  }
335
414
 
415
+ function getSourceLabel(sourcePath?: string): string {
416
+ if (!sourcePath) return "";
417
+ return sourcePath.startsWith(homedir()) ? "global" : "project";
418
+ }
419
+
420
+ function getManagerServerInfo(): McpManagerServerInfo[] {
421
+ return readConfigs().map((server) => ({
422
+ name: server.name,
423
+ enabled: server.enabled,
424
+ connected: connections.has(server.name),
425
+ transport: server.transport,
426
+ toolCount: toolCache.get(server.name)?.length ?? 0,
427
+ sourceLabel: getSourceLabel(server.sourcePath),
428
+ }));
429
+ }
430
+
336
431
  // ─── Formatters ───────────────────────────────────────────────────────────────
337
432
 
338
433
  function formatServerList(servers: McpServerConfig[]): string {
339
- if (servers.length === 0) return "No MCP servers configured. Add servers to .mcp.json or .lsd/mcp.json.";
434
+ if (servers.length === 0) {
435
+ return [
436
+ "No MCP servers configured.\n",
437
+ "Configuration guide:",
438
+ " Global (all projects): ~/.lsd/mcp.json",
439
+ " Project-level: .mcp.json or .lsd/mcp.json\n",
440
+ 'Example ~/.lsd/mcp.json:',
441
+ '{',
442
+ ' "mcpServers": {',
443
+ ' "my-server": {',
444
+ ' "command": "path/to/server",',
445
+ ' "args": ["--working-dir", "."]',
446
+ ' }',
447
+ ' }',
448
+ '}\n',
449
+ "After editing, run: /mcp reload",
450
+ ].join("\n");
451
+ }
340
452
 
341
453
  const lines: string[] = ["MCP servers\n"];
342
454
 
@@ -349,11 +461,14 @@ function formatServerList(servers: McpServerConfig[]): string {
349
461
  lines.push(` connected: ${connected}`);
350
462
  lines.push(` transport: ${s.transport}`);
351
463
  lines.push(` tools: ${tools}`);
352
- if (s.sourcePath) lines.push(` source: ${basename(s.sourcePath)}`);
464
+ if (s.sourcePath) {
465
+ lines.push(` source: ${getSourceLabel(s.sourcePath)} — ${basename(s.sourcePath)}`);
466
+ }
353
467
  lines.push("");
354
468
  }
355
469
 
356
470
  lines.push("Hints:");
471
+ lines.push(" /mcp");
357
472
  lines.push(" /mcp inspect <server>");
358
473
  lines.push(" /mcp enable <server>");
359
474
  lines.push(" /mcp disable <server>");
@@ -394,6 +509,42 @@ function formatMcpCommandHelp(): string {
394
509
  ].join("\n");
395
510
  }
396
511
 
512
+ async function openMcpManager(ctx: ExtensionCommandContext): Promise<void> {
513
+ await ctx.ui.custom<void>(
514
+ (tui, theme, _keybindings, done) => new McpManagerComponent({
515
+ getServers: () => getManagerServerInfo(),
516
+ onToggle: async (name) => {
517
+ const config = getServerConfig(name);
518
+ if (!config) return null;
519
+ const result = await setServerEnabled(name, !config.enabled);
520
+ const updated = getServerConfig(result.canonicalName);
521
+ if (updated?.enabled) {
522
+ await warmupServer(updated.name);
523
+ }
524
+ return getManagerServerInfo().find((server) => server.name === result.canonicalName) ?? null;
525
+ },
526
+ onInspect: async (name) => {
527
+ const { canonicalName, tools } = await listServerTools(name, undefined, { useCache: true });
528
+ return formatToolList(canonicalName, tools);
529
+ },
530
+ onReconnect: async (name) => {
531
+ const { canonicalName } = await listServerTools(name, undefined, { forceReconnect: true, useCache: false });
532
+ return getManagerServerInfo().find((server) => server.name === canonicalName) ?? null;
533
+ },
534
+ onClose: () => done(undefined),
535
+ requestRender: () => tui.requestRender(),
536
+ }, theme),
537
+ {
538
+ overlay: true,
539
+ overlayOptions: {
540
+ width: "80%",
541
+ maxHeight: "70%",
542
+ anchor: "center",
543
+ },
544
+ },
545
+ );
546
+ }
547
+
397
548
  async function handleMcpCommand(args: string, ctx: ExtensionCommandContext): Promise<void> {
398
549
  const trimmed = args.trim();
399
550
  const parts = trimmed.split(/\s+/).filter(Boolean);
@@ -418,21 +569,9 @@ async function handleMcpCommand(args: string, ctx: ExtensionCommandContext): Pro
418
569
  }
419
570
 
420
571
  const canonicalName = config.name;
421
- const cached = toolCache.get(canonicalName);
422
- if (cached) {
423
- ctx.ui.notify(formatToolList(canonicalName, cached), "info");
424
- return;
425
- }
426
572
 
427
573
  try {
428
- const client = await getOrConnect(canonicalName);
429
- const result = await client.listTools(undefined, { timeout: 30000 });
430
- const tools: McpToolSchema[] = (result.tools ?? []).map((tool) => ({
431
- name: tool.name,
432
- description: tool.description ?? "",
433
- inputSchema: tool.inputSchema as Record<string, unknown> | undefined,
434
- }));
435
- toolCache.set(canonicalName, tools);
574
+ const { tools } = await listServerTools(canonicalName, undefined, { useCache: true });
436
575
  ctx.ui.notify(formatToolList(canonicalName, tools), "info");
437
576
  } catch (error) {
438
577
  const message = error instanceof Error ? error.message : String(error);
@@ -456,9 +595,8 @@ async function handleMcpCommand(args: string, ctx: ExtensionCommandContext): Pro
456
595
  ctx.ui.notify(`MCP server ${result.canonicalName} ${changeText}.`, "info");
457
596
 
458
597
  if (enabled) {
459
- const warmupResults = await warmupEnabledServers();
460
- const warmupResult = warmupResults.find((entry) => entry.name === result.canonicalName);
461
- if (warmupResult?.status === "error") {
598
+ const warmupResult = await warmupServer(result.canonicalName);
599
+ if (warmupResult.status === "error") {
462
600
  ctx.ui.notify(`Failed to connect ${result.canonicalName}: ${warmupResult.error}`, "error");
463
601
  }
464
602
  }
@@ -542,6 +680,10 @@ export default function(pi: ExtensionAPI) {
542
680
  return [];
543
681
  },
544
682
  handler: async (args, ctx) => {
683
+ if (!args.trim() && typeof ctx.ui.custom === "function") {
684
+ await openMcpManager(ctx);
685
+ return;
686
+ }
545
687
  await handleMcpCommand(args, ctx);
546
688
  },
547
689
  });
@@ -552,7 +694,8 @@ export default function(pi: ExtensionAPI) {
552
694
  name: "mcp_servers",
553
695
  label: "MCP Servers",
554
696
  description:
555
- "List all available MCP servers configured in project files (.mcp.json, .lsd/mcp.json, legacy .gsd/mcp.json). " +
697
+ "List all available MCP servers from global (~/.lsd/mcp.json) and project-level " +
698
+ "(.mcp.json, .lsd/mcp.json, legacy .gsd/mcp.json) config files. " +
556
699
  "Shows server names, transport type, and connection status. Use mcp_discover to get full tool schemas for a server.",
557
700
  promptSnippet:
558
701
  "List available MCP servers from project configuration",
@@ -623,32 +766,7 @@ export default function(pi: ExtensionAPI) {
623
766
 
624
767
  async execute(_id, params, signal) {
625
768
  try {
626
- const canonicalServer = getCanonicalServerName(params.server);
627
-
628
- // Return cached tools if available
629
- const cached = toolCache.get(canonicalServer);
630
- if (cached) {
631
- const text = formatToolList(canonicalServer, cached);
632
- const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
633
- let finalText = truncation.content;
634
- if (truncation.truncated) {
635
- finalText += `\n\n[Truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
636
- }
637
- return {
638
- content: [{ type: "text", text: finalText }],
639
- details: { server: canonicalServer, toolCount: cached.length, cached: true },
640
- };
641
- }
642
-
643
- const client = await getOrConnect(canonicalServer, signal);
644
- const result = await client.listTools(undefined, { signal, timeout: 30000 });
645
- const tools: McpToolSchema[] = (result.tools ?? []).map((t) => ({
646
- name: t.name,
647
- description: t.description ?? "",
648
- inputSchema: t.inputSchema as Record<string, unknown> | undefined,
649
- }));
650
- toolCache.set(canonicalServer, tools);
651
-
769
+ const { canonicalName: canonicalServer, tools, cached } = await listServerTools(params.server, signal, { useCache: true });
652
770
  const text = formatToolList(canonicalServer, tools);
653
771
  const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
654
772
  let finalText = truncation.content;
@@ -658,7 +776,7 @@ export default function(pi: ExtensionAPI) {
658
776
 
659
777
  return {
660
778
  content: [{ type: "text", text: finalText }],
661
- details: { server: canonicalServer, toolCount: tools.length, cached: false },
779
+ details: { server: canonicalServer, toolCount: tools.length, cached },
662
780
  };
663
781
  } catch (err: unknown) {
664
782
  const msg = err instanceof Error ? err.message : String(err);
@@ -717,15 +835,13 @@ export default function(pi: ExtensionAPI) {
717
835
 
718
836
  async execute(_id, params, signal) {
719
837
  try {
720
- const canonicalServer = getCanonicalServerName(params.server);
721
- const client = await getOrConnect(canonicalServer, signal);
722
- const result = await client.callTool(
723
- { name: params.tool, arguments: params.args ?? {} },
724
- undefined,
725
- { signal, timeout: 60000 },
838
+ const { canonicalServer, result } = await callServerTool(
839
+ params.server,
840
+ params.tool,
841
+ params.args ?? {},
842
+ signal,
726
843
  );
727
844
 
728
- // Serialize result content to text
729
845
  const contentItems = result.content as Array<{ type: string; text?: string }>;
730
846
  const raw = contentItems
731
847
  .map((c) => (c.type === "text" ? c.text ?? "" : JSON.stringify(c)))
@@ -800,20 +916,26 @@ export default function(pi: ExtensionAPI) {
800
916
  const servers = readConfigs();
801
917
  const enabledServers = servers.filter((server) => server.enabled);
802
918
  if (servers.length > 0) {
803
- ctx.ui.notify(`MCP client ready — ${enabledServers.length}/${servers.length} server(s) enabled`, "info");
919
+ ctx.ui.notify(`MCP client ready — ${enabledServers.length}/${servers.length} server(s) enabled, warming up…`, "info");
804
920
  }
805
921
  if (enabledServers.length === 0) return;
806
922
 
807
- void warmupEnabledServers().then((results) => {
923
+ try {
924
+ const warmupTimeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error("warmup timed out after 30s")), 30_000));
925
+ const results = await Promise.race([warmupEnabledServers(), warmupTimeout]);
926
+ const succeeded = results.filter((entry) => entry.status === "connected");
808
927
  const failed = results.filter((entry) => entry.status === "error");
928
+ if (succeeded.length > 0) {
929
+ ctx.ui.notify(`MCP autoconnect complete — ${succeeded.length} server(s) connected`, "success");
930
+ }
809
931
  if (failed.length > 0) {
810
932
  const failureSummary = failed.map((entry) => `${entry.name}: ${entry.error}`).join("; ");
811
933
  ctx.ui.notify(`MCP autoconnect partial failure — ${failureSummary}`, "warning");
812
934
  }
813
- }).catch((error) => {
935
+ } catch (error) {
814
936
  const message = error instanceof Error ? error.message : String(error);
815
937
  ctx.ui.notify(`MCP autoconnect failed: ${message}`, "warning");
816
- });
938
+ }
817
939
  });
818
940
 
819
941
  pi.on("session_shutdown", async () => {
@@ -823,6 +945,6 @@ export default function(pi: ExtensionAPI) {
823
945
 
824
946
  pi.on("session_switch", async () => {
825
947
  await reloadMcpState();
826
- void warmupEnabledServers();
948
+ await warmupEnabledServers();
827
949
  });
828
950
  }