@oh-my-pi/pi-coding-agent 1.338.0 → 1.341.0

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 (42) hide show
  1. package/CHANGELOG.md +60 -1
  2. package/package.json +3 -3
  3. package/src/cli/args.ts +8 -0
  4. package/src/core/agent-session.ts +32 -14
  5. package/src/core/export-html/index.ts +48 -15
  6. package/src/core/export-html/template.html +3 -11
  7. package/src/core/mcp/client.ts +43 -16
  8. package/src/core/mcp/config.ts +152 -6
  9. package/src/core/mcp/index.ts +6 -2
  10. package/src/core/mcp/loader.ts +30 -3
  11. package/src/core/mcp/manager.ts +69 -10
  12. package/src/core/mcp/types.ts +9 -3
  13. package/src/core/model-resolver.ts +101 -0
  14. package/src/core/sdk.ts +65 -18
  15. package/src/core/session-manager.ts +117 -14
  16. package/src/core/settings-manager.ts +107 -19
  17. package/src/core/title-generator.ts +94 -0
  18. package/src/core/tools/bash.ts +1 -2
  19. package/src/core/tools/edit-diff.ts +2 -2
  20. package/src/core/tools/edit.ts +43 -5
  21. package/src/core/tools/grep.ts +3 -2
  22. package/src/core/tools/index.ts +73 -13
  23. package/src/core/tools/lsp/client.ts +45 -20
  24. package/src/core/tools/lsp/config.ts +708 -34
  25. package/src/core/tools/lsp/index.ts +423 -23
  26. package/src/core/tools/lsp/types.ts +5 -0
  27. package/src/core/tools/task/bundled-agents/explore.md +1 -1
  28. package/src/core/tools/task/bundled-agents/reviewer.md +1 -1
  29. package/src/core/tools/task/model-resolver.ts +52 -3
  30. package/src/core/tools/write.ts +67 -4
  31. package/src/index.ts +5 -0
  32. package/src/main.ts +23 -2
  33. package/src/modes/interactive/components/model-selector.ts +96 -18
  34. package/src/modes/interactive/components/session-selector.ts +20 -7
  35. package/src/modes/interactive/components/settings-defs.ts +59 -2
  36. package/src/modes/interactive/components/settings-selector.ts +8 -11
  37. package/src/modes/interactive/components/tool-execution.ts +18 -0
  38. package/src/modes/interactive/components/tree-selector.ts +2 -2
  39. package/src/modes/interactive/components/welcome.ts +40 -3
  40. package/src/modes/interactive/interactive-mode.ts +87 -10
  41. package/src/core/export-html/vendor/highlight.min.js +0 -1213
  42. package/src/core/export-html/vendor/marked.min.js +0 -6
@@ -1,13 +1,25 @@
1
1
  export { type AskToolDetails, askTool, createAskTool } from "./ask.js";
2
2
  export { type BashToolDetails, bashTool, createBashTool } from "./bash.js";
3
- export { createEditTool, editTool } from "./edit.js";
3
+ export { createEditTool, type EditToolOptions, editTool } from "./edit.js";
4
4
  // Exa MCP tools (22 tools)
5
5
  export { exaTools } from "./exa/index.js";
6
6
  export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types.js";
7
7
  export { createFindTool, type FindToolDetails, findTool } from "./find.js";
8
8
  export { createGrepTool, type GrepToolDetails, grepTool } from "./grep.js";
9
9
  export { createLsTool, type LsToolDetails, lsTool } from "./ls.js";
10
- export { createLspTool, type LspToolDetails, lspTool } from "./lsp/index.js";
10
+ export {
11
+ createLspTool,
12
+ type FileDiagnosticsResult,
13
+ type FileFormatResult,
14
+ formatFile,
15
+ getDiagnosticsForFile,
16
+ getLspStatus,
17
+ type LspServerStatus,
18
+ type LspToolDetails,
19
+ type LspWarmupResult,
20
+ lspTool,
21
+ warmupLspServers,
22
+ } from "./lsp/index.js";
11
23
  export { createNotebookTool, type NotebookToolDetails, notebookTool } from "./notebook.js";
