@oh-my-pi/pi-coding-agent 3.20.1 → 3.24.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.
- package/CHANGELOG.md +107 -8
- package/docs/custom-tools.md +3 -3
- package/docs/extensions.md +226 -220
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +50 -53
- package/examples/custom-tools/README.md +2 -17
- package/examples/extensions/README.md +76 -74
- package/examples/extensions/todo.ts +2 -5
- package/examples/hooks/custom-compaction.ts +2 -4
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +7 -11
- package/package.json +6 -6
- package/src/cli/args.ts +9 -6
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +16 -5
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -8
- package/src/core/file-mentions.ts +5 -8
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +1 -1
- package/src/core/sdk.ts +64 -105
- package/src/core/session-manager.ts +18 -22
- package/src/core/settings-manager.ts +66 -1
- package/src/core/slash-commands.ts +12 -5
- package/src/core/system-prompt.ts +49 -36
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/ask.ts +98 -4
- package/src/core/tools/bash-interceptor.ts +11 -4
- package/src/core/tools/bash.ts +121 -5
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit-diff.ts +73 -24
- package/src/core/tools/edit.ts +221 -34
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +149 -5
- package/src/core/tools/gemini-image.ts +279 -56
- package/src/core/tools/git.ts +17 -3
- package/src/core/tools/grep.ts +185 -5
- package/src/core/tools/index.test.ts +180 -0
- package/src/core/tools/index.ts +96 -242
- package/src/core/tools/ls.ts +133 -5
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +21 -22
- package/src/core/tools/notebook.ts +112 -4
- package/src/core/tools/output.ts +175 -15
- package/src/core/tools/read.ts +127 -25
- package/src/core/tools/render-utils.ts +241 -0
- package/src/core/tools/renderers.ts +40 -828
- package/src/core/tools/review.ts +26 -25
- package/src/core/tools/rulebook.ts +11 -3
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +264 -254
- package/src/core/tools/task/index.ts +48 -208
- package/src/core/tools/task/render.ts +26 -11
- package/src/core/tools/task/types.ts +7 -12
- package/src/core/tools/task/worker-protocol.ts +17 -0
- package/src/core/tools/task/worker.ts +238 -0
- package/src/core/tools/truncate.ts +27 -1
- package/src/core/tools/web-fetch.ts +25 -49
- package/src/core/tools/web-search/index.ts +132 -46
- package/src/core/tools/web-search/providers/anthropic.ts +7 -2
- package/src/core/tools/web-search/providers/exa.ts +2 -1
- package/src/core/tools/web-search/providers/perplexity.ts +6 -1
- package/src/core/tools/web-search/render.ts +6 -4
- package/src/core/tools/web-search/types.ts +13 -0
- package/src/core/tools/write.ts +96 -14
- package/src/core/voice.ts +1 -1
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +5 -5
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +49 -0
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +93 -538
- package/src/modes/interactive/interactive-mode.ts +19 -7
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/task.md +0 -7
- package/src/prompts/tools/gemini-image.md +5 -1
- package/src/prompts/tools/output.md +6 -2
- package/src/prompts/tools/task.md +68 -0
- package/src/prompts/tools/web-fetch.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/utils/image-convert.ts +8 -2
- package/src/utils/image-magick.ts +247 -0
- package/src/utils/image-resize.ts +53 -13
- package/examples/custom-tools/question/index.ts +0 -84
- package/examples/custom-tools/subagent/README.md +0 -172
- package/examples/custom-tools/subagent/agents/planner.md +0 -37
- package/examples/custom-tools/subagent/agents/scout.md +0 -50
- package/examples/custom-tools/subagent/agents/worker.md +0 -24
- package/examples/custom-tools/subagent/agents.ts +0 -156
- package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
- package/examples/custom-tools/subagent/commands/implement.md +0 -10
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
- package/examples/custom-tools/subagent/index.ts +0 -1002
- package/examples/sdk/05-tools.ts +0 -94
- package/examples/sdk/12-full-control.ts +0 -95
- package/src/prompts/browser.md +0 -71
package/src/core/tools/index.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export { type AskToolDetails, askTool, createAskTool } from "./ask";
|
|
2
|
-
export { type BashToolDetails,
|
|
3
|
-
export { createEditTool
|
|
2
|
+
export { type BashToolDetails, createBashTool } from "./bash";
|
|
3
|
+
export { createEditTool } from "./edit";
|
|
4
4
|
// Exa MCP tools (22 tools)
|
|
5
5
|
export { exaTools } from "./exa/index";
|
|
6
6
|
export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types";
|
|
7
|
-
export { createFindTool, type FindToolDetails
|
|
7
|
+
export { createFindTool, type FindToolDetails } from "./find";
|
|
8
|
+
export { setPreferredImageProvider } from "./gemini-image";
|
|
8
9
|
export { createGitTool, type GitToolDetails, gitTool } from "./git";
|
|
9
|
-
export { createGrepTool, type GrepToolDetails
|
|
10
|
-
export { createLsTool, type LsToolDetails
|
|
10
|
+
export { createGrepTool, type GrepToolDetails } from "./grep";
|
|
11
|
+
export { createLsTool, type LsToolDetails } from "./ls";
|
|
11
12
|
export {
|
|
12
13
|
createLspTool,
|
|
13
14
|
type FileDiagnosticsResult,
|
|
@@ -19,19 +20,14 @@ export {
|
|
|
19
20
|
lspTool,
|
|
20
21
|
warmupLspServers,
|
|
21
22
|
} from "./lsp/index";
|
|
22
|
-
export { createNotebookTool, type NotebookToolDetails
|
|
23
|
-
export { createOutputTool, type OutputToolDetails
|
|
24
|
-
export { createReadTool, type ReadToolDetails
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
createRulebookTool,
|
|
28
|
-
filterRulebookRules,
|
|
29
|
-
formatRulesForPrompt,
|
|
30
|
-
type RulebookToolDetails,
|
|
31
|
-
} from "./rulebook";
|
|
23
|
+
export { createNotebookTool, type NotebookToolDetails } from "./notebook";
|
|
24
|
+
export { createOutputTool, type OutputToolDetails } from "./output";
|
|
25
|
+
export { createReadTool, type ReadToolDetails } from "./read";
|
|
26
|
+
export { reportFindingTool, submitReviewTool } from "./review";
|
|
27
|
+
export { filterRulebookRules, formatRulesForPrompt, type RulebookToolDetails } from "./rulebook";
|
|
32
28
|
export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index";
|
|
33
29
|
export type { TruncationResult } from "./truncate";
|
|
34
|
-
export { createWebFetchTool, type WebFetchToolDetails
|
|
30
|
+
export { createWebFetchTool, type WebFetchToolDetails } from "./web-fetch";
|
|
35
31
|
export {
|
|
36
32
|
companyWebSearchTools,
|
|
37
33
|
createWebSearchTool,
|
|
@@ -39,6 +35,7 @@ export {
|
|
|
39
35
|
getWebSearchTools,
|
|
40
36
|
hasExaWebSearch,
|
|
41
37
|
linkedinWebSearchTools,
|
|
38
|
+
setPreferredWebSearchProvider,
|
|
42
39
|
type WebSearchProvider,
|
|
43
40
|
type WebSearchResponse,
|
|
44
41
|
type WebSearchToolsOptions,
|
|
@@ -50,247 +47,104 @@ export {
|
|
|
50
47
|
webSearchLinkedinTool,
|
|
51
48
|
webSearchTool,
|
|
52
49
|
} from "./web-search/index";
|
|
53
|
-
export { createWriteTool, type WriteToolDetails
|
|
50
|
+
export { createWriteTool, type WriteToolDetails } from "./write";
|
|
54
51
|
|
|
55
52
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
72
|
-
import {
|
|
53
|
+
import type { Rule } from "../../capability/rule";
|
|
54
|
+
import type { EventBus } from "../event-bus";
|
|
55
|
+
import { createAskTool } from "./ask";
|
|
56
|
+
import { createBashTool } from "./bash";
|
|
57
|
+
import { createEditTool } from "./edit";
|
|
58
|
+
import { createFindTool } from "./find";
|
|
59
|
+
import { createGitTool } from "./git";
|
|
60
|
+
import { createGrepTool } from "./grep";
|
|
61
|
+
import { createLsTool } from "./ls";
|
|
62
|
+
import { createLspTool } from "./lsp/index";
|
|
63
|
+
import { createNotebookTool } from "./notebook";
|
|
64
|
+
import { createOutputTool } from "./output";
|
|
65
|
+
import { createReadTool } from "./read";
|
|
66
|
+
import { reportFindingTool, submitReviewTool } from "./review";
|
|
67
|
+
import { createRulebookTool } from "./rulebook";
|
|
68
|
+
import { createTaskTool } from "./task/index";
|
|
69
|
+
import { createWebFetchTool } from "./web-fetch";
|
|
70
|
+
import { createWebSearchTool } from "./web-search/index";
|
|
71
|
+
import { createWriteTool } from "./write";
|
|
73
72
|
|
|
74
73
|
/** Tool type (AgentTool from pi-ai) */
|
|
75
74
|
export type Tool = AgentTool<any, any, any>;
|
|
76
75
|
|
|
77
|
-
/**
|
|
78
|
-
export interface
|
|
76
|
+
/** Session context for tool factories */
|
|
77
|
+
export interface ToolSession {
|
|
78
|
+
/** Current working directory */
|
|
79
|
+
cwd: string;
|
|
80
|
+
/** Whether UI is available */
|
|
81
|
+
hasUI: boolean;
|
|
82
|
+
/** Rulebook rules */
|
|
83
|
+
rulebookRules: Rule[];
|
|
84
|
+
/** Event bus for tool/extension communication */
|
|
85
|
+
eventBus?: EventBus;
|
|
86
|
+
/** Get session file */
|
|
79
87
|
getSessionFile: () => string | null;
|
|
88
|
+
/** Get session spawns */
|
|
89
|
+
getSessionSpawns: () => string | null;
|
|
90
|
+
/** Settings manager (optional) */
|
|
91
|
+
settings?: {
|
|
92
|
+
getImageAutoResize(): boolean;
|
|
93
|
+
getLspFormatOnWrite(): boolean;
|
|
94
|
+
getLspDiagnosticsOnWrite(): boolean;
|
|
95
|
+
getLspDiagnosticsOnEdit(): boolean;
|
|
96
|
+
getEditFuzzyMatch(): boolean;
|
|
97
|
+
getGitToolEnabled(): boolean;
|
|
98
|
+
getBashInterceptorEnabled(): boolean;
|
|
99
|
+
};
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Tool definitions: static tools and their factory functions
|
|
102
|
-
const toolDefs: Record<string, { tool: Tool; create: ToolFactory }> = {
|
|
103
|
-
ask: { tool: askTool, create: createAskTool },
|
|
104
|
-
read: {
|
|
105
|
-
tool: readTool,
|
|
106
|
-
create: (cwd, _ctx, options) => createReadTool(cwd, { autoResizeImages: options?.readAutoResizeImages ?? true }),
|
|
107
|
-
},
|
|
108
|
-
bash: { tool: bashTool, create: createBashTool },
|
|
109
|
-
edit: {
|
|
110
|
-
tool: editTool,
|
|
111
|
-
create: (cwd, _ctx, options) => {
|
|
112
|
-
const enableDiagnostics = options?.lspDiagnosticsOnEdit ?? false;
|
|
113
|
-
const enableFormat = options?.lspFormatOnWrite ?? true;
|
|
114
|
-
const writethrough = createLspWritethrough(cwd, {
|
|
115
|
-
enableFormat,
|
|
116
|
-
enableDiagnostics,
|
|
117
|
-
});
|
|
118
|
-
return createEditTool(cwd, { fuzzyMatch: options?.editFuzzyMatch ?? true, writethrough });
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
write: {
|
|
122
|
-
tool: writeTool,
|
|
123
|
-
create: (cwd, _ctx, options) => {
|
|
124
|
-
const enableFormat = options?.lspFormatOnWrite ?? true;
|
|
125
|
-
const enableDiagnostics = options?.lspDiagnosticsOnWrite ?? true;
|
|
126
|
-
const writethrough = createLspWritethrough(cwd, {
|
|
127
|
-
enableFormat,
|
|
128
|
-
enableDiagnostics,
|
|
129
|
-
});
|
|
130
|
-
return createWriteTool(cwd, { writethrough });
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
grep: { tool: grepTool, create: createGrepTool },
|
|
134
|
-
find: { tool: findTool, create: createFindTool },
|
|
135
|
-
git: { tool: gitTool, create: createGitTool },
|
|
136
|
-
ls: { tool: lsTool, create: createLsTool },
|
|
137
|
-
lsp: { tool: lspTool, create: createLspTool },
|
|
138
|
-
notebook: { tool: notebookTool, create: createNotebookTool },
|
|
139
|
-
output: { tool: outputTool, create: (cwd, ctx) => createOutputTool(cwd, ctx) },
|
|
140
|
-
task: { tool: taskTool, create: (cwd, ctx, opts) => createTaskTool(cwd, ctx, opts) },
|
|
141
|
-
web_fetch: { tool: webFetchTool, create: createWebFetchTool },
|
|
142
|
-
web_search: { tool: webSearchTool, create: createWebSearchTool },
|
|
143
|
-
report_finding: { tool: reportFindingTool, create: createReportFindingTool },
|
|
144
|
-
submit_review: { tool: submitReviewTool, create: createSubmitReviewTool },
|
|
102
|
+
type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
|
|
103
|
+
|
|
104
|
+
export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
105
|
+
ask: createAskTool,
|
|
106
|
+
bash: createBashTool,
|
|
107
|
+
edit: createEditTool,
|
|
108
|
+
find: createFindTool,
|
|
109
|
+
git: createGitTool,
|
|
110
|
+
grep: createGrepTool,
|
|
111
|
+
ls: createLsTool,
|
|
112
|
+
lsp: createLspTool,
|
|
113
|
+
notebook: createNotebookTool,
|
|
114
|
+
output: createOutputTool,
|
|
115
|
+
read: createReadTool,
|
|
116
|
+
rulebook: createRulebookTool,
|
|
117
|
+
task: createTaskTool,
|
|
118
|
+
web_fetch: createWebFetchTool,
|
|
119
|
+
web_search: createWebSearchTool,
|
|
120
|
+
write: createWriteTool,
|
|
145
121
|
};
|
|
146
122
|
|
|
147
|
-
export
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Tool sets defined by name (base sets, without UI-only tools)
|
|
153
|
-
export const baseCodingToolNames: ToolName[] = [
|
|
154
|
-
"read",
|
|
155
|
-
"bash",
|
|
156
|
-
"edit",
|
|
157
|
-
"write",
|
|
158
|
-
"grep",
|
|
159
|
-
"find",
|
|
160
|
-
"git",
|
|
161
|
-
"ls",
|
|
162
|
-
"lsp",
|
|
163
|
-
"notebook",
|
|
164
|
-
"output",
|
|
165
|
-
"task",
|
|
166
|
-
"web_fetch",
|
|
167
|
-
"web_search",
|
|
168
|
-
];
|
|
169
|
-
const baseReadOnlyToolNames: ToolName[] = ["read", "grep", "find", "ls"];
|
|
170
|
-
|
|
171
|
-
// Default tools for full access mode (using process.cwd(), no UI)
|
|
172
|
-
export const codingTools: Tool[] = baseCodingToolNames.map((name) => toolDefs[name].tool);
|
|
173
|
-
|
|
174
|
-
// Read-only tools for exploration without modification (using process.cwd(), no UI)
|
|
175
|
-
export const readOnlyTools: Tool[] = baseReadOnlyToolNames.map((name) => toolDefs[name].tool);
|
|
176
|
-
|
|
177
|
-
// All available tools (using process.cwd(), no UI)
|
|
178
|
-
export const allTools = Object.fromEntries(Object.entries(toolDefs).map(([name, def]) => [name, def.tool])) as Record<
|
|
179
|
-
ToolName,
|
|
180
|
-
Tool
|
|
181
|
-
>;
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Create coding tools configured for a specific working directory.
|
|
185
|
-
* @param cwd - Working directory for tools
|
|
186
|
-
* @param hasUI - Whether UI is available (includes ask tool if true)
|
|
187
|
-
* @param sessionContext - Optional session context for tools that need it
|
|
188
|
-
* @param options - Options for tool configuration
|
|
189
|
-
*/
|
|
190
|
-
export async function createCodingTools(
|
|
191
|
-
cwd: string,
|
|
192
|
-
hasUI = false,
|
|
193
|
-
sessionContext?: SessionContext,
|
|
194
|
-
options?: CodingToolsOptions,
|
|
195
|
-
): Promise<Tool[]> {
|
|
196
|
-
const names = hasUI ? [...baseCodingToolNames, ...uiToolNames] : baseCodingToolNames;
|
|
197
|
-
const optionsWithTools = { ...options, availableTools: new Set(names) };
|
|
198
|
-
return Promise.all(names.map((name) => toolDefs[name].create(cwd, sessionContext, optionsWithTools)));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Create read-only tools configured for a specific working directory.
|
|
203
|
-
* @param cwd - Working directory for tools
|
|
204
|
-
* @param hasUI - Whether UI is available (includes ask tool if true)
|
|
205
|
-
* @param sessionContext - Optional session context for tools that need it
|
|
206
|
-
* @param options - Options for tool configuration
|
|
207
|
-
*/
|
|
208
|
-
export async function createReadOnlyTools(
|
|
209
|
-
cwd: string,
|
|
210
|
-
hasUI = false,
|
|
211
|
-
sessionContext?: SessionContext,
|
|
212
|
-
options?: CodingToolsOptions,
|
|
213
|
-
): Promise<Tool[]> {
|
|
214
|
-
const names = hasUI ? [...baseReadOnlyToolNames, ...uiToolNames] : baseReadOnlyToolNames;
|
|
215
|
-
const optionsWithTools = { ...options, availableTools: new Set(names) };
|
|
216
|
-
return Promise.all(names.map((name) => toolDefs[name].create(cwd, sessionContext, optionsWithTools)));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Create all tools configured for a specific working directory.
|
|
221
|
-
* @param cwd - Working directory for tools
|
|
222
|
-
* @param sessionContext - Optional session context for tools that need it
|
|
223
|
-
* @param options - Options for tool configuration
|
|
224
|
-
*/
|
|
225
|
-
export async function createAllTools(
|
|
226
|
-
cwd: string,
|
|
227
|
-
sessionContext?: SessionContext,
|
|
228
|
-
options?: CodingToolsOptions,
|
|
229
|
-
): Promise<Record<ToolName, Tool>> {
|
|
230
|
-
const names = Object.keys(toolDefs);
|
|
231
|
-
const optionsWithTools = { ...options, availableTools: new Set(names) };
|
|
232
|
-
const entries = await Promise.all(
|
|
233
|
-
Object.entries(toolDefs).map(async ([name, def]) => [
|
|
234
|
-
name,
|
|
235
|
-
await def.create(cwd, sessionContext, optionsWithTools),
|
|
236
|
-
]),
|
|
237
|
-
);
|
|
238
|
-
return Object.fromEntries(entries) as Record<ToolName, Tool>;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Wrap a bash tool with interception that redirects common patterns to specialized tools.
|
|
243
|
-
* This helps prevent LLMs from falling back to shell commands when better tools exist.
|
|
244
|
-
*
|
|
245
|
-
* @param bashTool - The bash tool to wrap
|
|
246
|
-
* @param availableTools - Set of tool names that are available (for context-aware blocking)
|
|
247
|
-
* @returns Wrapped bash tool with interception
|
|
248
|
-
*/
|
|
249
|
-
export function wrapBashWithInterception(bashTool: Tool, availableTools: Set<string>): Tool {
|
|
250
|
-
const originalExecute = bashTool.execute;
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
...bashTool,
|
|
254
|
-
execute: async (toolCallId, params, signal, onUpdate, context) => {
|
|
255
|
-
const command = (params as { command: string }).command;
|
|
256
|
-
|
|
257
|
-
// Check for forbidden patterns
|
|
258
|
-
const interception = checkBashInterception(command, availableTools);
|
|
259
|
-
if (interception.block) {
|
|
260
|
-
throw new Error(interception.message);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Check for simple ls that should use ls tool
|
|
264
|
-
const lsInterception = checkSimpleLsInterception(command, availableTools);
|
|
265
|
-
if (lsInterception.block) {
|
|
266
|
-
throw new Error(lsInterception.message);
|
|
267
|
-
}
|
|
123
|
+
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
124
|
+
report_finding: () => reportFindingTool,
|
|
125
|
+
submit_review: () => submitReviewTool,
|
|
126
|
+
};
|
|
268
127
|
|
|
269
|
-
|
|
270
|
-
return originalExecute(toolCallId, params, signal, onUpdate, context);
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
}
|
|
128
|
+
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
274
129
|
|
|
275
130
|
/**
|
|
276
|
-
*
|
|
277
|
-
* Finds the bash tool and wraps it with interception based on other available tools.
|
|
278
|
-
*
|
|
279
|
-
* @param tools - Array of tools to process
|
|
280
|
-
* @returns Tools with bash interception applied
|
|
131
|
+
* Create tools from BUILTIN_TOOLS registry.
|
|
281
132
|
*/
|
|
282
|
-
export function
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
133
|
+
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
|
|
134
|
+
const requestedTools = toolNames && toolNames.length > 0 ? toolNames : undefined;
|
|
135
|
+
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
|
|
136
|
+
const entries = requestedTools
|
|
137
|
+
? requestedTools
|
|
138
|
+
.filter((name, index) => requestedTools.indexOf(name) === index && name in allTools)
|
|
139
|
+
.map((name) => [name, allTools[name]] as const)
|
|
140
|
+
: Object.entries(BUILTIN_TOOLS);
|
|
141
|
+
const results = await Promise.all(entries.map(([, factory]) => factory(session)));
|
|
142
|
+
const tools = results.filter((t): t is Tool => t !== null);
|
|
143
|
+
|
|
144
|
+
if (requestedTools) {
|
|
145
|
+
const allowed = new Set(requestedTools);
|
|
146
|
+
return tools.filter((tool) => allowed.has(tool.name));
|
|
288
147
|
}
|
|
289
148
|
|
|
290
|
-
return tools
|
|
291
|
-
if (tool.name === "bash") {
|
|
292
|
-
return wrapBashWithInterception(tool, toolNames);
|
|
293
|
-
}
|
|
294
|
-
return tool;
|
|
295
|
-
});
|
|
149
|
+
return tools;
|
|
296
150
|
}
|
package/src/core/tools/ls.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import nodePath from "node:path";
|
|
3
3
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
6
|
import { Type } from "@sinclair/typebox";
|
|
7
|
+
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
8
|
+
import type { RenderResultOptions } from "../custom-tools/types";
|
|
5
9
|
import { untilAborted } from "../utils";
|
|
10
|
+
import type { ToolSession } from "./index";
|
|
6
11
|
import { resolveToCwd } from "./path-utils";
|
|
7
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
formatAge,
|
|
14
|
+
formatBytes,
|
|
15
|
+
formatCount,
|
|
16
|
+
formatEmptyMessage,
|
|
17
|
+
formatExpandHint,
|
|
18
|
+
formatMeta,
|
|
19
|
+
formatMoreItems,
|
|
20
|
+
formatTruncationSuffix,
|
|
21
|
+
PREVIEW_LIMITS,
|
|
22
|
+
} from "./render-utils";
|
|
8
23
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate";
|
|
9
24
|
|
|
10
25
|
const lsSchema = Type.Object({
|
|
@@ -23,7 +38,7 @@ export interface LsToolDetails {
|
|
|
23
38
|
entryLimitReached?: number;
|
|
24
39
|
}
|
|
25
40
|
|
|
26
|
-
export function createLsTool(
|
|
41
|
+
export function createLsTool(session: ToolSession): AgentTool<typeof lsSchema> {
|
|
27
42
|
return {
|
|
28
43
|
name: "ls",
|
|
29
44
|
label: "Ls",
|
|
@@ -35,7 +50,7 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
35
50
|
signal?: AbortSignal,
|
|
36
51
|
) => {
|
|
37
52
|
return untilAborted(signal, async () => {
|
|
38
|
-
const dirPath = resolveToCwd(path || ".", cwd);
|
|
53
|
+
const dirPath = resolveToCwd(path || ".", session.cwd);
|
|
39
54
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
40
55
|
|
|
41
56
|
// Check if path exists
|
|
@@ -145,5 +160,118 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
145
160
|
};
|
|
146
161
|
}
|
|
147
162
|
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// TUI Renderer
|
|
165
|
+
// =============================================================================
|
|
166
|
+
|
|
167
|
+
interface LsRenderArgs {
|
|
168
|
+
path?: string;
|
|
169
|
+
limit?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
173
|
+
|
|
174
|
+
export const lsToolRenderer = {
|
|
175
|
+
renderCall(args: LsRenderArgs, uiTheme: Theme): Component {
|
|
176
|
+
const label = uiTheme.fg("toolTitle", uiTheme.bold("Ls"));
|
|
177
|
+
let text = `${label} ${uiTheme.fg("accent", args.path || ".")}`;
|
|
178
|
+
|
|
179
|
+
const meta: string[] = [];
|
|
180
|
+
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
181
|
+
text += formatMeta(meta, uiTheme);
|
|
182
|
+
|
|
183
|
+
return new Text(text, 0, 0);
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
renderResult(
|
|
187
|
+
result: { content: Array<{ type: string; text?: string }>; details?: LsToolDetails },
|
|
188
|
+
{ expanded }: RenderResultOptions,
|
|
189
|
+
uiTheme: Theme,
|
|
190
|
+
): Component {
|
|
191
|
+
const details = result.details;
|
|
192
|
+
const textContent = result.content?.find((c) => c.type === "text")?.text ?? "";
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
(!textContent || textContent.trim() === "" || textContent.trim() === "(empty directory)") &&
|
|
196
|
+
(!details?.entries || details.entries.length === 0)
|
|
197
|
+
) {
|
|
198
|
+
return new Text(formatEmptyMessage("Empty directory", uiTheme), 0, 0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let entries: string[] = details?.entries ? [...details.entries] : [];
|
|
202
|
+
if (entries.length === 0) {
|
|
203
|
+
const rawLines = textContent.split("\n").filter((l: string) => l.trim());
|
|
204
|
+
entries = rawLines.filter((line) => !/^\[.*\]$/.test(line.trim()));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (entries.length === 0) {
|
|
208
|
+
return new Text(formatEmptyMessage("Empty directory", uiTheme), 0, 0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let dirCount = details?.dirCount;
|
|
212
|
+
let fileCount = details?.fileCount;
|
|
213
|
+
if (dirCount === undefined || fileCount === undefined) {
|
|
214
|
+
dirCount = 0;
|
|
215
|
+
fileCount = 0;
|
|
216
|
+
for (const entry of entries) {
|
|
217
|
+
if (entry.endsWith("/")) {
|
|
218
|
+
dirCount += 1;
|
|
219
|
+
} else {
|
|
220
|
+
fileCount += 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const truncated = Boolean(details?.truncation?.truncated || details?.entryLimitReached);
|
|
226
|
+
const icon = truncated
|
|
227
|
+
? uiTheme.styledSymbol("status.warning", "warning")
|
|
228
|
+
: uiTheme.styledSymbol("status.success", "success");
|
|
229
|
+
|
|
230
|
+
const summaryText = [formatCount("dir", dirCount ?? 0), formatCount("file", fileCount ?? 0)].join(
|
|
231
|
+
uiTheme.sep.dot,
|
|
232
|
+
);
|
|
233
|
+
const maxEntries = expanded ? entries.length : Math.min(entries.length, COLLAPSED_LIST_LIMIT);
|
|
234
|
+
const hasMoreEntries = entries.length > maxEntries;
|
|
235
|
+
const expandHint = formatExpandHint(expanded, hasMoreEntries, uiTheme);
|
|
236
|
+
|
|
237
|
+
let text = `${icon} ${uiTheme.fg("dim", summaryText)}${formatTruncationSuffix(truncated, uiTheme)}${expandHint}`;
|
|
238
|
+
|
|
239
|
+
const truncationReasons: string[] = [];
|
|
240
|
+
if (details?.entryLimitReached) {
|
|
241
|
+
truncationReasons.push(`entry limit ${details.entryLimitReached}`);
|
|
242
|
+
}
|
|
243
|
+
if (details?.truncation?.truncated) {
|
|
244
|
+
truncationReasons.push(`output cap ${formatBytes(details.truncation.maxBytes)}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const hasTruncation = truncationReasons.length > 0;
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < maxEntries; i++) {
|
|
250
|
+
const entry = entries[i];
|
|
251
|
+
const isLast = i === maxEntries - 1 && !hasMoreEntries && !hasTruncation;
|
|
252
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
253
|
+
const isDir = entry.endsWith("/");
|
|
254
|
+
const entryPath = isDir ? entry.slice(0, -1) : entry;
|
|
255
|
+
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
256
|
+
const entryIcon = isDir
|
|
257
|
+
? uiTheme.fg("accent", uiTheme.icon.folder)
|
|
258
|
+
: uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
259
|
+
const entryColor = isDir ? "accent" : "toolOutput";
|
|
260
|
+
text += `\n ${uiTheme.fg("dim", branch)} ${entryIcon} ${uiTheme.fg(entryColor, entry)}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (hasMoreEntries) {
|
|
264
|
+
const moreEntriesBranch = hasTruncation ? uiTheme.tree.branch : uiTheme.tree.last;
|
|
265
|
+
text += `\n ${uiTheme.fg("dim", moreEntriesBranch)} ${uiTheme.fg(
|
|
266
|
+
"muted",
|
|
267
|
+
formatMoreItems(entries.length - maxEntries, "entry", uiTheme),
|
|
268
|
+
)}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (hasTruncation) {
|
|
272
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("warning", `truncated: ${truncationReasons.join(", ")}`)}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return new Text(text, 0, 0);
|
|
276
|
+
},
|
|
277
|
+
};
|