@oh-my-pi/pi-coding-agent 1.340.0 → 2.0.1337

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 (153) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +5 -3
  5. package/src/cli/args.ts +13 -6
  6. package/src/cli/file-processor.ts +3 -3
  7. package/src/cli/list-models.ts +2 -2
  8. package/src/cli/plugin-cli.ts +1 -1
  9. package/src/cli/session-picker.ts +2 -2
  10. package/src/cli.ts +1 -1
  11. package/src/config.ts +3 -3
  12. package/src/core/agent-session.ts +189 -29
  13. package/src/core/bash-executor.ts +50 -10
  14. package/src/core/compaction/branch-summarization.ts +5 -5
  15. package/src/core/compaction/compaction.ts +3 -3
  16. package/src/core/compaction/index.ts +3 -3
  17. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  18. package/src/core/custom-commands/index.ts +15 -0
  19. package/src/core/custom-commands/loader.ts +232 -0
  20. package/src/core/custom-commands/types.ts +112 -0
  21. package/src/core/custom-tools/index.ts +3 -3
  22. package/src/core/custom-tools/loader.ts +10 -8
  23. package/src/core/custom-tools/types.ts +11 -6
  24. package/src/core/custom-tools/wrapper.ts +2 -1
  25. package/src/core/exec.ts +22 -12
  26. package/src/core/export-html/index.ts +5 -5
  27. package/src/core/file-mentions.ts +54 -0
  28. package/src/core/hooks/index.ts +5 -5
  29. package/src/core/hooks/loader.ts +21 -16
  30. package/src/core/hooks/runner.ts +6 -6
  31. package/src/core/hooks/tool-wrapper.ts +2 -2
  32. package/src/core/hooks/types.ts +12 -15
  33. package/src/core/index.ts +6 -6
  34. package/src/core/logger.ts +112 -0
  35. package/src/core/mcp/client.ts +3 -3
  36. package/src/core/mcp/config.ts +1 -1
  37. package/src/core/mcp/index.ts +12 -12
  38. package/src/core/mcp/loader.ts +2 -2
  39. package/src/core/mcp/manager.ts +6 -6
  40. package/src/core/mcp/tool-bridge.ts +3 -3
  41. package/src/core/mcp/transports/http.ts +1 -1
  42. package/src/core/mcp/transports/index.ts +2 -2
  43. package/src/core/mcp/transports/stdio.ts +1 -1
  44. package/src/core/messages.ts +22 -0
  45. package/src/core/model-registry.ts +2 -2
  46. package/src/core/model-resolver.ts +103 -2
  47. package/src/core/plugins/doctor.ts +1 -1
  48. package/src/core/plugins/index.ts +6 -6
  49. package/src/core/plugins/installer.ts +4 -4
  50. package/src/core/plugins/loader.ts +4 -9
  51. package/src/core/plugins/manager.ts +5 -5
  52. package/src/core/plugins/paths.ts +3 -3
  53. package/src/core/sdk.ts +127 -52
  54. package/src/core/session-manager.ts +123 -20
  55. package/src/core/settings-manager.ts +106 -22
  56. package/src/core/skills.ts +5 -5
  57. package/src/core/slash-commands.ts +60 -45
  58. package/src/core/system-prompt.ts +6 -6
  59. package/src/core/title-generator.ts +94 -0
  60. package/src/core/tools/bash.ts +33 -157
  61. package/src/core/tools/context.ts +2 -2
  62. package/src/core/tools/edit-diff.ts +5 -5
  63. package/src/core/tools/edit.ts +60 -9
  64. package/src/core/tools/exa/company.ts +3 -3
  65. package/src/core/tools/exa/index.ts +16 -17
  66. package/src/core/tools/exa/linkedin.ts +3 -3
  67. package/src/core/tools/exa/mcp-client.ts +9 -9
  68. package/src/core/tools/exa/render.ts +5 -5
  69. package/src/core/tools/exa/researcher.ts +3 -3
  70. package/src/core/tools/exa/search.ts +6 -5
  71. package/src/core/tools/exa/types.ts +5 -6
  72. package/src/core/tools/exa/websets.ts +3 -3
  73. package/src/core/tools/find.ts +3 -3
  74. package/src/core/tools/grep.ts +6 -5
  75. package/src/core/tools/index.ts +114 -40
  76. package/src/core/tools/ls.ts +4 -4
  77. package/src/core/tools/lsp/client.ts +204 -108
  78. package/src/core/tools/lsp/config.ts +709 -35
  79. package/src/core/tools/lsp/edits.ts +2 -2
  80. package/src/core/tools/lsp/index.ts +432 -30
  81. package/src/core/tools/lsp/render.ts +2 -2
  82. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  83. package/src/core/tools/lsp/types.ts +5 -0
  84. package/src/core/tools/lsp/utils.ts +1 -1
  85. package/src/core/tools/notebook.ts +1 -1
  86. package/src/core/tools/output.ts +175 -0
  87. package/src/core/tools/read.ts +7 -7
  88. package/src/core/tools/renderers.ts +92 -13
  89. package/src/core/tools/review.ts +268 -0
  90. package/src/core/tools/task/agents.ts +1 -1
  91. package/src/core/tools/task/bundled-agents/explore.md +1 -1
  92. package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
  93. package/src/core/tools/task/discovery.ts +2 -2
  94. package/src/core/tools/task/executor.ts +145 -28
  95. package/src/core/tools/task/index.ts +78 -30
  96. package/src/core/tools/task/model-resolver.ts +72 -13
  97. package/src/core/tools/task/parallel.ts +1 -1
  98. package/src/core/tools/task/render.ts +219 -30
  99. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  100. package/src/core/tools/task/types.ts +36 -2
  101. package/src/core/tools/web-fetch.ts +5 -3
  102. package/src/core/tools/web-search/auth.ts +1 -1
  103. package/src/core/tools/web-search/index.ts +17 -15
  104. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  105. package/src/core/tools/web-search/providers/exa.ts +3 -5
  106. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  107. package/src/core/tools/web-search/render.ts +3 -3
  108. package/src/core/tools/write.ts +70 -7
  109. package/src/index.ts +33 -17
  110. package/src/main.ts +60 -34
  111. package/src/migrations.ts +3 -3
  112. package/src/modes/index.ts +5 -5
  113. package/src/modes/interactive/components/armin.ts +1 -1
  114. package/src/modes/interactive/components/assistant-message.ts +1 -1
  115. package/src/modes/interactive/components/bash-execution.ts +4 -4
  116. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  117. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  118. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  119. package/src/modes/interactive/components/diff.ts +1 -1
  120. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  121. package/src/modes/interactive/components/footer.ts +5 -5
  122. package/src/modes/interactive/components/hook-editor.ts +2 -2
  123. package/src/modes/interactive/components/hook-input.ts +2 -2
  124. package/src/modes/interactive/components/hook-message.ts +3 -3
  125. package/src/modes/interactive/components/hook-selector.ts +2 -2
  126. package/src/modes/interactive/components/model-selector.ts +341 -41
  127. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  128. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  129. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  130. package/src/modes/interactive/components/session-selector.ts +24 -11
  131. package/src/modes/interactive/components/settings-defs.ts +51 -3
  132. package/src/modes/interactive/components/settings-selector.ts +13 -16
  133. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  134. package/src/modes/interactive/components/theme-selector.ts +2 -2
  135. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  136. package/src/modes/interactive/components/tool-execution.ts +44 -8
  137. package/src/modes/interactive/components/tree-selector.ts +5 -5
  138. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  139. package/src/modes/interactive/components/user-message.ts +1 -1
  140. package/src/modes/interactive/components/welcome.ts +42 -5
  141. package/src/modes/interactive/interactive-mode.ts +169 -48
  142. package/src/modes/interactive/theme/theme.ts +8 -7
  143. package/src/modes/print-mode.ts +4 -3
  144. package/src/modes/rpc/rpc-client.ts +4 -4
  145. package/src/modes/rpc/rpc-mode.ts +21 -11
  146. package/src/modes/rpc/rpc-types.ts +3 -3
  147. package/src/utils/changelog.ts +2 -2
  148. package/src/utils/clipboard.ts +1 -1
  149. package/src/utils/shell-snapshot.ts +218 -0
  150. package/src/utils/shell.ts +93 -13
  151. package/src/utils/tools-manager.ts +1 -1
  152. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  153. package/src/core/tools/exa/logger.ts +0 -56