12
24
  export { createReadTool, type ReadToolDetails, readTool } from "./read.js";
13
25
  export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index.js";
@@ -31,7 +43,7 @@ export {
31
43
  webSearchLinkedinTool,
32
44
  webSearchTool,
33
45
  } from "./web-search/index.js";
34
- export { createWriteTool, writeTool } from "./write.js";
46
+ export { createWriteTool, type WriteToolDetails, type WriteToolOptions, writeTool } from "./write.js";
35
47
 
36
48
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
37
49
  import { askTool, createAskTool } from "./ask.js";
@@ -41,7 +53,7 @@ import { createEditTool, editTool } from "./edit.js";
41
53
  import { createFindTool, findTool } from "./find.js";
42
54
  import { createGrepTool, grepTool } from "./grep.js";
43
55
  import { createLsTool, lsTool } from "./ls.js";
44
- import { createLspTool, lspTool } from "./lsp/index.js";
56
+ import { createLspTool, formatFile, getDiagnosticsForFile, lspTool } from "./lsp/index.js";
45
57
  import { createNotebookTool, notebookTool } from "./notebook.js";
46
58
  import { createReadTool, readTool } from "./read.js";
47
59
  import { createTaskTool, taskTool } from "./task/index.js";
@@ -57,16 +69,47 @@ export interface SessionContext {
57
69
  getSessionFile: () => string | null;
58
70
  }
59
71
 
72
+ /** Options for creating coding tools */
73
+ export interface CodingToolsOptions {
74
+ /** Whether to fetch LSP diagnostics after write tool writes files (default: true) */
75
+ lspDiagnosticsOnWrite?: boolean;
76
+ /** Whether to fetch LSP diagnostics after edit tool edits files (default: false) */
77
+ lspDiagnosticsOnEdit?: boolean;
78
+ /** Whether to format files using LSP after write tool writes (default: true) */
79
+ lspFormatOnWrite?: boolean;
80
+ /** Whether to accept high-confidence fuzzy matches in edit tool (default: true) */
81
+ editFuzzyMatch?: boolean;
82
+ }
83
+
60
84
  // Factory function type
61
- type ToolFactory = (cwd: string, sessionContext?: SessionContext) => Tool;
85
+ type ToolFactory = (cwd: string, sessionContext?: SessionContext, options?: CodingToolsOptions) => Tool;
62
86
 
63
87
  // Tool definitions: static tools and their factory functions
