@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.67

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 (93) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/docs/sdk.md +1 -1
  3. package/package.json +5 -5
  4. package/scripts/generate-template.ts +6 -6
  5. package/src/cli/args.ts +3 -0
  6. package/src/core/agent-session.ts +39 -0
  7. package/src/core/bash-executor.ts +3 -3
  8. package/src/core/cursor/exec-bridge.ts +95 -88
  9. package/src/core/custom-commands/bundled/review/index.ts +142 -145
  10. package/src/core/custom-commands/bundled/wt/index.ts +68 -66
  11. package/src/core/custom-commands/loader.ts +4 -6
  12. package/src/core/custom-tools/index.ts +2 -2
  13. package/src/core/custom-tools/loader.ts +66 -61
  14. package/src/core/custom-tools/types.ts +4 -4
  15. package/src/core/custom-tools/wrapper.ts +61 -25
  16. package/src/core/event-bus.ts +19 -47
  17. package/src/core/extensions/index.ts +8 -4
  18. package/src/core/extensions/loader.ts +160 -120
  19. package/src/core/extensions/types.ts +4 -4
  20. package/src/core/extensions/wrapper.ts +149 -100
  21. package/src/core/hooks/index.ts +1 -1
  22. package/src/core/hooks/tool-wrapper.ts +96 -70
  23. package/src/core/hooks/types.ts +1 -2
  24. package/src/core/index.ts +1 -0
  25. package/src/core/mcp/index.ts +6 -2
  26. package/src/core/mcp/json-rpc.ts +88 -0
  27. package/src/core/mcp/loader.ts +22 -4
  28. package/src/core/mcp/manager.ts +202 -48
  29. package/src/core/mcp/tool-bridge.ts +143 -55
  30. package/src/core/mcp/tool-cache.ts +122 -0
  31. package/src/core/python-executor.ts +3 -9
  32. package/src/core/sdk.ts +33 -32
  33. package/src/core/session-manager.ts +30 -0
  34. package/src/core/settings-manager.ts +54 -1
  35. package/src/core/ssh/ssh-executor.ts +6 -84
  36. package/src/core/streaming-output.ts +107 -53
  37. package/src/core/tools/ask.ts +92 -93
  38. package/src/core/tools/bash.ts +103 -94
  39. package/src/core/tools/calculator.ts +41 -26
  40. package/src/core/tools/complete.ts +76 -66
  41. package/src/core/tools/context.ts +22 -24
  42. package/src/core/tools/exa/index.ts +1 -1
  43. package/src/core/tools/exa/mcp-client.ts +56 -101
  44. package/src/core/tools/find.ts +250 -253
  45. package/src/core/tools/git.ts +39 -33
  46. package/src/core/tools/grep.ts +440 -427
  47. package/src/core/tools/index.ts +63 -61
  48. package/src/core/tools/ls.ts +119 -114
  49. package/src/core/tools/lsp/clients/biome-client.ts +5 -7
  50. package/src/core/tools/lsp/clients/index.ts +4 -4
  51. package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
  52. package/src/core/tools/lsp/config.ts +2 -2
  53. package/src/core/tools/lsp/index.ts +604 -578
  54. package/src/core/tools/notebook.ts +121 -119
  55. package/src/core/tools/output.ts +163 -147
  56. package/src/core/tools/patch/applicator.ts +1100 -0
  57. package/src/core/tools/patch/diff.ts +362 -0
  58. package/src/core/tools/patch/fuzzy.ts +647 -0
  59. package/src/core/tools/patch/index.ts +430 -0
  60. package/src/core/tools/patch/normalize.ts +220 -0
  61. package/src/core/tools/patch/normative.ts +73 -0
  62. package/src/core/tools/patch/parser.ts +528 -0
  63. package/src/core/tools/patch/shared.ts +257 -0
  64. package/src/core/tools/patch/types.ts +244 -0
  65. package/src/core/tools/python.ts +139 -136
  66. package/src/core/tools/read.ts +239 -216
  67. package/src/core/tools/render-utils.ts +196 -77
  68. package/src/core/tools/renderers.ts +6 -2
  69. package/src/core/tools/ssh.ts +99 -80
  70. package/src/core/tools/task/executor.ts +11 -7
  71. package/src/core/tools/task/index.ts +352 -343
  72. package/src/core/tools/task/worker.ts +13 -23
  73. package/src/core/tools/todo-write.ts +74 -59
  74. package/src/core/tools/web-fetch.ts +54 -47
  75. package/src/core/tools/web-search/index.ts +27 -16
  76. package/src/core/tools/write.ts +108 -47
  77. package/src/core/ttsr.ts +106 -152
  78. package/src/core/voice.ts +49 -39
  79. package/src/index.ts +16 -12
  80. package/src/lib/worktree/index.ts +1 -9
  81. package/src/modes/interactive/components/diff.ts +15 -8
  82. package/src/modes/interactive/components/settings-defs.ts +42 -0
  83. package/src/modes/interactive/components/tool-execution.ts +46 -8
  84. package/src/modes/interactive/controllers/event-controller.ts +6 -19
  85. package/src/modes/interactive/controllers/input-controller.ts +1 -1
  86. package/src/modes/interactive/utils/ui-helpers.ts +5 -1
  87. package/src/modes/rpc/rpc-mode.ts +99 -81
  88. package/src/prompts/tools/patch.md +76 -0
  89. package/src/prompts/tools/read.md +1 -1
  90. package/src/prompts/tools/{edit.md → replace.md} +1 -0
  91. package/src/utils/shell.ts +0 -40
  92. package/src/core/tools/edit-diff.ts +0 -574
  93. package/src/core/tools/edit.ts +0 -345