@@ -11,10 +11,10 @@ import {
11
11
  Text,
12
12
  truncateToWidth,
13
13
  } from "@oh-my-pi/pi-tui";
14
- import type { SessionInfo } from "../../../core/session-manager.js";
15
- import { fuzzyFilter } from "../../../utils/fuzzy.js";
16
- import { theme } from "../theme/theme.js";
17
- import { DynamicBorder } from "./dynamic-border.js";
14
+ import type { SessionInfo } from "../../../core/session-manager";
15
+ import { fuzzyFilter } from "../../../utils/fuzzy";
16
+ import { theme } from "../theme/theme";
17
+ import { DynamicBorder } from "./dynamic-border";
18
18
 
19
19
  /**
20
20
  * Custom session list component with multi-line items and search
@@ -90,7 +90,7 @@ class SessionList implements Component {
90
90
  );
91
91
  const endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);
92
92
 
93
- // Render visible sessions (2 lines per session + blank line)
93
+ // Render visible sessions (2-3 lines per session + blank line)
94
94
  for (let i = startIndex; i < endIndex; i++) {
95
95
  const session = this.filteredSessions[i];
96
96
  const isSelected = i === this.selectedIndex;
@@ -98,19 +98,32 @@ class SessionList implements Component {
98
98
  // Normalize first message to single line
99
99
  const normalizedMessage = session.firstMessage.replace(/\n/g, " ").trim();
100
100
 
101
- // First line: cursor + message (truncate to visible width)
101
+ // First line: cursor + title (or first message if no title)
102
102
  const cursor = isSelected ? theme.fg("accent", "› ") : " ";
103
- const maxMsgWidth = width - 2; // Account for cursor (2 visible chars)
104
- const truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, "...");
105
- const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
103
+ const maxWidth = width - 2; // Account for cursor (2 visible chars)
104
+
105
+ if (session.title) {
106
+ // Has title: show title on first line, dimmed first message on second line
107
+ const truncatedTitle = truncateToWidth(session.title, maxWidth, "...");
108
+ const titleLine = cursor + (isSelected ? theme.bold(truncatedTitle) : truncatedTitle);
109
+ lines.push(titleLine);
110
+
111
+ // Second line: dimmed first message preview
112
+ const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth, "...");
113
+ lines.push(` ${theme.fg("dim", truncatedPreview)}`);
114
+ } else {
115
+ // No title: show first message as main line
116
+ const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth, "...");
117
+ const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
118
+ lines.push(messageLine);
119
+ }
106
120
 
107
- // Second line: metadata (dimmed) - also truncate for safety
121
+ // Metadata line: date + message count
108
122
  const modified = formatDate(session.modified);
109
123
  const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
110
124
  const metadata = ` ${modified} · ${msgCount}`;
111
125
  const metadataLine = theme.fg("dim", truncateToWidth(metadata, width, ""));
112
126
 
113
- lines.push(messageLine);
114
127
  lines.push(metadataLine);
115
128
  lines.push(""); // Blank line between sessions
116
129
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
12
12
  import { getCapabilities } from "@oh-my-pi/pi-tui";
13
- import type { SettingsManager } from "../../../core/settings-manager.js";
13
+ import type { SettingsManager } from "../../../core/settings-manager";
14
14
 
15
15
  // Setting value types
16
16
  export type SettingValue = boolean | string;
@@ -20,7 +20,7 @@ interface BaseSettingDef {
20
20
  id: string;
21
21
  label: string;
22
22
  description: string;
23
- tab: "config" | "exa";
23
+ tab: string;
24
24
  }
25
25
 
26
26
  // Boolean toggle setting
@@ -99,6 +99,16 @@ export const SETTINGS_DEFS: SettingDef[] = [
99
99
  get: (sm) => sm.getQueueMode(),
100
100
  set: (sm, v) => sm.setQueueMode(v as "all" | "one-at-a-time"), // Also handled in session
101
101
  },
102
+ {
103
+ id: "interruptMode",
104
+ tab: "config",
105
+ type: "enum",
106
+ label: "Interrupt mode",
107
+ description: "When to process queued messages: immediately (interrupt tools) or wait for turn to complete",
108
+ values: ["immediate", "wait"],
109
+ get: (sm) => sm.getInterruptMode(),
110
+ set: (sm, v) => sm.setInterruptMode(v as "immediate" | "wait"), // Also handled in session
111
+ },
102
112
  {
103
113
  id: "hideThinking",
104
114
  tab: "config",
@@ -135,6 +145,15 @@ export const SETTINGS_DEFS: SettingDef[] = [
135
145
  get: (sm) => sm.getMCPProjectConfigEnabled(),
136
146
  set: (sm, v) => sm.setMCPProjectConfigEnabled(v),
137
147
  },
148
+ {
149
+ id: "editFuzzyMatch",
150
+ tab: "config",
151
+ type: "boolean",
152
+ label: "Edit fuzzy match",
153
+ description: "Accept high-confidence fuzzy matches for whitespace/indentation differences",
154
+ get: (sm) => sm.getEditFuzzyMatch(),
155
+ set: (sm, v) => sm.setEditFuzzyMatch(v),
156
+ },
138
157
  {
139
158
  id: "thinkingLevel",
140
159
  tab: "config",
@@ -161,6 +180,35 @@ export const SETTINGS_DEFS: SettingDef[] = [
161
180
  getOptions: () => [], // Filled dynamically from context
162
181
  },
163
182
 
183
+ // LSP tab
184
+ {
185
+ id: "lspFormatOnWrite",
186
+ tab: "lsp",
187
+ type: "boolean",
188
+ label: "Format on write",
189
+ description: "Automatically format code files using LSP after writing",
190
+ get: (sm) => sm.getLspFormatOnWrite(),
191
+ set: (sm, v) => sm.setLspFormatOnWrite(v),
192
+ },
193
+ {
194
+ id: "lspDiagnosticsOnWrite",
195
+ tab: "lsp",
196
+ type: "boolean",
197
+ label: "Diagnostics on write",
198
+ description: "Return LSP diagnostics (errors/warnings) after writing code files",
199
+ get: (sm) => sm.getLspDiagnosticsOnWrite(),
200
+ set: (sm, v) => sm.setLspDiagnosticsOnWrite(v),
201
+ },
202
+ {
203
+ id: "lspDiagnosticsOnEdit",
204
+ tab: "lsp",
205
+ type: "boolean",
206
+ label: "Diagnostics on edit",
207
+ description: "Return LSP diagnostics (errors/warnings) after editing code files",
208
+ get: (sm) => sm.getLspDiagnosticsOnEdit(),
209
+ set: (sm, v) => sm.setLspDiagnosticsOnEdit(v),
210
+ },
211
+
164
212
  // Exa tab
165
213
  {
166
214
  id: "exaEnabled",
@@ -219,7 +267,7 @@ export const SETTINGS_DEFS: SettingDef[] = [
219
267
  ];
220
268
 
221
269
  /** Get settings for a specific tab */
