@oh-my-pi/pi-coding-agent 8.4.3 → 8.5.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 +26 -0
- package/package.json +6 -6
- package/src/cursor.ts +1 -1
- package/src/modes/components/model-selector.ts +43 -14
- package/src/modes/components/tool-execution.ts +1 -3
- package/src/modes/controllers/event-controller.ts +0 -6
- package/src/modes/interactive-mode.ts +1 -20
- package/src/modes/types.ts +0 -1
- package/src/prompts/system/custom-system-prompt.md +14 -0
- package/src/prompts/system/plan-mode-active.md +4 -0
- package/src/prompts/system/system-prompt.md +12 -0
- package/src/prompts/tools/find.md +3 -2
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/task.md +1 -0
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +4 -0
- package/src/session/agent-storage.ts +54 -1
- package/src/session/session-manager.ts +29 -2
- package/src/system-prompt.ts +26 -1
- package/src/task/executor.ts +99 -13
- package/src/task/index.ts +58 -11
- package/src/task/template.ts +3 -1
- package/src/task/types.ts +5 -0
- package/src/task/worker-protocol.ts +1 -0
- package/src/task/worker.ts +9 -0
- package/src/tools/bash.ts +1 -3
- package/src/tools/find.ts +74 -150
- package/src/tools/grep.ts +215 -109
- package/src/tools/index.ts +0 -3
- package/src/tools/output-meta.ts +2 -2
- package/src/tools/python.ts +1 -3
- package/src/tools/read.ts +30 -20
- package/src/prompts/tools/enter-plan-mode.md +0 -92
- package/src/tools/enter-plan-mode.ts +0 -76
package/src/tools/read.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { renderCodeCell, renderOutputBlock, renderStatusLine } from "../tui";
|
|
|
16
16
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
17
17
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
|
18
18
|
import { ensureTool } from "../utils/tools-manager";
|
|
19
|
-
import {
|
|
19
|
+
import { runRg } from "./grep";
|
|
20
20
|
import { applyListLimit } from "./list-limit";
|
|
21
21
|
import { LsTool } from "./ls";
|
|
22
22
|
import type { OutputMeta } from "./output-meta";
|
|
@@ -164,18 +164,26 @@ async function listCandidateFiles(
|
|
|
164
164
|
signal?: AbortSignal,
|
|
165
165
|
notify?: (message: string) => void,
|
|
166
166
|
): Promise<{ files: string[]; truncated: boolean; error?: string }> {
|
|
167
|
-
let
|
|
167
|
+
let rgPath: string | undefined;
|
|
168
168
|
try {
|
|
169
|
-
|
|
169
|
+
rgPath = await ensureTool("rg", { silent: true, notify });
|
|
170
170
|
} catch {
|
|
171
|
-
return { files: [], truncated: false, error: "
|
|
171
|
+
return { files: [], truncated: false, error: "rg not available" };
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (!
|
|
175
|
-
return { files: [], truncated: false, error: "
|
|
174
|
+
if (!rgPath) {
|
|
175
|
+
return { files: [], truncated: false, error: "rg not available" };
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
const args: string[] = [
|
|
178
|
+
const args: string[] = [
|
|
179
|
+
"--files",
|
|
180
|
+
"--color=never",
|
|
181
|
+
"--hidden",
|
|
182
|
+
"--glob",
|
|
183
|
+
"!**/.git/**",
|
|
184
|
+
"--glob",
|
|
185
|
+
"!**/node_modules/**",
|
|
186
|
+
];
|
|
179
187
|
|
|
180
188
|
const gitignoreFiles = new Set<string>();
|
|
181
189
|
const rootGitignore = path.join(searchRoot, ".gitignore");
|
|
@@ -185,20 +193,19 @@ async function listCandidateFiles(
|
|
|
185
193
|
|
|
186
194
|
try {
|
|
187
195
|
const gitignoreArgs = [
|
|
188
|
-
"--
|
|
189
|
-
"f",
|
|
196
|
+
"--files",
|
|
190
197
|
"--color=never",
|
|
191
198
|
"--hidden",
|
|
192
|
-
"--
|
|
199
|
+
"--no-ignore",
|
|
200
|
+
"--glob",
|
|
201
|
+
"!**/.git/**",
|
|
202
|
+
"--glob",
|
|
203
|
+
"!**/node_modules/**",
|
|
193
204
|
"--glob",
|
|
194
205
|
".gitignore",
|
|
195
|
-
"--exclude",
|
|
196
|
-
"node_modules",
|
|
197
|
-
"--exclude",
|
|
198
|
-
".git",
|
|
199
206
|
searchRoot,
|
|
200
207
|
];
|
|
201
|
-
const { stdout } = await
|
|
208
|
+
const { stdout } = await runRg(rgPath, gitignoreArgs, signal);
|
|
202
209
|
const output = stdout.trim();
|
|
203
210
|
if (output) {
|
|
204
211
|
const nestedGitignores = output
|
|
@@ -224,15 +231,15 @@ async function listCandidateFiles(
|
|
|
224
231
|
args.push("--ignore-file", gitignorePath);
|
|
225
232
|
}
|
|
226
233
|
|
|
227
|
-
args.push(
|
|
234
|
+
args.push(searchRoot);
|
|
228
235
|
|
|
229
|
-
const { stdout, stderr, exitCode } = await
|
|
236
|
+
const { stdout, stderr, exitCode } = await runRg(rgPath, args, signal);
|
|
230
237
|
const output = stdout.trim();
|
|
231
238
|
|
|
232
239
|
if (!output) {
|
|
233
|
-
//
|
|
240
|
+
// rg exit codes: 0 = ok, 1 = no matches, other = error
|
|
234
241
|
if (exitCode !== 0 && exitCode !== 1) {
|
|
235
|
-
return { files: [], truncated: false, error: stderr.trim() || `
|
|
242
|
+
return { files: [], truncated: false, error: stderr.trim() || `rg failed (exit ${exitCode})` };
|
|
236
243
|
}
|
|
237
244
|
return { files: [], truncated: false };
|
|
238
245
|
}
|
|
@@ -242,7 +249,10 @@ async function listCandidateFiles(
|
|
|
242
249
|
.map(line => line.replace(/\r$/, "").trim())
|
|
243
250
|
.filter(line => line.length > 0);
|
|
244
251
|
|
|
245
|
-
|
|
252
|
+
const truncated = files.length > MAX_FUZZY_CANDIDATES;
|
|
253
|
+
const limited = truncated ? files.slice(0, MAX_FUZZY_CANDIDATES) : files;
|
|
254
|
+
|
|
255
|
+
return { files: limited, truncated };
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
async function findReadPathSuggestions(
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
Transitions to plan mode for designing implementation approaches before writing code.
|
|
2
|
-
|
|
3
|
-
<conditions>
|
|
4
|
-
Prefer using EnterPlanMode for implementation tasks unless they're simple. Use it when ANY of these conditions apply:
|
|
5
|
-
1. **New Feature Implementation**: Adding meaningful new functionality
|
|
6
|
-
- Example: "Add a logout button" — where should it go? What should happen on click?
|
|
7
|
-
- Example: "Add form validation" — what rules? What error messages?
|
|
8
|
-
2. **Multiple Valid Approaches**: The task can be solved in several different ways
|
|
9
|
-
- Example: "Add caching to the API" — could use Redis, in-memory, file-based, etc.
|
|
10
|
-
- Example: "Improve performance" — many optimization strategies possible
|
|
11
|
-
3. **Code Modifications**: Changes that affect existing behavior or structure
|
|
12
|
-
- Example: "Update the login flow" — what exactly should change?
|
|
13
|
-
- Example: "Refactor this component" — what's the target architecture?
|
|
14
|
-
4. **Architectural Decisions**: The task requires choosing between patterns or technologies
|
|
15
|
-
- Example: "Add real-time updates" — WebSockets vs SSE vs polling
|
|
16
|
-
- Example: "Implement state management" — Redux vs Context vs custom solution
|
|
17
|
-
5. **Multi-File Changes**: The task will likely touch more than 2-3 files
|
|
18
|
-
- Example: "Refactor the authentication system"
|
|
19
|
-
- Example: "Add a new API endpoint with tests"
|
|
20
|
-
6. **Unclear Requirements**: You need to explore before understanding the full scope
|
|
21
|
-
- Example: "Make the app faster" — need to profile and identify bottlenecks
|
|
22
|
-
- Example: "Fix the bug in checkout" — need to investigate root cause
|
|
23
|
-
7. **User Preferences Matter**: The implementation could reasonably go multiple ways
|
|
24
|
-
- If you would use `ask` to clarify the approach, use EnterPlanMode instead
|
|
25
|
-
- Plan mode lets you explore first, then present options with context
|
|
26
|
-
</conditions>
|
|
27
|
-
|
|
28
|
-
<instruction>
|
|
29
|
-
In plan mode:
|
|
30
|
-
1. Explore codebase with `find`, `grep`, `read`, `ls`
|
|
31
|
-
2. Understand existing patterns and architecture
|
|
32
|
-
3. Design implementation approach
|
|
33
|
-
4. Use `ask` if clarification needed
|
|
34
|
-
5. Call `exit_plan_mode` when ready
|
|
35
|
-
</instruction>
|
|
36
|
-
|
|
37
|
-
<output>
|
|
38
|
-
Requires user approval to enter. Once approved, you enter read-only exploration mode with restricted tool access.
|
|
39
|
-
</output>
|
|
40
|
-
|
|
41
|
-
<example name="auth">
|
|
42
|
-
User: "Add user authentication to the app"
|
|
43
|
-
→ Use plan mode: architectural decisions (session vs JWT, where to store tokens, middleware structure)
|
|
44
|
-
</example>
|
|
45
|
-
|
|
46
|
-
<example name="optimization">
|
|
47
|
-
User: "Optimize the database queries"
|
|
48
|
-
→ Use plan mode: multiple approaches possible, need to profile first, significant impact
|
|
49
|
-
</example>
|
|
50
|
-
|
|
51
|
-
<example name="dark-mode">
|
|
52
|
-
User: "Implement dark mode"
|
|
53
|
-
→ Use plan mode: architectural decision on theme system, affects many components
|
|
54
|
-
</example>
|
|
55
|
-
|
|
56
|
-
<example name="delete-button">
|
|
57
|
-
User: "Add a delete button to the user profile"
|
|
58
|
-
→ Use plan mode: seems simple but involves placement, confirmation dialog, API call, error handling, state updates
|
|
59
|
-
</example>
|
|
60
|
-
|
|
61
|
-
<example name="error-handling">
|
|
62
|
-
User: "Update the error handling in the API"
|
|
63
|
-
→ Use plan mode: affects multiple files, user should approve the approach
|
|
64
|
-
</example>
|
|
65
|
-
|
|
66
|
-
<example name="typo-skip">
|
|
67
|
-
User: "Fix the typo in the README"
|
|
68
|
-
→ Skip plan mode: straightforward, no planning needed
|
|
69
|
-
</example>
|
|
70
|
-
|
|
71
|
-
<example name="debug-skip">
|
|
72
|
-
User: "Add a console.log to debug this function"
|
|
73
|
-
→ Skip plan mode: simple, obvious implementation
|
|
74
|
-
</example>
|
|
75
|
-
|
|
76
|
-
<example name="research-skip">
|
|
77
|
-
User: "What files handle routing?"
|
|
78
|
-
→ Skip plan mode: research task, not implementation planning
|
|
79
|
-
</example>
|
|
80
|
-
|
|
81
|
-
<avoid>
|
|
82
|
-
- Single-line or few-line fixes (typos, obvious bugs)
|
|
83
|
-
- Adding a single function with clear requirements
|
|
84
|
-
- Tasks with very specific, detailed instructions
|
|
85
|
-
- Pure research/exploration tasks
|
|
86
|
-
</avoid>
|
|
87
|
-
|
|
88
|
-
<critical>
|
|
89
|
-
- This tool REQUIRES user approval — they must consent to entering plan mode
|
|
90
|
-
- If unsure whether to use it, err on the side of planning — alignment upfront beats rework
|
|
91
|
-
- Users appreciate being consulted before significant changes are made to their codebase
|
|
92
|
-
</critical>
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { Type } from "@sinclair/typebox";
|
|
5
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
6
|
-
import { resolvePlanUrlToPath } from "../internal-urls";
|
|
7
|
-
import enterPlanModeDescription from "../prompts/tools/enter-plan-mode.md" with { type: "text" };
|
|
8
|
-
import type { ToolSession } from ".";
|
|
9
|
-
import { ToolError } from "./tool-errors";
|
|
10
|
-
|
|
11
|
-
const enterPlanModeSchema = Type.Object({
|
|
12
|
-
workflow: Type.Optional(Type.Union([Type.Literal("parallel"), Type.Literal("iterative")])),
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export interface EnterPlanModeDetails {
|
|
16
|
-
planFilePath: string;
|
|
17
|
-
planExists: boolean;
|
|
18
|
-
workflow?: "parallel" | "iterative";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class EnterPlanModeTool implements AgentTool<typeof enterPlanModeSchema, EnterPlanModeDetails> {
|
|
22
|
-
public readonly name = "enter_plan_mode";
|
|
23
|
-
public readonly label = "EnterPlanMode";
|
|
24
|
-
public readonly description: string;
|
|
25
|
-
public readonly parameters = enterPlanModeSchema;
|
|
26
|
-
|
|
27
|
-
private readonly session: ToolSession;
|
|
28
|
-
|
|
29
|
-
constructor(session: ToolSession) {
|
|
30
|
-
this.session = session;
|
|
31
|
-
this.description = renderPromptTemplate(enterPlanModeDescription);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public async execute(
|
|
35
|
-
_toolCallId: string,
|
|
36
|
-
params: { workflow?: "parallel" | "iterative" },
|
|
37
|
-
_signal?: AbortSignal,
|
|
38
|
-
_onUpdate?: AgentToolUpdateCallback<EnterPlanModeDetails>,
|
|
39
|
-
_context?: AgentToolContext,
|
|
40
|
-
): Promise<AgentToolResult<EnterPlanModeDetails>> {
|
|
41
|
-
const state = this.session.getPlanModeState?.();
|
|
42
|
-
if (state?.enabled) {
|
|
43
|
-
throw new ToolError("Plan mode is already active.");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const sessionId = this.session.getSessionId?.();
|
|
47
|
-
if (!sessionId) {
|
|
48
|
-
throw new ToolError("Plan mode requires an active session.");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const settingsManager = this.session.settingsManager;
|
|
52
|
-
if (!settingsManager) {
|
|
53
|
-
throw new ToolError("Settings manager unavailable for plan mode.");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const planFilePath = `plan://${sessionId}/plan.md`;
|
|
57
|
-
const resolvedPlanPath = resolvePlanUrlToPath(planFilePath, {
|
|
58
|
-
getPlansDirectory: settingsManager.getPlansDirectory.bind(settingsManager),
|
|
59
|
-
cwd: this.session.cwd,
|
|
60
|
-
});
|
|
61
|
-
let planExists = false;
|
|
62
|
-
try {
|
|
63
|
-
const stat = await fs.stat(resolvedPlanPath);
|
|
64
|
-
planExists = stat.isFile();
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (!isEnoent(error)) {
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
content: [{ type: "text", text: "Plan mode requested." }],
|
|
73
|
-
details: { planFilePath, planExists, workflow: params.workflow },
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|