@@ -17,7 +17,7 @@ import { execCommand } from "../exec";
17
17
  import type { HookUIContext } from "../hooks/types";
18
18
  import { logger } from "../logger";
19
19
  import { getAllPluginToolPaths } from "../plugins/loader";
20
- import type { CustomToolAPI, CustomToolFactory, CustomToolsLoadResult, LoadedCustomTool } from "./types";
20
+ import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
21
21
 
22
22
  /**
23
23
  * Resolve tool path.
@@ -56,13 +56,6 @@ function createNoOpUIContext(): HookUIContext {
56
56
  };
57
57
  }
58
58
 
59
- /** Error with source metadata */
60
- interface ToolLoadError {
61
- path: string;
62
- error: string;
63
- source?: { provider: string; providerName: string; level: "user" | "project" };
64
- }
65
-
66
59
  /**
67
60
  * Load a single tool module using native Bun import.
68
61
  */
@@ -118,64 +111,80 @@ interface ToolPathWithSource {
118
111
  }
119
112
 
120
113
  /**
121
- * Load all tools from configuration.
122
- * @param pathsWithSources - Array of tool paths with optional source metadata
123
- * @param cwd - Current working directory for resolving relative paths
124
- * @param builtInToolNames - Names of built-in tools to check for conflicts
114
+ * Loads custom tools from paths with conflict detection and error handling.
115
+ *
116
+ * Manages a shared API instance passed to all tool factories, providing access to
117
+ * execution context, UI, logger, and injected dependencies. The UI context can be
118
+ * updated after loading via setUIContext().
125
119
  */
