@oh-my-pi/pi-coding-agent 10.2.2 → 10.3.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 +51 -0
- package/package.json +7 -7
- package/src/commit/agentic/prompts/analyze-file.md +7 -7
- package/src/commit/agentic/prompts/session-user.md +4 -4
- package/src/commit/agentic/prompts/system.md +14 -16
- package/src/commit/prompts/analysis-system.md +7 -9
- package/src/commit/prompts/analysis-user.md +0 -3
- package/src/commit/prompts/changelog-system.md +14 -19
- package/src/commit/prompts/file-observer-system.md +2 -2
- package/src/commit/prompts/reduce-system.md +13 -23
- package/src/commit/prompts/summary-system.md +7 -21
- package/src/config/settings-schema.ts +135 -56
- package/src/config/settings.ts +0 -6
- package/src/cursor.ts +2 -1
- package/src/extensibility/extensions/index.ts +0 -11
- package/src/extensibility/extensions/types.ts +1 -30
- package/src/extensibility/hooks/types.ts +1 -31
- package/src/index.ts +0 -11
- package/src/ipy/prelude.py +1 -113
- package/src/lsp/index.ts +66 -515
- package/src/lsp/render.ts +0 -11
- package/src/lsp/types.ts +3 -87
- package/src/modes/components/settings-defs.ts +3 -2
- package/src/modes/components/settings-selector.ts +14 -14
- package/src/modes/theme/theme.ts +45 -1
- package/src/prompts/agents/designer.md +23 -27
- package/src/prompts/agents/explore.md +28 -38
- package/src/prompts/agents/init.md +17 -17
- package/src/prompts/agents/plan.md +21 -27
- package/src/prompts/agents/reviewer.md +37 -37
- package/src/prompts/compaction/branch-summary.md +9 -9
- package/src/prompts/compaction/compaction-summary.md +8 -12
- package/src/prompts/compaction/compaction-update-summary.md +17 -19
- package/src/prompts/review-request.md +12 -13
- package/src/prompts/system/custom-system-prompt.md +6 -26
- package/src/prompts/system/plan-mode-active.md +23 -35
- package/src/prompts/system/plan-mode-subagent.md +7 -7
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/system-prompt.md +134 -149
- package/src/prompts/system/web-search.md +10 -10
- package/src/prompts/tools/ask.md +12 -15
- package/src/prompts/tools/bash.md +7 -7
- package/src/prompts/tools/exit-plan-mode.md +6 -6
- package/src/prompts/tools/gemini-image.md +4 -4
- package/src/prompts/tools/grep.md +4 -4
- package/src/prompts/tools/lsp.md +12 -19
- package/src/prompts/tools/patch.md +26 -30
- package/src/prompts/tools/python.md +14 -57
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/replace.md +8 -8
- package/src/prompts/tools/ssh.md +14 -27
- package/src/prompts/tools/task.md +23 -35
- package/src/prompts/tools/todo-write.md +29 -38
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +0 -2
- package/src/session/agent-session.ts +27 -6
- package/src/system-prompt.ts +1 -219
- package/src/task/agents.ts +2 -1
- package/src/tools/bash-interceptor.ts +0 -24
- package/src/tools/bash.ts +1 -7
- package/src/tools/index.ts +8 -3
- package/src/tools/read.ts +74 -17
- package/src/tools/renderers.ts +0 -2
- package/src/lsp/rust-analyzer.ts +0 -184
- package/src/tools/ls.ts +0 -307
|
@@ -5,23 +5,23 @@ Launch agents to handle complex, multi-step tasks autonomously.
|
|
|
5
5
|
<critical>
|
|
6
6
|
## Context is everything
|
|
7
7
|
|
|
8
|
-
Subagents fail
|
|
8
|
+
Subagents fail with vague context. Every task needs:
|
|
9
9
|
1. **Goal** - What this accomplishes (one sentence)
|
|
10
10
|
2. **Constraints** - Hard requirements, banned approaches, naming conventions
|
|
11
11
|
3. **Existing Code** - File paths and function signatures to use as patterns
|
|
12
12
|
4. **API Contract** - If the task produces or consumes an interface, spell it out
|
|
13
13
|
|
|
14
|
-
Subagents CAN grep
|
|
14
|
+
Subagents CAN grep parent conversation file for supplementary details, but CANNOT grep for:
|
|
15
15
|
- Decisions you made but didn't write down
|
|
16
16
|
- Conventions that exist only in your head
|
|
17
17
|
- Which of 50 possible approaches you want
|
|
18
|
-
**Rule of thumb:** If you'd
|
|
18
|
+
**Rule of thumb:** If you'd answer clarifying question for junior dev, info belongs in context.
|
|
19
19
|
</critical>
|
|
20
20
|
|
|
21
21
|
<context-structure>
|
|
22
22
|
## Required context structure
|
|
23
23
|
|
|
24
|
-
Use this template.
|
|
24
|
+
Use this template. Omit sections only if N/A.
|
|
25
25
|
|
|
26
26
|
````
|
|
27
27
|
## Goal
|
|
@@ -33,7 +33,7 @@ Use this template. Sections can be omitted only if truly N/A.
|
|
|
33
33
|
- [What already exists vs what to create]
|
|
34
34
|
|
|
35
35
|
## Existing Code
|
|
36
|
-
Reference files the agent MUST read
|
|
36
|
+
Reference files the agent MUST read/use as patterns:
|
|
37
37
|
- `path/to/file.ts` - [what pattern it demonstrates]
|
|
38
38
|
- `path/to/other.rs` - [what to reuse from it]
|
|
39
39
|
|
|
@@ -50,19 +50,13 @@ fn example(input: Type) -> Result<Output>
|
|
|
50
50
|
{{files}}
|
|
51
51
|
````
|
|
52
52
|
|
|
53
|
-
### Bad context (agent
|
|
53
|
+
### Bad context (agent fails or guesses wrong)
|
|
54
54
|
|
|
55
55
|
```
|
|
56
56
|
N-API migration. Keep highlight sync. Use JsString. No WASM.
|
|
57
57
|
Task: {{description}} Files: {{files}}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Why it fails:
|
|
61
|
-
- No existing code to reference - agent doesn't know your patterns
|
|
62
|
-
- No API contract - agent will invent signatures that don't match consumers
|
|
63
|
-
- No goal - agent doesn't know what success looks like
|
|
64
|
-
- "Keep highlight sync" is meaningless without knowing what highlight is or where it lives
|
|
65
|
-
|
|
66
60
|
### Good context (agent can act confidently)
|
|
67
61
|
|
|
68
62
|
````
|
|
@@ -71,9 +65,9 @@ Port grep module from WASM to N-API, matching existing text module patterns.
|
|
|
71
65
|
|
|
72
66
|
## Constraints
|
|
73
67
|
- Use `#[napi]` attribute macro on all exports (not `#[napi(js_name = "...")]`)
|
|
74
|
-
- Return `napi::Result<T>` for fallible ops
|
|
75
|
-
- Use `spawn_blocking` for
|
|
76
|
-
- Accept `JsString` for string params (NOT JsStringUtf8
|
|
68
|
+
- Return `napi::Result<T>` for fallible ops; never panic
|
|
69
|
+
- Use `spawn_blocking` for filesystem ops or >1ms work
|
|
70
|
+
- Accept `JsString` for string params (NOT JsStringUtf8; lifetime issues)
|
|
77
71
|
- Keep all existing function names - TS bindings depend on them
|
|
78
72
|
- No new crate dependencies
|
|
79
73
|
|
|
@@ -104,36 +98,36 @@ pub async fn search(pattern: JsString, path: JsString, env: Env) -> napi::Result
|
|
|
104
98
|
|
|
105
99
|
<parallelization>
|
|
106
100
|
## When to parallelize vs sequence
|
|
107
|
-
**
|
|
101
|
+
**Test:** Can agent B write correct code without seeing A's output?
|
|
108
102
|
- If YES → parallelize
|
|
109
103
|
- If NO → sequence (A completes, then B runs with A's output in context)
|
|
110
104
|
|
|
111
|
-
###
|
|
105
|
+
### Dependencies that MUST be sequential
|
|
112
106
|
|
|
113
107
|
|First|Then|Why|
|
|
114
108
|
|---|---|---|
|
|
115
|
-
|Create Rust API|Update TS bindings|Bindings need
|
|
116
|
-
|Define interface/types|Implement consumers|Consumers need
|
|
117
|
-
|Scaffold with signatures|Implement bodies|Implementations need
|
|
109
|
+
|Create Rust API|Update TS bindings|Bindings need export names and signatures|
|
|
110
|
+
|Define interface/types|Implement consumers|Consumers need contract|
|
|
111
|
+
|Scaffold with signatures|Implement bodies|Implementations need shape|
|
|
118
112
|
|Core module|Dependent modules|Dependents import from core|
|
|
119
113
|
|
|
120
114
|
### Safe to parallelize
|
|
121
|
-
- Independent modules
|
|
115
|
+
- Independent modules not importing each other
|
|
122
116
|
- Tests for already-implemented code
|
|
123
117
|
- Documentation for stable APIs
|
|
124
118
|
- Refactors in isolated file scopes
|
|
125
119
|
|
|
126
120
|
### Phased execution pattern
|
|
127
121
|
|
|
128
|
-
For migrations/refactors
|
|
122
|
+
For layered migrations/refactors:
|
|
129
123
|
**Phase 1 - Foundation (do yourself or single task):**
|
|
130
|
-
Create
|
|
124
|
+
Create scaffold, define interfaces, establish API shape. Never fan out until contract known.
|
|
131
125
|
**Phase 2 - Parallel implementation:**
|
|
132
|
-
Fan out to independent tasks
|
|
126
|
+
Fan out to independent tasks consuming same known interface. Include Phase 1 API contract in every task's context.
|
|
133
127
|
**Phase 3 - Integration (do yourself):**
|
|
134
|
-
Wire things together, update build/CI, fix
|
|
128
|
+
Wire things together, update build/CI, fix mismatches.
|
|
135
129
|
**Phase 4 - Dependent layer:**
|
|
136
|
-
Fan out again for work
|
|
130
|
+
Fan out again for work consuming Phase 2 outputs.
|
|
137
131
|
|
|
138
132
|
### Example: WASM to N-API migration
|
|
139
133
|
**WRONG** (launched together, will fail):
|
|
@@ -168,15 +162,14 @@ tasks: [
|
|
|
168
162
|
|
|
169
163
|
<parameters>
|
|
170
164
|
- `agent`: Agent type for all tasks
|
|
171
|
-
- `context`: Template with `{{placeholders}}
|
|
165
|
+
- `context`: Template with `{{placeholders}}`; **Must follow structure above**.
|
|
172
166
|
- `isolated`: (optional) Run in git worktree, return patches
|
|
173
167
|
- `tasks`: Array of `{id, description, args}`
|
|
174
168
|
- `id`: CamelCase identifier (max 32 chars)
|
|
175
|
-
- `description`: What
|
|
169
|
+
- `description`: What task does (for logging)
|
|
176
170
|
- `args`: Object with keys matching `{{placeholders}}` in context
|
|
177
171
|
- `skills`: (optional) Skill names to preload
|
|
178
|
-
- `schema`: JTD schema for response structure
|
|
179
|
-
**Schema goes in `schema` parameter. Never describe output format in `context`.**
|
|
172
|
+
- `schema`: JTD schema for response structure (**required**; use typed properties, not `{ "type": "string" }`). **Schema goes in `schema`; never describe output format in `context`.**
|
|
180
173
|
</parameters>
|
|
181
174
|
|
|
182
175
|
<agents>
|
|
@@ -189,11 +182,6 @@ tasks: [
|
|
|
189
182
|
</agents>
|
|
190
183
|
|
|
191
184
|
<avoid>
|
|
192
|
-
- Terse context that requires agents to guess conventions
|
|
193
|
-
- Launching dependent tasks in parallel (bindings + API, consumer + producer)
|
|
194
|
-
- Missing "Existing Code" references - agents need patterns to follow
|
|
195
|
-
- Assuming agents know your codebase - they start fresh each time
|
|
196
|
-
- Describing output format in context instead of schema
|
|
197
185
|
- Single tasks doing too much - prefer focused, file-scoped tasks
|
|
198
186
|
</avoid>
|
|
199
187
|
````
|
|
@@ -1,49 +1,43 @@
|
|
|
1
1
|
# Todo Write
|
|
2
2
|
|
|
3
|
-
Create
|
|
3
|
+
Create/manage structured task list for coding session.
|
|
4
4
|
|
|
5
5
|
<conditions>
|
|
6
|
-
Use
|
|
7
|
-
1. Complex multi-step tasks
|
|
8
|
-
2.
|
|
9
|
-
3. User
|
|
10
|
-
4.
|
|
11
|
-
5.
|
|
12
|
-
6.
|
|
13
|
-
7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
|
|
6
|
+
Use proactively:
|
|
7
|
+
1. Complex multi-step tasks requiring 3+ steps/actions
|
|
8
|
+
2. User requests todo list
|
|
9
|
+
3. User provides multiple tasks (numbered/comma-separated)
|
|
10
|
+
4. After new instructions—capture requirements as todos
|
|
11
|
+
5. Starting task—mark in_progress BEFORE beginning
|
|
12
|
+
6. After completing—mark completed, add follow-up tasks found
|
|
14
13
|
</conditions>
|
|
15
14
|
|
|
16
15
|
<protocol>
|
|
17
|
-
1. **Task States**:
|
|
18
|
-
- pending:
|
|
19
|
-
- in_progress:
|
|
20
|
-
- completed:
|
|
16
|
+
1. **Task States**:
|
|
17
|
+
- pending: not started
|
|
18
|
+
- in_progress: working
|
|
19
|
+
- completed: finished
|
|
21
20
|
2. **Task Management**:
|
|
22
|
-
- Update
|
|
23
|
-
- Mark
|
|
24
|
-
- Multiple tasks may be in_progress
|
|
25
|
-
- Remove tasks
|
|
21
|
+
- Update status in real time
|
|
22
|
+
- Mark complete IMMEDIATELY after finishing (no batching)
|
|
23
|
+
- Multiple tasks may be in_progress in parallel
|
|
24
|
+
- Remove tasks no longer relevant
|
|
26
25
|
3. **Task Completion Requirements**:
|
|
27
|
-
- ONLY mark
|
|
28
|
-
-
|
|
29
|
-
- When blocked, create
|
|
30
|
-
- Never mark a task as completed if:
|
|
31
|
-
- Tests are failing
|
|
32
|
-
- Implementation is partial
|
|
33
|
-
- You encountered unresolved errors
|
|
34
|
-
- You couldn't find necessary files or dependencies
|
|
26
|
+
- ONLY mark completed when FULLY accomplished
|
|
27
|
+
- On errors/blockers/inability to finish, keep in_progress
|
|
28
|
+
- When blocked, create task describing what needs resolving
|
|
35
29
|
4. **Task Breakdown**:
|
|
36
30
|
- Create specific, actionable items
|
|
37
|
-
- Break complex tasks into smaller
|
|
38
|
-
- Use clear, descriptive
|
|
31
|
+
- Break complex tasks into smaller steps
|
|
32
|
+
- Use clear, descriptive names
|
|
39
33
|
</protocol>
|
|
40
34
|
|
|
41
35
|
<output>
|
|
42
|
-
Returns confirmation
|
|
36
|
+
Returns confirmation todo list updated.
|
|
43
37
|
</output>
|
|
44
38
|
|
|
45
39
|
<important>
|
|
46
|
-
When in doubt, use this
|
|
40
|
+
When in doubt, use this.
|
|
47
41
|
</important>
|
|
48
42
|
|
|
49
43
|
<example name="use-dark-mode">
|
|
@@ -53,20 +47,17 @@ User: Add dark mode toggle to settings. Run tests when done.
|
|
|
53
47
|
|
|
54
48
|
<example name="use-features">
|
|
55
49
|
User: Implement user registration, product catalog, shopping cart, checkout.
|
|
56
|
-
→ Creates todos
|
|
50
|
+
→ Creates todos per feature with subtasks
|
|
57
51
|
</example>
|
|
58
52
|
|
|
59
53
|
<example name="skip">
|
|
60
54
|
User: Run npm install / Add a comment to this function / What does git status do?
|
|
61
|
-
→
|
|
55
|
+
→ Do directly. Single-step/informational tasks need no tracking.
|
|
62
56
|
</example>
|
|
63
57
|
|
|
64
58
|
<avoid>
|
|
65
|
-
Skip
|
|
66
|
-
1.
|
|
67
|
-
2.
|
|
68
|
-
3.
|
|
69
|
-
4. The task is purely conversational or informational
|
|
70
|
-
|
|
71
|
-
If there is only one trivial task to do, just do it directly.
|
|
59
|
+
Skip when:
|
|
60
|
+
1. Single straightforward task
|
|
61
|
+
2. Task completable in <3 trivial steps
|
|
62
|
+
3. Task purely conversational/informational
|
|
72
63
|
</avoid>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Write
|
|
2
2
|
|
|
3
|
-
Creates or overwrites
|
|
3
|
+
Creates or overwrites file at specified path.
|
|
4
4
|
|
|
5
5
|
<conditions>
|
|
6
|
-
- Creating new files explicitly required by
|
|
6
|
+
- Creating new files explicitly required by task
|
|
7
7
|
- Replacing entire file contents when editing would be more complex
|
|
8
8
|
</conditions>
|
|
9
9
|
|
|
10
10
|
<output>
|
|
11
|
-
Confirmation of file creation/write with path. When LSP
|
|
11
|
+
Confirmation of file creation/write with path. When LSP available, content may be auto-formatted before writing and diagnostics returned. Returns error if write fails (permissions, invalid path, disk full).
|
|
12
12
|
</output>
|
|
13
13
|
|
|
14
14
|
<critical>
|
package/src/sdk.ts
CHANGED
|
@@ -96,7 +96,6 @@ import {
|
|
|
96
96
|
FindTool,
|
|
97
97
|
GrepTool,
|
|
98
98
|
getWebSearchTools,
|
|
99
|
-
LsTool,
|
|
100
99
|
loadSshTool,
|
|
101
100
|
PythonTool,
|
|
102
101
|
ReadTool,
|
|
@@ -235,7 +234,6 @@ export {
|
|
|
235
234
|
FindTool,
|
|
236
235
|
GrepTool,
|
|
237
236
|
loadSshTool,
|
|
238
|
-
LsTool,
|
|
239
237
|
PythonTool,
|
|
240
238
|
ReadTool,
|
|
241
239
|
WriteTool,
|
|
@@ -2461,13 +2461,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2461
2461
|
* Check if agent stopped with incomplete todos and prompt to continue.
|
|
2462
2462
|
*/
|
|
2463
2463
|
private async _checkTodoCompletion(): Promise<void> {
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2464
|
+
const remindersEnabled = this.settings.get("todo.reminders");
|
|
2465
|
+
const todosEnabled = this.settings.get("todo.enabled");
|
|
2466
|
+
if (!remindersEnabled || !todosEnabled) {
|
|
2466
2467
|
this._todoReminderCount = 0;
|
|
2467
2468
|
return;
|
|
2468
2469
|
}
|
|
2469
2470
|
|
|
2470
|
-
|
|
2471
|
+
const remindersMax = this.settings.get("todo.reminders.max");
|
|
2472
|
+
if (this._todoReminderCount >= remindersMax) {
|
|
2471
2473
|
logger.debug("Todo completion: max reminders reached", { count: this._todoReminderCount });
|
|
2472
2474
|
return;
|
|
2473
2475
|
}
|
|
@@ -2503,7 +2505,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2503
2505
|
`<system_reminder>\n` +
|
|
2504
2506
|
`You stopped with ${incomplete.length} incomplete todo item(s):\n${todoList}\n\n` +
|
|
2505
2507
|
`Please continue working on these tasks or mark them complete if finished.\n` +
|
|
2506
|
-
`(Reminder ${this._todoReminderCount}/${
|
|
2508
|
+
`(Reminder ${this._todoReminderCount}/${remindersMax})\n` +
|
|
2507
2509
|
`</system_reminder>`;
|
|
2508
2510
|
|
|
2509
2511
|
logger.debug("Todo completion: sending reminder", {
|
|
@@ -2516,7 +2518,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2516
2518
|
type: "todo_reminder",
|
|
2517
2519
|
todos: incomplete,
|
|
2518
2520
|
attempt: this._todoReminderCount,
|
|
2519
|
-
maxAttempts:
|
|
2521
|
+
maxAttempts: remindersMax,
|
|
2520
2522
|
});
|
|
2521
2523
|
|
|
2522
2524
|
// Inject reminder and continue the conversation
|
|
@@ -3811,13 +3813,32 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3811
3813
|
|
|
3812
3814
|
// Include available tools
|
|
3813
3815
|
const tools = this.agent.state.tools;
|
|
3816
|
+
|
|
3817
|
+
// Recursively strip all fields starting with 'TypeBox.' from an object
|
|
3818
|
+
function stripTypeBoxFields(obj: any): any {
|
|
3819
|
+
if (Array.isArray(obj)) {
|
|
3820
|
+
return obj.map(stripTypeBoxFields);
|
|
3821
|
+
}
|
|
3822
|
+
if (obj && typeof obj === "object") {
|
|
3823
|
+
const result: Record<string, any> = {};
|
|
3824
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
3825
|
+
if (!k.startsWith("TypeBox.")) {
|
|
3826
|
+
result[k] = stripTypeBoxFields(v);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
return result;
|
|
3830
|
+
}
|
|
3831
|
+
return obj;
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3814
3834
|
if (tools.length > 0) {
|
|
3815
3835
|
lines.push("## Available Tools\n");
|
|
3816
3836
|
for (const tool of tools) {
|
|
3817
3837
|
lines.push(`### ${tool.name}\n`);
|
|
3818
3838
|
lines.push(tool.description);
|
|
3819
3839
|
lines.push("\n```yaml");
|
|
3820
|
-
|
|
3840
|
+
const parametersClean = stripTypeBoxFields(tool.parameters);
|
|
3841
|
+
lines.push(YAML.stringify(parametersClean, null, 2));
|
|
3821
3842
|
lines.push("```\n");
|
|
3822
3843
|
}
|
|
3823
3844
|
lines.push("\n");
|
package/src/system-prompt.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System prompt construction and project context loading
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from "node:fs/promises";
|
|
5
4
|
import * as os from "node:os";
|
|
6
5
|
import * as path from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { getSystemInfo as getNativeSystemInfo, type SystemInfo } from "@oh-my-pi/pi-natives";
|
|
9
7
|
import { $ } from "bun";
|
|
10
8
|
import chalk from "chalk";
|
|
11
9
|
import { contextFileCapability } from "./capability/context-file";
|
|
@@ -106,24 +104,6 @@ function parseWmicTable(output: string, header: string): string | null {
|
|
|
106
104
|
|
|
107
105
|
const AGENTS_MD_PATTERN = "**/AGENTS.md";
|
|
108
106
|
const AGENTS_MD_LIMIT = 200;
|
|
109
|
-
const PROJECT_TREE_LIMIT = 2000;
|
|
110
|
-
const PROJECT_TREE_PER_DIR_LIMIT = 10;
|
|
111
|
-
const PROJECT_TREE_PER_DIR_DEPTH = 2;
|
|
112
|
-
const PROJECT_TREE_IGNORED = new Set([
|
|
113
|
-
".git",
|
|
114
|
-
".hg",
|
|
115
|
-
".svn",
|
|
116
|
-
".next",
|
|
117
|
-
".turbo",
|
|
118
|
-
".cache",
|
|
119
|
-
".venv",
|
|
120
|
-
".idea",
|
|
121
|
-
".vscode",
|
|
122
|
-
"build",
|
|
123
|
-
"dist",
|
|
124
|
-
"node_modules",
|
|
125
|
-
"target",
|
|
126
|
-
]);
|
|
127
107
|
|
|
128
108
|
interface AgentsMdSearch {
|
|
129
109
|
scopePath: string;
|
|
@@ -161,201 +141,6 @@ function buildAgentsMdSearch(cwd: string): AgentsMdSearch {
|
|
|
161
141
|
};
|
|
162
142
|
}
|
|
163
143
|
|
|
164
|
-
type ProjectTreeEntry = {
|
|
165
|
-
name: string;
|
|
166
|
-
isDirectory: boolean;
|
|
167
|
-
path: string;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
type ProjectTreeScan = {
|
|
171
|
-
children: Map<string, ProjectTreeEntry[]>;
|
|
172
|
-
truncated: boolean;
|
|
173
|
-
truncatedDirs: Set<string>;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const GLOB_TIMEOUT_MS = 5000;
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Scan project tree using ripgrep-wasm find with exclusion filters.
|
|
180
|
-
* Returns null if scan fails.
|
|
181
|
-
*/
|
|
182
|
-
async function scanProjectTreeWithGlob(root: string): Promise<ProjectTreeScan | null> {
|
|
183
|
-
let entries: string[];
|
|
184
|
-
const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
|
|
185
|
-
try {
|
|
186
|
-
const result = await untilAborted(timeoutSignal, () =>
|
|
187
|
-
glob({
|
|
188
|
-
pattern: "**/*",
|
|
189
|
-
path: root,
|
|
190
|
-
fileType: FileType.File,
|
|
191
|
-
}),
|
|
192
|
-
);
|
|
193
|
-
entries = result.matches.map(match => match.path).filter(entry => entry.length > 0);
|
|
194
|
-
} catch {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Build directory contents map from file list
|
|
199
|
-
// Map<dirPath, Map<entryPath, isDirectory>>
|
|
200
|
-
const dirContents = new Map<string, Map<string, boolean>>();
|
|
201
|
-
dirContents.set(root, new Map());
|
|
202
|
-
|
|
203
|
-
for (const entry of entries) {
|
|
204
|
-
const filePath = entry;
|
|
205
|
-
if (!filePath) continue;
|
|
206
|
-
const absolutePath = path.join(root, filePath);
|
|
207
|
-
// Check static ignores on path components
|
|
208
|
-
const relative = path.relative(root, absolutePath);
|
|
209
|
-
const parts = relative.split(path.sep);
|
|
210
|
-
if (parts.some(p => PROJECT_TREE_IGNORED.has(p))) continue;
|
|
211
|
-
|
|
212
|
-
// Add file to its parent directory
|
|
213
|
-
const parent = path.dirname(absolutePath);
|
|
214
|
-
if (!dirContents.has(parent)) dirContents.set(parent, new Map());
|
|
215
|
-
dirContents.get(parent)!.set(absolutePath, false);
|
|
216
|
-
|
|
217
|
-
// Add all intermediate directories
|
|
218
|
-
let dir = parent;
|
|
219
|
-
while (dir.length >= root.length && dir !== path.dirname(dir)) {
|
|
220
|
-
const parentDir = path.dirname(dir);
|
|
221
|
-
if (!dirContents.has(parentDir)) dirContents.set(parentDir, new Map());
|
|
222
|
-
dirContents.get(parentDir)!.set(dir, true);
|
|
223
|
-
dir = parentDir;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// BFS to build the tree with limits
|
|
228
|
-
const children = new Map<string, ProjectTreeEntry[]>();
|
|
229
|
-
let entryCount = 0;
|
|
230
|
-
let truncated = false;
|
|
231
|
-
const truncatedDirs = new Set<string>();
|
|
232
|
-
|
|
233
|
-
const queue: Array<{ dirPath: string; depth: number }> = [{ dirPath: root, depth: 0 }];
|
|
234
|
-
let cursor = 0;
|
|
235
|
-
|
|
236
|
-
while (cursor < queue.length && !truncated) {
|
|
237
|
-
const { dirPath, depth } = queue[cursor];
|
|
238
|
-
cursor += 1;
|
|
239
|
-
|
|
240
|
-
const contents = dirContents.get(dirPath);
|
|
241
|
-
if (!contents || contents.size === 0) continue;
|
|
242
|
-
|
|
243
|
-
// Get stats for sorting
|
|
244
|
-
const entries = Array.from(contents.entries());
|
|
245
|
-
const withStats = await Promise.all(
|
|
246
|
-
entries.map(async ([entryPath, isDirectory]) => {
|
|
247
|
-
try {
|
|
248
|
-
const stats = await fs.stat(entryPath);
|
|
249
|
-
return { entryPath, isDirectory, mtimeMs: stats.mtimeMs };
|
|
250
|
-
} catch {
|
|
251
|
-
return { entryPath, isDirectory, mtimeMs: 0 };
|
|
252
|
-
}
|
|
253
|
-
}),
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
withStats.sort((a, b) => {
|
|
257
|
-
if (a.mtimeMs !== b.mtimeMs) return b.mtimeMs - a.mtimeMs;
|
|
258
|
-
return path.basename(a.entryPath).localeCompare(path.basename(b.entryPath));
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const perDirLimit = depth >= PROJECT_TREE_PER_DIR_DEPTH ? PROJECT_TREE_PER_DIR_LIMIT : null;
|
|
262
|
-
const limited = perDirLimit === null ? withStats : withStats.slice(0, perDirLimit);
|
|
263
|
-
const hasMoreEntries = perDirLimit !== null && withStats.length > perDirLimit;
|
|
264
|
-
|
|
265
|
-
const mapped: ProjectTreeEntry[] = [];
|
|
266
|
-
for (const { entryPath, isDirectory } of limited) {
|
|
267
|
-
if (entryCount >= PROJECT_TREE_LIMIT) {
|
|
268
|
-
truncated = true;
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
mapped.push({
|
|
273
|
-
name: path.basename(entryPath),
|
|
274
|
-
isDirectory,
|
|
275
|
-
path: entryPath,
|
|
276
|
-
});
|
|
277
|
-
entryCount += 1;
|
|
278
|
-
|
|
279
|
-
if (isDirectory) {
|
|
280
|
-
queue.push({ dirPath: entryPath, depth: depth + 1 });
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (!truncated && hasMoreEntries) {
|
|
285
|
-
truncatedDirs.add(dirPath);
|
|
286
|
-
}
|
|
287
|
-
children.set(dirPath, mapped);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return { children, truncated, truncatedDirs };
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function scanProjectTree(root: string): Promise<ProjectTreeScan> {
|
|
294
|
-
const globResult = await scanProjectTreeWithGlob(root);
|
|
295
|
-
if (globResult) return globResult;
|
|
296
|
-
return { children: new Map(), truncated: false, truncatedDirs: new Set() };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function renderProjectTree(scan: ProjectTreeScan, root: string): string {
|
|
300
|
-
const lines: string[] = [];
|
|
301
|
-
|
|
302
|
-
const collapseDir = (dirPath: string): { path: string; entries: ProjectTreeEntry[] } | null => {
|
|
303
|
-
let currentPath = dirPath;
|
|
304
|
-
while (true) {
|
|
305
|
-
const entries = scan.children.get(currentPath);
|
|
306
|
-
if (!entries || entries.length === 0) return null;
|
|
307
|
-
const files = entries.filter(entry => !entry.isDirectory);
|
|
308
|
-
const dirs = entries.filter(entry => entry.isDirectory);
|
|
309
|
-
if (files.length === 0 && dirs.length === 1 && !scan.truncatedDirs.has(currentPath)) {
|
|
310
|
-
currentPath = dirs[0].path;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
return { path: currentPath, entries };
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const renderDir = (dirPath: string, indent: string, isRoot: boolean): void => {
|
|
318
|
-
const collapsed = collapseDir(dirPath);
|
|
319
|
-
if (!collapsed) return;
|
|
320
|
-
const { path: collapsedPath, entries } = collapsed;
|
|
321
|
-
|
|
322
|
-
// For non-root directories, print the header and indent contents
|
|
323
|
-
const contentIndent = isRoot ? indent : `${indent} `;
|
|
324
|
-
if (!isRoot) {
|
|
325
|
-
const relative = path.relative(root, collapsedPath) || ".";
|
|
326
|
-
lines.push(`${indent}@ ${relative}`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const files = entries.filter(entry => !entry.isDirectory);
|
|
330
|
-
const dirs = entries.filter(entry => entry.isDirectory);
|
|
331
|
-
|
|
332
|
-
for (const entry of files) {
|
|
333
|
-
lines.push(`${contentIndent}- ${entry.name}`);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (scan.truncatedDirs.has(collapsedPath)) {
|
|
337
|
-
lines.push(`${contentIndent}- …`);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
for (const entry of dirs) {
|
|
341
|
-
renderDir(entry.path, contentIndent, false);
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
renderDir(root, "", true);
|
|
346
|
-
|
|
347
|
-
if (scan.truncated) {
|
|
348
|
-
lines.push("…");
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return lines.join("\n");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
async function buildProjectTreeSnapshot(root: string): Promise<string> {
|
|
355
|
-
const scan = await scanProjectTree(root);
|
|
356
|
-
return renderProjectTree(scan, root);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
144
|
async function getGpuModel(): Promise<string | null> {
|
|
360
145
|
switch (process.platform) {
|
|
361
146
|
case "win32": {
|
|
@@ -704,7 +489,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
704
489
|
// Resolve context files: use provided or discover
|
|
705
490
|
const contextFiles = providedContextFiles ?? (await loadProjectContextFiles({ cwd: resolvedCwd }));
|
|
706
491
|
const agentsMdSearch = buildAgentsMdSearch(resolvedCwd);
|
|
707
|
-
const projectTree = await buildProjectTreeSnapshot(resolvedCwd);
|
|
708
492
|
|
|
709
493
|
// Build tool descriptions array
|
|
710
494
|
// Priority: toolNames (explicit list) > tools (Map) > defaults
|
|
@@ -742,7 +526,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
742
526
|
customPrompt: resolvedCustomPrompt,
|
|
743
527
|
appendPrompt: resolvedAppendPrompt ?? "",
|
|
744
528
|
contextFiles,
|
|
745
|
-
projectTree,
|
|
746
529
|
agentsMdSearch,
|
|
747
530
|
git,
|
|
748
531
|
skills: filteredSkills,
|
|
@@ -759,7 +542,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
759
542
|
environment: await getEnvironmentInfo(),
|
|
760
543
|
systemPromptCustomization: systemPromptCustomization ?? "",
|
|
761
544
|
contextFiles,
|
|
762
|
-
projectTree,
|
|
763
545
|
agentsMdSearch,
|
|
764
546
|
git,
|
|
765
547
|
skills: filteredSkills,
|
package/src/task/agents.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { AgentDefinition, AgentSource } from "./types";
|
|
|
18
18
|
interface AgentFrontmatter {
|
|
19
19
|
name: string;
|
|
20
20
|
description: string;
|
|
21
|
+
tools?: string[];
|
|
21
22
|
spawns?: string;
|
|
22
23
|
model?: string | string[];
|
|
23
24
|
thinkingLevel?: string;
|
|
@@ -54,7 +55,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
54
55
|
fileName: "quick_task.md",
|
|
55
56
|
frontmatter: {
|
|
56
57
|
name: "quick_task",
|
|
57
|
-
description: "
|
|
58
|
+
description: "Low-reasoning agent for strictly mechanical updates or data collection only",
|
|
58
59
|
model: "pi/smol",
|
|
59
60
|
},
|
|
60
61
|
template: taskMd,
|
|
@@ -103,27 +103,3 @@ export function checkBashInterception(
|
|
|
103
103
|
|
|
104
104
|
return { block: false };
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Check if a command is a simple directory listing that should use `ls` tool.
|
|
109
|
-
* Only applies to bare `ls` without complex flags.
|
|
110
|
-
*/
|
|
111
|
-
export function checkSimpleLsInterception(command: string, availableTools: string[]): InterceptionResult {
|
|
112
|
-
if (!availableTools.includes("ls")) {
|
|
113
|
-
return { block: false };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Match simple ls commands (ls, ls -la, ls /path, etc.)
|
|
117
|
-
// Don't intercept complex pipes or commands
|
|
118
|
-
const simpleLsPattern = /^\s*ls(\s+(-[a-zA-Z]+\s*)*)?(\s+[^|;&]+)?\s*$/;
|
|
119
|
-
|
|
120
|
-
if (simpleLsPattern.test(command.trim())) {
|
|
121
|
-
return {
|
|
122
|
-
block: true,
|
|
123
|
-
message: `Use the \`ls\` tool instead of bash ls. It provides structured output.\n\nOriginal command: ${command}`,
|
|
124
|
-
suggestedTool: "ls",
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return { block: false };
|
|
129
|
-
}
|