64
88
  const toolDefs: Record<string, { tool: Tool; create: ToolFactory }> = {
65
89
  ask: { tool: askTool, create: createAskTool },
66
90
  read: { tool: readTool, create: createReadTool },
67
91
  bash: { tool: bashTool, create: createBashTool },
68
- edit: { tool: editTool, create: createEditTool },
69
- write: { tool: writeTool, create: createWriteTool },
92
+ edit: {
93
+ tool: editTool,
94
+ create: (cwd, _ctx, options) => {
95
+ const enableDiagnostics = options?.lspDiagnosticsOnEdit ?? false;
96
+ return createEditTool(cwd, {
97
+ fuzzyMatch: options?.editFuzzyMatch ?? true,
98
+ getDiagnostics: enableDiagnostics ? (absolutePath) => getDiagnosticsForFile(absolutePath, cwd) : undefined,
99
+ });
100
+ },
101
+ },
102
+ write: {
103
+ tool: writeTool,
104
+ create: (cwd, _ctx, options) => {
105
+ const enableFormat = options?.lspFormatOnWrite ?? true;
106
+ const enableDiagnostics = options?.lspDiagnosticsOnWrite ?? true;
107
+ return createWriteTool(cwd, {
108
+ formatOnWrite: enableFormat ? (absolutePath) => formatFile(absolutePath, cwd) : undefined,
109
+ getDiagnostics: enableDiagnostics ? (absolutePath) => getDiagnosticsForFile(absolutePath, cwd) : undefined,
110
+ });
111
+ },
112
+ },
70
113
  grep: { tool: grepTool, create: createGrepTool },
71
114
  find: { tool: findTool, create: createFindTool },
72
115
  ls: { tool: lsTool, create: createLsTool },
@@ -116,10 +159,16 @@ export const allTools = Object.fromEntries(Object.entries(toolDefs).map(([name,
116
159
  * @param cwd - Working directory for tools
117
160
  * @param hasUI - Whether UI is available (includes ask tool if true)
118
161
  * @param sessionContext - Optional session context for tools that need it
162
+ * @param options - Options for tool configuration
119
163
  */
120
- export function createCodingTools(cwd: string, hasUI = false, sessionContext?: SessionContext): Tool[] {
164
+ export function createCodingTools(
165
+ cwd: string,
166
+ hasUI = false,
167
+ sessionContext?: SessionContext,
168
+ options?: CodingToolsOptions,
169
+ ): Tool[] {
121
170
  const names = hasUI ? [...baseCodingToolNames, ...uiToolNames] : baseCodingToolNames;
122
- return names.map((name) => toolDefs[name].create(cwd, sessionContext));
171
+ return names.map((name) => toolDefs[name].create(cwd, sessionContext, options));
123
172
  }
124
173
 
125
174
  /**
@@ -127,20 +176,31 @@ export function createCodingTools(cwd: string, hasUI = false, sessionContext?: S
127
176
  * @param cwd - Working directory for tools
128
177
  * @param hasUI - Whether UI is available (includes ask tool if true)
129
178
  * @param sessionContext - Optional session context for tools that need it
179
+ * @param options - Options for tool configuration
130
180
  */
131
- export function createReadOnlyTools(cwd: string, hasUI = false, sessionContext?: SessionContext): Tool[] {
181
+ export function createReadOnlyTools(
182
+ cwd: string,
183
+ hasUI = false,
184
+ sessionContext?: SessionContext,
185
+ options?: CodingToolsOptions,
186
+ ): Tool[] {
132
187
  const names = hasUI ? [...baseReadOnlyToolNames, ...uiToolNames] : baseReadOnlyToolNames;
133
- return names.map((name) => toolDefs[name].create(cwd, sessionContext));
188
+ return names.map((name) => toolDefs[name].create(cwd, sessionContext, options));
134
189
  }
135
190
 
136
191
  /**
137
192
  * Create all tools configured for a specific working directory.
138
193
  * @param cwd - Working directory for tools
139
194
  * @param sessionContext - Optional session context for tools that need it
195
+ * @param options - Options for tool configuration
140
196
  */
141
- export function createAllTools(cwd: string, sessionContext?: SessionContext): Record<ToolName, Tool> {
197
+ export function createAllTools(
198
+ cwd: string,
199
+ sessionContext?: SessionContext,
200
+ options?: CodingToolsOptions,
201
+ ): Record<ToolName, Tool> {
142
202
  return Object.fromEntries(
143
- Object.entries(toolDefs).map(([name, def]) => [name, def.create(cwd, sessionContext)]),
203
+ Object.entries(toolDefs).map(([name, def]) => [name, def.create(cwd, sessionContext, options)]),
144
204
  ) as Record<ToolName, Tool>;
145
205
  }
146
206
 
@@ -17,20 +17,32 @@ import { detectLanguageId, fileToUri } from "./utils.js";
17
17
 
18
18
  const clients = new Map<string, LspClient>();
19
19
 
20
- // Idle timeout: shutdown clients after 5 minutes of inactivity
21
- const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
20
+ // Idle timeout configuration (disabled by default)
21
+ let idleTimeoutMs: number | null = null;
22
+ let idleCheckInterval: Timer | null = null;
22
23
  const IDLE_CHECK_INTERVAL_MS = 60 * 1000;
23
24
 
24
- // Background task to shutdown idle clients
25
- let idleCheckInterval: Timer | null = null;
25
+ /**
26
+ * Configure the idle timeout for LSP clients.
27
+ * @param ms - Timeout in milliseconds, or null/undefined to disable
28
+ */
29
+ export function setIdleTimeout(ms: number | null | undefined): void {
30
+ idleTimeoutMs = ms ?? null;
31
+
32
+ if (idleTimeoutMs && idleTimeoutMs > 0) {
33
+ startIdleChecker();
34
+ } else {
35
+ stopIdleChecker();
36
+ }
37
+ }
26
38
 
27
39
  function startIdleChecker(): void {
28
40
  if (idleCheckInterval) return;
29
41
  idleCheckInterval = setInterval(() => {
42
+ if (!idleTimeoutMs) return;
30
43
  const now = Date.now();
31
44
  for (const [key, client] of Array.from(clients.entries())) {
32
- if (now - client.lastActivity > IDLE_TIMEOUT_MS) {
33
- console.log(`[LSP] Shutting down idle client: ${key}`);
45
+ if (now - client.lastActivity > idleTimeoutMs) {
34
46
  shutdownClient(key);
35
47
  }
36
48
  }
@@ -98,6 +110,12 @@ const CLIENT_CAPABILITIES = {
98
110
  properties: ["edit"],
99
111
  },
100
112
  },
113
+ formatting: {
114
+ dynamicRegistration: false,
115
+ },
116
+ rangeFormatting: {
117
+ dynamicRegistration: false,
118
+ },
101
119
  publishDiagnostics: {
102
120
  relatedInformation: true,
103
121
  versionSupport: false,
@@ -357,7 +375,8 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string): Prom
357
375
  }
358
376
 
359
377
  const args = config.args ?? [];
360
- const proc = Bun.spawn([config.command, ...args], {
378
+ const command = config.resolvedCommand ?? config.command;
379
+ const proc = Bun.spawn([command, ...args], {
361
380
  cwd,
362
381
  stdin: "pipe",
363
382
  stdout: "pipe",
@@ -379,16 +398,9 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string): Prom
379
398
  };
380
399
  clients.set(key, client);
381
400
 
382
- // Start idle checker if not already running
383
- startIdleChecker();
384
-
385
401
  // Register crash recovery - remove client on process exit
386
402
  proc.exited.then(() => {
387
- console.log(`[LSP] Process exited: ${key}`);
388
403
  clients.delete(key);
389
- if (clients.size === 0) {
390
- stopIdleChecker();
391
- }
392
404
  });
393
405
 
394
406
  // Start background message reader
@@ -497,10 +509,6 @@ export function shutdownClient(key: string): void {
497
509
  // Kill process
498
510
  client.process.kill();
499
511
  clients.delete(key);
500
-
501
- if (clients.size === 0) {
502
- stopIdleChecker();
503
- }
504
512
  }
505
513
 
506
514
  // =============================================================================
@@ -570,8 +578,6 @@ export async function sendNotification(client: LspClient, method: string, params
570
578
  * Shutdown all LSP clients.
571
579
  */
572
580
  export function shutdownAll(): void {
573
- stopIdleChecker();
574
-
575
581
  for (const client of Array.from(clients.values())) {
576
582
  // Reject all pending requests
577
583
  for (const pending of Array.from(client.pendingRequests.values())) {
@@ -587,6 +593,25 @@ export function shutdownAll(): void {
587
593
  clients.clear();
588
594
  }
589
595
 
596
+ /** Status of an LSP server */
597
+ export interface LspServerStatus {
598
+ name: string;
599
+ status: "connecting" | "ready" | "error";
600
+ fileTypes: string[];
601
+ error?: string;
602
+ }
603
+
604
+ /**
605
+ * Get status of all active LSP clients.
606
+ */
607
+ export function getActiveClients(): LspServerStatus[] {
608
+ return Array.from(clients.values()).map((client) => ({
609
+ name: client.config.command,
610
+ status: "ready" as const,
611
+ fileTypes: client.config.fileTypes,
612
+ }));
613
+ }
614
+
590
615
  // =============================================================================
591
616
  // Process Cleanup
592
617
  // =============================================================================