222
- export function getSettingsForTab(tab: "config" | "exa"): SettingDef[] {
270
+ export function getSettingsForTab(tab: string): SettingDef[] {
223
271
  return SETTINGS_DEFS.filter((def) => def.tab === tab);
224
272
  }
225
273
 
@@ -16,11 +16,11 @@ import {
16
16
  type TabBarTheme,
17
17
  Text,
18
18
  } from "@oh-my-pi/pi-tui";
19
- import type { SettingsManager } from "../../../core/settings-manager.js";
20
- import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js";
21
- import { DynamicBorder } from "./dynamic-border.js";
22
- import { PluginSettingsComponent } from "./plugin-settings.js";
23
- import { getSettingsForTab, type SettingDef } from "./settings-defs.js";
19
+ import type { SettingsManager } from "../../../core/settings-manager";
20
+ import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme";
21
+ import { DynamicBorder } from "./dynamic-border";
22
+ import { PluginSettingsComponent } from "./plugin-settings";
23
+ import { getSettingsForTab, type SettingDef } from "./settings-defs";
24
24
 
25
25
  function getTabBarTheme(): TabBarTheme {
26
26
  return {
@@ -93,10 +93,11 @@ class SelectSubmenu extends Container {
93
93
  }
94
94
  }
95
95
 
96
- type TabId = "config" | "exa" | "plugins";
96
+ type TabId = string;
97
97
 
98
98
  const SETTINGS_TABS: Tab[] = [
99
99
  { id: "config", label: "Config" },
100
+ { id: "lsp", label: "LSP" },
100
101
  { id: "exa", label: "Exa" },
101
102
  { id: "plugins", label: "Plugins" },
102
103
  ];
@@ -189,14 +190,10 @@ export class SettingsSelectorComponent extends Container {
189
190
  const bottomBorder = this.children[this.children.length - 1];
190
191
  this.removeChild(bottomBorder);
191
192
 
192
- switch (tabId) {
193
- case "config":
194
- case "exa":
195
- this.showSettingsTab(tabId);
196
- break;
197
- case "plugins":
198
- this.showPluginsTab();
199
- break;
193
+ if (tabId === "plugins") {
194
+ this.showPluginsTab();
195
+ } else {
196
+ this.showSettingsTab(tabId);
200
197
  }
201
198
 
202
199
  // Re-add bottom border
@@ -301,9 +298,9 @@ export class SettingsSelectorComponent extends Container {
301
298
  }
302
299
 
303
300
  /**
304
- * Show a settings tab (config or exa) using definitions.
301
+ * Show a settings tab using definitions.
305
302
  */
306
- private showSettingsTab(tabId: "config" | "exa"): void {
303
+ private showSettingsTab(tabId: string): void {
307
304
  const defs = getSettingsForTab(tabId);
308
305
  const items: SettingItem[] = [];
309
306
 
@@ -1,6 +1,6 @@
1
1
  import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
2
- import { getSelectListTheme } from "../theme/theme.js";
3
- import { DynamicBorder } from "./dynamic-border.js";
2
+ import { getSelectListTheme } from "../theme/theme";
3
+ import { DynamicBorder } from "./dynamic-border";
4
4
 
5
5
  /**
6
6
  * Component that renders a show images selector with borders
@@ -1,6 +1,6 @@
1
1
  import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
2
- import { getAvailableThemes, getSelectListTheme } from "../theme/theme.js";
3
- import { DynamicBorder } from "./dynamic-border.js";
2
+ import { getAvailableThemes, getSelectListTheme } from "../theme/theme";
3
+ import { DynamicBorder } from "./dynamic-border";
4
4
 
5
5
  /**
6
6
  * Component that renders a theme selector
@@ -1,7 +1,7 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
3
- import { getSelectListTheme } from "../theme/theme.js";
4
- import { DynamicBorder } from "./dynamic-border.js";
3
+ import { getSelectListTheme } from "../theme/theme";
4
+ import { DynamicBorder } from "./dynamic-border";
5
5
 
6
6
  const LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {
7
7
  off: "No reasoning",
@@ -11,14 +11,14 @@ import {
11
11
  type TUI,
12
12
  } from "@oh-my-pi/pi-tui";
13
13
  import stripAnsi from "strip-ansi";
14
- import type { CustomTool } from "../../../core/custom-tools/types.js";
15
- import { computeEditDiff, type EditDiffError, type EditDiffResult } from "../../../core/tools/edit-diff.js";
16
- import { toolRenderers } from "../../../core/tools/renderers.js";
17
- import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate.js";
18
- import { sanitizeBinaryOutput } from "../../../utils/shell.js";
19
- import { getLanguageFromPath, highlightCode, theme } from "../theme/theme.js";
20
- import { renderDiff } from "./diff.js";
21
- import { truncateToVisualLines } from "./visual-truncate.js";
14
+ import type { CustomTool } from "../../../core/custom-tools/types";
15
+ import { computeEditDiff, type EditDiffError, type EditDiffResult } from "../../../core/tools/edit-diff";
16
+ import { toolRenderers } from "../../../core/tools/renderers";
17
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate";
18
+ import { sanitizeBinaryOutput } from "../../../utils/shell";
19
+ import { getLanguageFromPath, highlightCode, theme } from "../theme/theme";
20
+ import { renderDiff } from "./diff";
21
+ import { truncateToVisualLines } from "./visual-truncate";
22
22
 
23
23
  // Preview line limit for bash when not expanded
24
24
  const BASH_PREVIEW_LINES = 5;
@@ -501,6 +501,24 @@ export class ToolExecutionComponent extends Container {
501
501
  text += theme.fg("toolOutput", `\n... (${remaining} more lines, ${totalLines} total)`);
502
502
  }
503
503
  }
504
+
505
+ // Show LSP diagnostics if available
506
+ if (this.result?.details?.diagnostics?.available) {
507
+ const diag = this.result.details.diagnostics;
508
+ if (diag.diagnostics.length > 0) {
509
+ const icon = diag.hasErrors ? theme.fg("error", "●") : theme.fg("warning", "●");
510
+ text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
511
+ const maxDiags = this.expanded ? diag.diagnostics.length : 5;
512
+ const displayDiags = diag.diagnostics.slice(0, maxDiags);
513
+ for (const d of displayDiags) {
514
+ const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
515
+ text += `\n ${theme.fg(color, d)}`;
516
+ }
517
+ if (diag.diagnostics.length > maxDiags) {
518
+ text += theme.fg("dim", `\n ... (${diag.diagnostics.length - maxDiags} more)`);
519
+ }
520
+ }
521
+ }
504
522
  } else if (this.toolName === "edit") {
505
523
  const rawPath = this.args?.file_path || this.args?.path || "";
506
524
  const path = shortenPath(rawPath);
@@ -532,6 +550,24 @@ export class ToolExecutionComponent extends Container {
532
550
  text += `\n\n${renderDiff(this.editDiffPreview.diff, { filePath: rawPath })}`;
533
551
  }
534
552
  }
553
+
554
+ // Show LSP diagnostics if available
555
+ if (this.result?.details?.diagnostics?.available) {
556
+ const diag = this.result.details.diagnostics;
557
+ if (diag.diagnostics.length > 0) {
558
+ const icon = diag.hasErrors ? theme.fg("error", "●") : theme.fg("warning", "●");
559
+ text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
560
+ const maxDiags = this.expanded ? diag.diagnostics.length : 5;
561
+ const displayDiags = diag.diagnostics.slice(0, maxDiags);
562
+ for (const d of displayDiags) {
563
+ const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
564
+ text += `\n ${theme.fg(color, d)}`;
565
+ }
566
+ if (diag.diagnostics.length > maxDiags) {
567
+ text += theme.fg("dim", `\n ... (${diag.diagnostics.length - maxDiags} more)`);
568
+ }
569
+ }
570
+ }
535
571
  } else if (this.toolName === "ls") {
536
572
  const path = shortenPath(this.args?.path || ".");
537
573
  const limit = this.args?.limit;
@@ -17,9 +17,9 @@ import {
17
17
  TruncatedText,
18
18
  truncateToWidth,
19
19
  } from "@oh-my-pi/pi-tui";
20
- import type { SessionTreeNode } from "../../../core/session-manager.js";
21
- import { theme } from "../theme/theme.js";
22
- import { DynamicBorder } from "./dynamic-border.js";
20
+ import type { SessionTreeNode } from "../../../core/session-manager";
21
+ import { theme } from "../theme/theme";
22
+ import { DynamicBorder } from "./dynamic-border";
23
23
 
24
24
  /** Gutter info: position (displayIndent where connector was) and whether to show │ */
25
25
  interface GutterInfo {
@@ -356,7 +356,7 @@ class TreeList implements Component {
356
356
  parts.push("branch summary", entry.summary);
357
357
  break;
358
358
  case "model_change":
359
- parts.push("model", entry.modelId);
359
+ parts.push("model", entry.model);
360
360
  break;
361
361
  case "thinking_level_change":
362
362
  parts.push("thinking", entry.thinkingLevel);
@@ -558,7 +558,7 @@ class TreeList implements Component {
558
558
  result = theme.fg("warning", `[branch summary]: `) + normalize(entry.summary);
559
559
  break;
560
560
  case "model_change":
561
- result = theme.fg("dim", `[model: ${entry.modelId}]`);
561
+ result = theme.fg("dim", `[model: ${entry.model}]`);
562
562
  break;
563
563
  case "thinking_level_change":
564
564
  result = theme.fg("dim", `[thinking: ${entry.thinkingLevel}]`);
@@ -10,8 +10,8 @@ import {
10
10
  Text,
11
11
  truncateToWidth,
12
12
  } from "@oh-my-pi/pi-tui";
13
- import { theme } from "../theme/theme.js";
14
- import { DynamicBorder } from "./dynamic-border.js";
13
+ import { theme } from "../theme/theme";
14
+ import { DynamicBorder } from "./dynamic-border";
15
15
 
16
16
  interface UserMessageItem {
17
17
  id: string; // Entry ID in the session
@@ -1,5 +1,5 @@
1
1
  import { Container, Markdown, Spacer } from "@oh-my-pi/pi-tui";
2
- import { getMarkdownTheme, theme } from "../theme/theme.js";
2
+ import { getMarkdownTheme, theme } from "../theme/theme";
3
3
 
4
4
  /**
5
5
  * Component that renders a user message
@@ -1,12 +1,18 @@
1
1
  import { type Component, visibleWidth } from "@oh-my-pi/pi-tui";
2
- import { APP_NAME } from "../../../config.js";
3
- import { theme } from "../theme/theme.js";
2
+ import { APP_NAME } from "../../../config";
3
+ import { theme } from "../theme/theme";
4
4
 
5
5
  export interface RecentSession {
6
6
  name: string;
7
7
  timeAgo: string;
8
8
  }
9
9
 
10
+ export interface LspServerInfo {
11
+ name: string;
12
+ status: "ready" | "error" | "connecting";
13
+ fileTypes: string[];
14
+ }
15
+
10
16
  /**
11
17
  * Premium welcome screen with block-based Pi logo and two-column layout.
12
18
  */
@@ -15,12 +21,20 @@ export class WelcomeComponent implements Component {
15
21
  private modelName: string;
16
22
  private providerName: string;
17
23
  private recentSessions: RecentSession[];
18
-
19
- constructor(version: string, modelName: string, providerName: string, recentSessions: RecentSession[] = []) {
24
+ private lspServers: LspServerInfo[];
25
+
26
+ constructor(
27
+ version: string,
28
+ modelName: string,
29
+ providerName: string,
30
+ recentSessions: RecentSession[] = [],
31
+ lspServers: LspServerInfo[] = [],
32
+ ) {
20
33
  this.version = version;
21
34
  this.modelName = modelName;
22
35
  this.providerName = providerName;
23
36
  this.recentSessions = recentSessions;
37
+ this.lspServers = lspServers;
24
38
  }
25
39
 
26
40
  invalidate(): void {}
@@ -34,6 +48,10 @@ export class WelcomeComponent implements Component {
34
48
  this.recentSessions = sessions;
35
49
  }
36
50
 
51
+ setLspServers(servers: LspServerInfo[]): void {
52
+ this.lspServers = servers;
53
+ }
54
+
37
55
  render(termWidth: number): string[] {
38
56
  // Box dimensions - responsive with min/max
39
57
  const minWidth = 80;
@@ -76,13 +94,32 @@ export class WelcomeComponent implements Component {
76
94
  }
77
95
  }
78
96
 
97
+ // LSP servers content
98
+ const lspLines: string[] = [];
99
+ if (this.lspServers.length === 0) {
100
+ lspLines.push(` ${theme.fg("dim", "No LSP servers")}`);
101
+ } else {
102
+ for (const server of this.lspServers) {
103
+ const icon =
104
+ server.status === "ready"
105
+ ? theme.fg("success", "●")
106
+ : server.status === "connecting"
107
+ ? theme.fg("warning", "○")
108
+ : theme.fg("error", "●");
109
+ const exts = server.fileTypes.slice(0, 3).join(" ");
110
+ lspLines.push(` ${icon} ${theme.fg("muted", server.name)} ${theme.fg("dim", exts)}`);
111
+ }
112
+ }
113
+
79
114
  // Right column
80
115
  const rightLines = [
81
116
  ` ${theme.bold(theme.fg("accent", "Tips"))}`,
82
117
  ` ${theme.fg("dim", "?")}${theme.fg("muted", " for keyboard shortcuts")}`,
83
118
  ` ${theme.fg("dim", "/")}${theme.fg("muted", " for commands")}`,
84
119
  ` ${theme.fg("dim", "!")}${theme.fg("muted", " to run bash")}`,
85
- ` ${theme.fg("dim", "/status")}${theme.fg("muted", " for loaded extensions")}`,
120
+ separator,
121
+ ` ${theme.bold(theme.fg("accent", "LSP Servers"))}`,
122
+ ...lspLines,
86
123
  separator,
87
124
  ` ${theme.bold(theme.fg("accent", "Recent sessions"))}`,
88
125
  ...sessionLines,