126
- export async function loadCustomTools(
127
- pathsWithSources: ToolPathWithSource[],
128
- cwd: string,
129
- builtInToolNames: string[],
130
- ): Promise<CustomToolsLoadResult> {
131
- const tools: LoadedCustomTool[] = [];
132
- const errors: ToolLoadError[] = [];
133
- const seenNames = new Set<string>(builtInToolNames);
134
-
135
- // Shared API object - all tools get the same instance
136
- const sharedApi: CustomToolAPI = {
137
- cwd,
138
- exec: (command: string, args: string[], options?: ExecOptions) =>
139
- execCommand(command, args, options?.cwd ?? cwd, options),
140
- ui: createNoOpUIContext(),
141
- hasUI: false,
142
- logger,
143
- typebox,
144
- pi: piCodingAgent,
145
- };
120
+ export class CustomToolLoader {
121
+ tools: LoadedCustomTool[] = [];
122
+ errors: ToolLoadError[] = [];
123
+ private sharedApi: CustomToolAPI;
124
+ private seenNames: Set<string>;
125
+
126
+ constructor(cwd: string, builtInToolNames: string[]) {
127
+ this.sharedApi = {
128
+ cwd,
129
+ exec: (command: string, args: string[], options?: ExecOptions) =>
130
+ execCommand(command, args, options?.cwd ?? cwd, options),
131
+ ui: createNoOpUIContext(),
132
+ hasUI: false,
133
+ logger,
134
+ typebox,
135
+ pi: piCodingAgent,
136
+ };
137
+ this.seenNames = new Set<string>(builtInToolNames);
138
+ }
146
139
 
147
- for (const { path: toolPath, source } of pathsWithSources) {
148
- const { tools: loadedTools, error } = await loadTool(toolPath, cwd, sharedApi, source);
140
+ async load(pathsWithSources: ToolPathWithSource[]): Promise<void> {
141
+ for (const { path: toolPath, source } of pathsWithSources) {
142
+ const { tools: loadedTools, error } = await loadTool(toolPath, this.sharedApi.cwd, this.sharedApi, source);
149
143
 
150
- if (error) {
151
- errors.push(error);
152
- continue;
153
- }
144
+ if (error) {
145
+ this.errors.push(error);
146
+ continue;
147
+ }
154
148
 
155
- if (loadedTools) {
156
- for (const loadedTool of loadedTools) {
157
- // Check for name conflicts
158
- if (seenNames.has(loadedTool.tool.name)) {
159
- errors.push({
160
- path: toolPath,
161
- error: `Tool name "${loadedTool.tool.name}" conflicts with existing tool`,
162
- source,
163
- });
164
- continue;
149
+ if (loadedTools) {
150
+ for (const loadedTool of loadedTools) {
151
+ // Check for name conflicts
152
+ if (this.seenNames.has(loadedTool.tool.name)) {
153
+ this.errors.push({
154
+ path: toolPath,
155
+ error: `Tool name "${loadedTool.tool.name}" conflicts with existing tool`,
156
+ source,
157
+ });
158
+ continue;
159
+ }
160
+
161
+ this.seenNames.add(loadedTool.tool.name);
162
+ this.tools.push(loadedTool);
165
163
  }
166
-
167
- seenNames.add(loadedTool.tool.name);
168
- tools.push(loadedTool);
169
164
  }
170
165
  }
171
166
  }
172
167
 
168
+ setUIContext(uiContext: HookUIContext, hasUI: boolean): void {
169
+ this.sharedApi.ui = uiContext;
170
+ this.sharedApi.hasUI = hasUI;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Load all tools from configuration.
176
+ * @param pathsWithSources - Array of tool paths with optional source metadata
177
+ * @param cwd - Current working directory for resolving relative paths
178
+ * @param builtInToolNames - Names of built-in tools to check for conflicts
179
+ */
180
+ export async function loadCustomTools(pathsWithSources: ToolPathWithSource[], cwd: string, builtInToolNames: string[]) {
181
+ const loader = new CustomToolLoader(cwd, builtInToolNames);
182
+ await loader.load(pathsWithSources);
173
183
  return {
174
- tools,
175
- errors,
176
- setUIContext(uiContext, hasUI) {
177
- sharedApi.ui = uiContext;
178
- sharedApi.hasUI = hasUI;
184
+ tools: loader.tools,
185
+ errors: loader.errors,
186
+ setUIContext: (uiContext: HookUIContext, hasUI: boolean) => {
187
+ loader.setUIContext(uiContext, hasUI);
179
188
  },
180
189
  };
181
190
  }
@@ -190,11 +199,7 @@ export async function loadCustomTools(
190
199
  * @param cwd - Current working directory
191
200
  * @param builtInToolNames - Names of built-in tools to check for conflicts
192
201
  */
193
- export async function discoverAndLoadCustomTools(
194
- configuredPaths: string[],
195
- cwd: string,
196
- builtInToolNames: string[],
197
- ): Promise<CustomToolsLoadResult> {
202
+ export async function discoverAndLoadCustomTools(configuredPaths: string[], cwd: string, builtInToolNames: string[]) {
198
203
  const allPathsWithSources: ToolPathWithSource[] = [];
199
204
  const seen = new Set<string>();
200
205
 
@@ -138,10 +138,10 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
138
138
  execute(
139
139
  toolCallId: string,
140
140
  params: Static<TParams>,
141
- onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
141
+ onUpdate: AgentToolUpdateCallback<TDetails, TParams> | undefined,
142
142
  ctx: CustomToolContext,
143
143
  signal?: AbortSignal,
144
- ): Promise<AgentToolResult<TDetails>>;
144
+ ): Promise<AgentToolResult<TDetails, TParams>>;
145
145
 
146
146
  /** Called on session lifecycle events - use to reconstruct state or cleanup resources */
147
147
  onSession?: (event: CustomToolSessionEvent, ctx: CustomToolContext) => void | Promise<void>;
@@ -158,13 +158,13 @@ export type CustomToolFactory = (
158
158
  ) => CustomTool<any, any> | CustomTool<any, any>[] | Promise<CustomTool<any, any> | CustomTool<any, any>[]>;
159
159
 
160
160
  /** Loaded custom tool with metadata and wrapped AgentTool */
161
- export interface LoadedCustomTool {
161
+ export interface LoadedCustomTool<TParams extends TSchema = TSchema, TDetails = any> {
162
162
  /** Original path (as specified) */
163
163
  path: string;
164
164
  /** Resolved absolute path */
165
165
  resolvedPath: string;
166
166
  /** The original custom tool instance */
167
- tool: CustomTool;
167
+ tool: CustomTool<TParams, TDetails>;
168
168
  /** Source metadata (provider and level) */
169
169
  source?: { provider: string; providerName: string; level: "user" | "project" };
170
170
  }
@@ -1,33 +1,69 @@
1
1
  /**
2
- * Wraps CustomTool instances into AgentTool for use with the agent.
2
+ * CustomToolAdapter wraps CustomTool instances into AgentTool for use with the agent.
3
3
  */
4
4
 
5
- import type { AgentTool } from "@oh-my-pi/pi-agent-core";
5
+ import type { AgentTool, AgentToolResult, AgentToolUpdateCallback, RenderResultOptions } from "@oh-my-pi/pi-agent-core";
6
+ import type { Component } from "@oh-my-pi/pi-tui";
7
+ import type { Static, TSchema } from "@sinclair/typebox";
6
8
  import type { Theme } from "../../modes/interactive/theme/theme";
7
9
  import type { CustomTool, CustomToolContext, LoadedCustomTool } from "./types";
8
10
 
9
- /**
10
- * Wrap a CustomTool into an AgentTool.
11
- * The wrapper injects the ToolContext into execute calls.
12
- */
13
- export function wrapCustomTool(tool: CustomTool, getContext: () => CustomToolContext): AgentTool {
14
- return {
15
- name: tool.name,
16
- label: tool.label,
17
- description: tool.description,
18
- parameters: tool.parameters,
19
- execute: (toolCallId, params, signal, onUpdate, context) =>
20
- tool.execute(toolCallId, params, onUpdate, context ?? getContext(), signal),
21
- renderCall: tool.renderCall ? (args, theme) => tool.renderCall?.(args, theme as Theme) : undefined,
22
- renderResult: tool.renderResult
23
- ? (result, options, theme) => tool.renderResult?.(result, options, theme as Theme)
24
- : undefined,
25
- };
26
- }
11
+ export class CustomToolAdapter<TParams extends TSchema = TSchema, TDetails = any, TTheme extends Theme = Theme>
12
+ implements AgentTool<TParams, TDetails, TTheme>
13
+ {
14
+ name: string;
15
+ label: string;
16
+ description: string;
17
+ parameters: TParams;
27
18
 
28
- /**
29
- * Wrap all loaded custom tools into AgentTools.
30
- */
31
- export function wrapCustomTools(loadedTools: LoadedCustomTool[], getContext: () => CustomToolContext): AgentTool[] {
32
- return loadedTools.map((lt) => wrapCustomTool(lt.tool, getContext));
19
+ constructor(
20
+ private tool: CustomTool<TParams, TDetails>,
21
+ private getContext: () => CustomToolContext,
22
+ ) {
23
+ this.name = tool.name;
24
+ this.label = tool.label ?? "";
25
+ this.description = tool.description;
26
+ this.parameters = tool.parameters;
27
+ }
28
+
29
+ execute(
30
+ toolCallId: string,
31
+ params: Static<TParams>,
32
+ signal?: AbortSignal,
33
+ onUpdate?: AgentToolUpdateCallback<TDetails, TParams>,
34
+ context?: CustomToolContext,
35
+ ) {
36
+ return this.tool.execute(toolCallId, params, onUpdate, context ?? this.getContext(), signal);
37
+ }
38
+
39
+ /** Optional custom rendering for tool call display (returns UI component) */
40
+ renderCall(args: Static<TParams>, theme: TTheme): Component | undefined {
41
+ return this.tool.renderCall?.(args, theme);
42
+ }
43
+
44
+ /** Optional custom rendering for tool result display (returns UI component) */
45
+ renderResult(result: AgentToolResult<TDetails>, options: RenderResultOptions, theme: TTheme): Component | undefined {
46
+ return this.tool.renderResult?.(result, options, theme);
47
+ }
48
+
49
+ /**
50
+ * Backward-compatible export of factory function for existing callers.
51
+ * Prefer CustomToolAdapter constructor directly.
52
+ */
53
+ static wrap<TParams extends TSchema = TSchema, TDetails = any, TTheme extends Theme = Theme>(
54
+ tool: CustomTool<TParams, TDetails>,
55
+ getContext: () => CustomToolContext,
56
+ ): AgentTool<TParams, TDetails, TTheme> {
57
+ return new CustomToolAdapter(tool, getContext);
58
+ }
59
+
60
+ /**
61
+ * Wrap all loaded custom tools into AgentTools.
62
+ */
63
+ static wrapTools<TParams extends TSchema = TSchema, TDetails = any, TTheme extends Theme = Theme>(
64
+ loadedTools: LoadedCustomTool<TParams, TDetails>[],
65
+ getContext: () => CustomToolContext,
66
+ ): AgentTool<TParams, TDetails, TTheme>[] {
67
+ return loadedTools.map((lt) => CustomToolAdapter.wrap(lt.tool, getContext));
68
+ }
33
69
  }
@@ -1,27 +1,7 @@
1
- export interface EventBus {
2
- emit(channel: string, data: unknown): void;
3
- on(channel: string, handler: (data: unknown) => void): () => void;
4
- }
5
-
6
- export interface EventBusController extends EventBus {
7
- clear(): void;
8
- }
9
-
10
- class SimpleEventEmitter {
11
- private listeners = new Map<string, Set<(data: unknown) => void>>();
12
-
13
- on(channel: string, handler: (data: unknown) => void): void {
14
- if (!this.listeners.has(channel)) {
15
- this.listeners.set(channel, new Set());
16
- }
17
- this.listeners.get(channel)!.add(handler);
18
- }
1
+ export class EventBus {
2
+ private readonly listeners = new Map<string, Set<(data: unknown) => void>>();
19
3
 
20
- off(channel: string, handler: (data: unknown) => void): void {
21
- this.listeners.get(channel)?.delete(handler);
22
- }
23
-
24
- emit(channel: string, data: unknown): void {
4
+ public emit(channel: string, data: unknown): void {
25
5
  const handlers = this.listeners.get(channel);
26
6
  if (handlers) {
27
7
  for (const handler of handlers) {
@@ -30,30 +10,22 @@ class SimpleEventEmitter {
30
10
  }
31
11
  }
32
12
 
33
- removeAllListeners(): void {
34
- this.listeners.clear();
13
+ public on(channel: string, handler: (data: unknown) => void): () => void {
14
+ if (!this.listeners.has(channel)) {
15
+ this.listeners.set(channel, new Set());
16
+ }
17
+ const safeHandler = async (data: unknown) => {
18
+ try {
19
+ await handler(data);
20
+ } catch (err) {
21
+ console.error(`Event handler error (${channel}):`, err);
22
+ }
23
+ };
24
+ this.listeners.get(channel)!.add(safeHandler);
25
+ return () => this.listeners.get(channel)?.delete(safeHandler);
35
26
  }
36
- }
37
27
 
38
- export function createEventBus(): EventBusController {
39
- const emitter = new SimpleEventEmitter();
40
- return {
41
- emit: (channel, data) => {
42
- emitter.emit(channel, data);
43
- },
44
- on: (channel, handler) => {
45
- const safeHandler = async (data: unknown) => {
46
- try {
47
- await handler(data);
48
- } catch (err) {
49
- console.error(`Event handler error (${channel}):`, err);
50
- }
51
- };
52
- emitter.on(channel, safeHandler);
53
- return () => emitter.off(channel, safeHandler);
54
- },
55
- clear: () => {
56
- emitter.removeAllListeners();
57
- },
58
- };
28
+ public clear(): void {
29
+ this.listeners.clear();
30
+ }
59
31
  }
@@ -2,7 +2,7 @@
2
2
  * Extension system for lifecycle events and custom tools.
3
3
  */
4
4
 
5
- export { createExtensionRuntime, discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions } from "./loader";
5
+ export { discoverAndLoadExtensions, ExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./loader";
6
6
  export type {
7
7
  BranchHandler,
8
8
  ExtensionErrorListener,
@@ -32,7 +32,6 @@ export type {
32
32
  ExecResult,
33
33
  Extension,
34
34
  ExtensionActions,
35
- // API
36
35
  ExtensionAPI,
37
36
  ExtensionCommandContext,
38
37
  ExtensionCommandContextActions,
@@ -45,7 +44,6 @@ export type {
45
44
  ExtensionFactory,
46
45
  ExtensionFlag,
47
46
  ExtensionHandler,
48
- ExtensionRuntime,
49
47
  ExtensionShortcut,
50
48
  ExtensionUIContext,
51
49
  ExtensionUIDialogOptions,
@@ -112,4 +110,10 @@ export {
112
110
  isReadToolResult,
113
111
  isWriteToolResult,
114
112
  } from "./types";
115
- export { wrapRegisteredTool, wrapRegisteredTools, wrapToolWithExtensions } from "./wrapper";
113
+ export {
114
+ ExtensionToolWrapper,
115
+ RegisteredToolAdapter,
116
+ wrapRegisteredTool,
117
+ wrapRegisteredTools,
118
+ wrapToolWithExtensions,
119
+ } from "./wrapper";