@probelabs/probe 0.6.0-rc253 → 0.6.0-rc255
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/README.md +166 -3
- package/bin/binaries/probe-v0.6.0-rc255-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc255-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc255-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc255-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc255-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.d.ts +1 -1
- package/build/agent/ProbeAgent.js +51 -16
- package/build/agent/acp/tools.js +2 -1
- package/build/agent/acp/tools.test.js +2 -1
- package/build/agent/dsl/environment.js +19 -0
- package/build/agent/index.js +1512 -413
- package/build/agent/schemaUtils.js +91 -2
- package/build/agent/tools.js +0 -28
- package/build/delegate.js +3 -0
- package/build/index.js +2 -0
- package/build/tools/common.js +6 -5
- package/build/tools/edit.js +457 -65
- package/build/tools/executePlan.js +3 -1
- package/build/tools/fileTracker.js +318 -0
- package/build/tools/fuzzyMatch.js +271 -0
- package/build/tools/hashline.js +131 -0
- package/build/tools/lineEditHeuristics.js +138 -0
- package/build/tools/symbolEdit.js +119 -0
- package/build/tools/vercel.js +40 -9
- package/cjs/agent/ProbeAgent.cjs +1615 -517
- package/cjs/index.cjs +1643 -543
- package/index.d.ts +189 -1
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +1 -1
- package/src/agent/ProbeAgent.js +51 -16
- package/src/agent/acp/tools.js +2 -1
- package/src/agent/acp/tools.test.js +2 -1
- package/src/agent/dsl/environment.js +19 -0
- package/src/agent/index.js +14 -3
- package/src/agent/schemaUtils.js +91 -2
- package/src/agent/tools.js +0 -28
- package/src/delegate.js +3 -0
- package/src/index.js +2 -0
- package/src/tools/common.js +6 -5
- package/src/tools/edit.js +457 -65
- package/src/tools/executePlan.js +3 -1
- package/src/tools/fileTracker.js +318 -0
- package/src/tools/fuzzyMatch.js +271 -0
- package/src/tools/hashline.js +131 -0
- package/src/tools/lineEditHeuristics.js +138 -0
- package/src/tools/symbolEdit.js +119 -0
- package/src/tools/vercel.js +40 -9
- package/bin/binaries/probe-v0.6.0-rc253-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc253-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc253-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc253-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc253-x86_64-unknown-linux-musl.tar.gz +0 -0
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ During installation, the package will automatically download the appropriate pro
|
|
|
23
23
|
- **Search Code**: Search for patterns in your codebase using Elasticsearch-like query syntax
|
|
24
24
|
- **Query Code**: Find specific code structures using tree-sitter patterns
|
|
25
25
|
- **Extract Code**: Extract code blocks from files based on file paths and line numbers
|
|
26
|
+
- **Edit Code**: AI-powered code editing with text replacement (fuzzy matching), AST-aware symbol replace/insert across 16 languages, and line-targeted editing with optional hash-based integrity verification
|
|
26
27
|
- **AI Tools Integration**: Ready-to-use tools for Vercel AI SDK, LangChain, and other AI frameworks
|
|
27
28
|
- **System Message**: Default system message for AI assistants with instructions on using probe tools
|
|
28
29
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
@@ -91,7 +92,7 @@ const agent = new ProbeAgent({
|
|
|
91
92
|
path: '/path/to/your/project',
|
|
92
93
|
provider: 'anthropic', // or 'openai', 'google'
|
|
93
94
|
model: 'claude-3-5-sonnet-20241022', // Optional: override model
|
|
94
|
-
allowEdit:
|
|
95
|
+
allowEdit: true, // Optional: enable edit + create tools for code modification
|
|
95
96
|
debug: true, // Optional: enable debug logging
|
|
96
97
|
allowedTools: ['*'], // Optional: filter available tools (see Tool Filtering below)
|
|
97
98
|
enableMcp: true, // Optional: enable MCP tool integration
|
|
@@ -292,8 +293,7 @@ const agent5 = new ProbeAgent({
|
|
|
292
293
|
- `listFiles` - List files and directories
|
|
293
294
|
- `searchFiles` - Find files by glob pattern
|
|
294
295
|
- `bash` - Execute bash commands (requires `enableBash: true`)
|
|
295
|
-
- `
|
|
296
|
-
- `edit` - Edit files with exact string replacement (requires `allowEdit: true`)
|
|
296
|
+
- `edit` - Edit files with text replacement, AST-aware symbol editing, or line-targeted editing (requires `allowEdit: true`)
|
|
297
297
|
- `create` - Create new files (requires `allowEdit: true`)
|
|
298
298
|
- `delegate` - Delegate tasks to subagents (requires `enableDelegate: true`)
|
|
299
299
|
- `attempt_completion` - Signal task completion
|
|
@@ -327,6 +327,168 @@ probe agent "Analyze the architecture" --allowed-tools all
|
|
|
327
327
|
- Blocked tools will not appear in the system message and cannot be executed
|
|
328
328
|
- Use `allowedTools: []` for pure conversational AI without code analysis tools
|
|
329
329
|
|
|
330
|
+
### Code Editing (Edit & Create Tools)
|
|
331
|
+
|
|
332
|
+
Probe provides built-in code modification tools that work across all interfaces. The `edit` tool supports four modes: text-based find/replace with fuzzy matching, AST-aware symbol replacement, symbol insertion, and line-targeted editing with optional hash-based integrity verification.
|
|
333
|
+
|
|
334
|
+
#### Enabling Edit Tools
|
|
335
|
+
|
|
336
|
+
**CLI Agent:**
|
|
337
|
+
```bash
|
|
338
|
+
# Enable with --allow-edit flag
|
|
339
|
+
probe agent "Fix the bug in auth.js" --allow-edit --path ./my-project
|
|
340
|
+
|
|
341
|
+
# Enable with environment variable
|
|
342
|
+
ALLOW_EDIT=1 probe agent "Refactor the login flow" --path ./my-project
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**SDK (ProbeAgent):**
|
|
346
|
+
```javascript
|
|
347
|
+
import { ProbeAgent } from '@probelabs/probe';
|
|
348
|
+
|
|
349
|
+
const agent = new ProbeAgent({
|
|
350
|
+
path: '/path/to/project',
|
|
351
|
+
allowEdit: true, // enables edit + create tools
|
|
352
|
+
// hashLines defaults to true when allowEdit is true (line integrity verification)
|
|
353
|
+
provider: 'anthropic'
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// The agent can now modify files when answering questions
|
|
357
|
+
const answer = await agent.answer("Fix the off-by-one error in calculateTotal");
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**SDK (Standalone Tools):**
|
|
361
|
+
```javascript
|
|
362
|
+
import { editTool, createTool } from '@probelabs/probe';
|
|
363
|
+
|
|
364
|
+
const edit = editTool({
|
|
365
|
+
allowedFolders: ['/path/to/project'],
|
|
366
|
+
cwd: '/path/to/project'
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const create = createTool({
|
|
370
|
+
allowedFolders: ['/path/to/project'],
|
|
371
|
+
cwd: '/path/to/project'
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
#### Edit Tool Modes
|
|
376
|
+
|
|
377
|
+
| Mode | Parameters | Use Case |
|
|
378
|
+
|------|-----------|----------|
|
|
379
|
+
| **Text edit** | `old_string` + `new_string` | Small, precise changes: fix a condition, rename a variable, update a value |
|
|
380
|
+
| **Symbol replace** | `symbol` + `new_string` | Replace an entire function/class/method by name (AST-aware, 16 languages) |
|
|
381
|
+
| **Symbol insert** | `symbol` + `new_string` + `position` | Insert new code before or after an existing symbol |
|
|
382
|
+
| **Line-targeted edit** | `start_line` + `new_string` | Edit specific lines from extract/search output; ideal for changes inside large functions |
|
|
383
|
+
|
|
384
|
+
#### Edit Tool Parameters
|
|
385
|
+
|
|
386
|
+
| Parameter | Required | Description |
|
|
387
|
+
|-----------|----------|-------------|
|
|
388
|
+
| `file_path` | Yes | Path to the file to edit (absolute or relative to cwd) |
|
|
389
|
+
| `new_string` | Yes | Replacement text or new code content |
|
|
390
|
+
| `old_string` | No | Text to find and replace. Copy verbatim from the file. |
|
|
391
|
+
| `replace_all` | No | Replace all occurrences (default: `false`, text mode only) |
|
|
392
|
+
| `symbol` | No | Code symbol name for AST-aware editing (e.g. `"myFunction"`, `"MyClass.myMethod"`) |
|
|
393
|
+
| `position` | No | `"before"` or `"after"` — insert near the symbol or line instead of replacing it |
|
|
394
|
+
| `start_line` | No | Line reference for line-targeted editing (e.g. `"42"` or `"42:ab"` with hash) |
|
|
395
|
+
| `end_line` | No | End of line range, inclusive (e.g. `"55"` or `"55:cd"`). Defaults to `start_line`. |
|
|
396
|
+
|
|
397
|
+
#### Examples
|
|
398
|
+
|
|
399
|
+
**Text edit — find and replace:**
|
|
400
|
+
```javascript
|
|
401
|
+
await edit.execute({
|
|
402
|
+
file_path: 'src/main.js',
|
|
403
|
+
old_string: 'return false;',
|
|
404
|
+
new_string: 'return true;'
|
|
405
|
+
});
|
|
406
|
+
// => "Successfully edited src/main.js (1 replacement)"
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Text edit — fuzzy matching handles whitespace differences:**
|
|
410
|
+
```javascript
|
|
411
|
+
// File has: " const x = 1;" (2-space indent)
|
|
412
|
+
// Your search string omits the indent — fuzzy matching finds it anyway
|
|
413
|
+
await edit.execute({
|
|
414
|
+
file_path: 'src/main.js',
|
|
415
|
+
old_string: 'const x = 1;',
|
|
416
|
+
new_string: ' const x = 2;'
|
|
417
|
+
});
|
|
418
|
+
// => "Successfully edited src/main.js (1 replacement, matched via line-trimmed)"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Symbol replace — rewrite a function by name:**
|
|
422
|
+
```javascript
|
|
423
|
+
await edit.execute({
|
|
424
|
+
file_path: 'src/utils.js',
|
|
425
|
+
symbol: 'calculateTotal',
|
|
426
|
+
new_string: `function calculateTotal(items) {
|
|
427
|
+
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
428
|
+
}`
|
|
429
|
+
});
|
|
430
|
+
// => "Successfully replaced symbol "calculateTotal" in src/utils.js (was lines 10-15, now 3 lines)"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Symbol insert — add a new function after an existing one:**
|
|
434
|
+
```javascript
|
|
435
|
+
await edit.execute({
|
|
436
|
+
file_path: 'src/utils.js',
|
|
437
|
+
symbol: 'calculateTotal',
|
|
438
|
+
position: 'after',
|
|
439
|
+
new_string: `function calculateTax(total, rate) {
|
|
440
|
+
return total * rate;
|
|
441
|
+
}`
|
|
442
|
+
});
|
|
443
|
+
// => "Successfully inserted 3 lines after symbol "calculateTotal" in src/utils.js (at line 16)"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Line-targeted edit — edit specific lines from extract output:**
|
|
447
|
+
```javascript
|
|
448
|
+
// After extracting "src/order.js#processOrder" and seeing lines 143-145
|
|
449
|
+
await edit.execute({
|
|
450
|
+
file_path: 'src/order.js',
|
|
451
|
+
start_line: '143',
|
|
452
|
+
end_line: '145',
|
|
453
|
+
new_string: ' const items = order.items.filter(i => i.active);'
|
|
454
|
+
});
|
|
455
|
+
// => "Successfully edited src/order.js (lines 143-145 replaced with 1 line)"
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Line-targeted edit — with hash integrity verification:**
|
|
459
|
+
```javascript
|
|
460
|
+
// Use the hash from extract output (e.g. "143:cd") for verification
|
|
461
|
+
await edit.execute({
|
|
462
|
+
file_path: 'src/order.js',
|
|
463
|
+
start_line: '143:cd',
|
|
464
|
+
new_string: ' const items = order.items.filter(i => i.active);'
|
|
465
|
+
});
|
|
466
|
+
// If hash matches: edit succeeds
|
|
467
|
+
// If hash doesn't match: error with updated reference for retry
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Create a new file:**
|
|
471
|
+
```javascript
|
|
472
|
+
await create.execute({
|
|
473
|
+
file_path: 'src/newModule.js',
|
|
474
|
+
content: `export function greet(name) {
|
|
475
|
+
return \`Hello, \${name}!\`;
|
|
476
|
+
}`
|
|
477
|
+
});
|
|
478
|
+
// => "Successfully created src/newModule.js (52 bytes)"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
#### Key Features
|
|
482
|
+
|
|
483
|
+
- **Fuzzy matching**: Text mode automatically handles minor whitespace and indentation differences between your search string and the actual file content. Four strategies are tried in order: exact match, line-trimmed, whitespace-normalized, indent-flexible.
|
|
484
|
+
- **AST-aware symbol editing**: Symbol mode uses tree-sitter to locate functions, classes, and methods by name across 16 languages (JavaScript, TypeScript, Python, Rust, Go, Java, C, C++, Ruby, PHP, Swift, Kotlin, Scala, C#, Lua, Zig).
|
|
485
|
+
- **Auto-indentation**: Symbol mode automatically adjusts the indentation of `new_string` to match the original symbol's indentation level.
|
|
486
|
+
- **Line-targeted editing**: Use line numbers from `extract`/`search` output to make precise edits inside large functions. Optional hash-based integrity verification detects stale references.
|
|
487
|
+
- **Extract→Edit workflow**: Extract a function by symbol name (e.g. `"file.js#myFunction"`), then use the line numbers from the output to surgically edit specific lines with `start_line`/`end_line`.
|
|
488
|
+
- **File state tracking**: Automatic per-session tracking ensures edits are never based on stale content. Files read via `search`/`extract` are tracked; edits to unread or modified files are blocked with recovery instructions.
|
|
489
|
+
- **Self-healing errors**: All error messages include specific recovery instructions telling you (or the LLM) exactly how to fix the call and retry.
|
|
490
|
+
- **Security**: Path traversal attacks are blocked — all file operations are restricted to `allowedFolders`.
|
|
491
|
+
|
|
330
492
|
### Using as an MCP Server
|
|
331
493
|
|
|
332
494
|
Probe includes a built-in MCP (Model Context Protocol) server for integration with AI assistants:
|
|
@@ -844,6 +1006,7 @@ probe mcp --timeout 60
|
|
|
844
1006
|
|
|
845
1007
|
## Additional Documentation
|
|
846
1008
|
|
|
1009
|
+
- [Edit & Create Tools](./docs/EDIT_CREATE_TOOLS.md) - Code editing with fuzzy matching, AST-aware symbol editing, and file creation
|
|
847
1010
|
- [Context Window Compaction](./CONTEXT_COMPACTION.md) - Automatic conversation history compression
|
|
848
1011
|
- [MCP Integration](./MCP_INTEGRATION_SUMMARY.md) - Model Context Protocol support details
|
|
849
1012
|
- [Delegate Tool](./DELEGATE_TOOL_README.md) - Task distribution to subagents
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -35,7 +35,7 @@ export interface ProbeAgentOptions {
|
|
|
35
35
|
systemPrompt?: string;
|
|
36
36
|
/** Predefined prompt type (persona) */
|
|
37
37
|
promptType?: 'code-explorer' | 'code-searcher' | 'engineer' | 'code-review' | 'support' | 'architect';
|
|
38
|
-
/** Allow the use of the '
|
|
38
|
+
/** Allow the use of the 'edit' and 'create' tools for code editing */
|
|
39
39
|
allowEdit?: boolean;
|
|
40
40
|
/** Enable the delegate tool for task distribution to subagents */
|
|
41
41
|
enableDelegate?: boolean;
|
|
@@ -57,7 +57,6 @@ import {
|
|
|
57
57
|
useSkillToolDefinition,
|
|
58
58
|
readImageToolDefinition,
|
|
59
59
|
attemptCompletionToolDefinition,
|
|
60
|
-
implementToolDefinition,
|
|
61
60
|
editToolDefinition,
|
|
62
61
|
createToolDefinition,
|
|
63
62
|
googleSearchToolDefinition,
|
|
@@ -66,6 +65,7 @@ import {
|
|
|
66
65
|
parseXmlToolCallWithThinking
|
|
67
66
|
} from './tools.js';
|
|
68
67
|
import { createMessagePreview, detectUnrecognizedToolCall, detectStuckResponse, areBothStuckResponses } from '../tools/common.js';
|
|
68
|
+
import { FileTracker } from '../tools/fileTracker.js';
|
|
69
69
|
import {
|
|
70
70
|
createWrappedTools,
|
|
71
71
|
listFilesToolInstance,
|
|
@@ -178,7 +178,7 @@ export class ProbeAgent {
|
|
|
178
178
|
* @param {string} [options.customPrompt] - Custom prompt to replace the default system message
|
|
179
179
|
* @param {string} [options.systemPrompt] - Alias for customPrompt; takes precedence when both are provided
|
|
180
180
|
* @param {string} [options.promptType] - Predefined prompt type (code-explorer, code-searcher, architect, code-review, support)
|
|
181
|
-
* @param {boolean} [options.allowEdit=false] - Allow the use of the '
|
|
181
|
+
* @param {boolean} [options.allowEdit=false] - Allow the use of the 'edit' and 'create' tools
|
|
182
182
|
* @param {boolean} [options.enableDelegate=false] - Enable the delegate tool for task distribution to subagents
|
|
183
183
|
* @param {boolean} [options.enableExecutePlan=false] - Enable the execute_plan DSL orchestration tool
|
|
184
184
|
* @param {string} [options.architectureFileName] - Architecture context filename to embed from repo root (defaults to AGENTS.md with CLAUDE.md fallback; ARCHITECTURE.md is always included when present)
|
|
@@ -229,6 +229,7 @@ export class ProbeAgent {
|
|
|
229
229
|
this.customPrompt = options.systemPrompt || options.customPrompt || null;
|
|
230
230
|
this.promptType = options.promptType || 'code-explorer';
|
|
231
231
|
this.allowEdit = !!options.allowEdit;
|
|
232
|
+
this.hashLines = options.hashLines !== undefined ? !!options.hashLines : this.allowEdit;
|
|
232
233
|
this.enableDelegate = !!options.enableDelegate;
|
|
233
234
|
this.enableExecutePlan = !!options.enableExecutePlan;
|
|
234
235
|
this.debug = options.debug || process.env.DEBUG === '1';
|
|
@@ -328,7 +329,8 @@ export class ProbeAgent {
|
|
|
328
329
|
if (this.debug) {
|
|
329
330
|
console.log(`[DEBUG] Generated session ID for agent: ${this.sessionId}`);
|
|
330
331
|
console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
|
|
331
|
-
console.log(`[DEBUG] Allow Edit
|
|
332
|
+
console.log(`[DEBUG] Allow Edit: ${this.allowEdit}`);
|
|
333
|
+
console.log(`[DEBUG] Hash Lines: ${this.hashLines}`);
|
|
332
334
|
console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
|
|
333
335
|
console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
|
|
334
336
|
console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
|
|
@@ -831,9 +833,12 @@ export class ProbeAgent {
|
|
|
831
833
|
cwd: this.cwd,
|
|
832
834
|
workspaceRoot: this.workspaceRoot,
|
|
833
835
|
allowedFolders: this.allowedFolders,
|
|
836
|
+
// File state tracking for safe multi-edit workflows (only when editing is enabled)
|
|
837
|
+
fileTracker: this.allowEdit ? new FileTracker({ debug: this.debug }) : null,
|
|
834
838
|
outline: this.outline,
|
|
835
839
|
searchDelegate: this.searchDelegate,
|
|
836
840
|
allowEdit: this.allowEdit,
|
|
841
|
+
hashLines: this.hashLines,
|
|
837
842
|
enableDelegate: this.enableDelegate,
|
|
838
843
|
enableExecutePlan: this.enableExecutePlan,
|
|
839
844
|
enableBash: this.enableBash,
|
|
@@ -2553,16 +2558,12 @@ ${extractGuidance}
|
|
|
2553
2558
|
}
|
|
2554
2559
|
|
|
2555
2560
|
// Edit tools (require both allowEdit flag AND allowedTools permission)
|
|
2556
|
-
if (this.allowEdit && isToolAllowed('implement')) {
|
|
2557
|
-
toolDefinitions += `${implementToolDefinition}\n`;
|
|
2558
|
-
}
|
|
2559
2561
|
if (this.allowEdit && isToolAllowed('edit')) {
|
|
2560
2562
|
toolDefinitions += `${editToolDefinition}\n`;
|
|
2561
2563
|
}
|
|
2562
2564
|
if (this.allowEdit && isToolAllowed('create')) {
|
|
2563
2565
|
toolDefinitions += `${createToolDefinition}\n`;
|
|
2564
2566
|
}
|
|
2565
|
-
|
|
2566
2567
|
// Bash tool (require both enableBash flag AND allowedTools permission)
|
|
2567
2568
|
if (this.enableBash && isToolAllowed('bash')) {
|
|
2568
2569
|
toolDefinitions += `${bashToolDefinition}\n`;
|
|
@@ -2645,7 +2646,7 @@ The configuration is loaded from src/config.js lines 15-25 which contains the da
|
|
|
2645
2646
|
availableToolsList += '- query: Search code using structural AST patterns.\n';
|
|
2646
2647
|
}
|
|
2647
2648
|
if (isToolAllowed('extract')) {
|
|
2648
|
-
availableToolsList += '- extract: Extract specific code blocks or lines from files.\n';
|
|
2649
|
+
availableToolsList += '- extract: Extract specific code blocks or lines from files. Use with symbol targets (e.g. "file.js#funcName") to get line numbers for line-targeted editing.\n';
|
|
2649
2650
|
}
|
|
2650
2651
|
if (isToolAllowed('listFiles')) {
|
|
2651
2652
|
availableToolsList += '- listFiles: List files and directories in a specified location.\n';
|
|
@@ -2662,11 +2663,8 @@ The configuration is loaded from src/config.js lines 15-25 which contains the da
|
|
|
2662
2663
|
if (isToolAllowed('readImage')) {
|
|
2663
2664
|
availableToolsList += '- readImage: Read and load an image file for AI analysis.\n';
|
|
2664
2665
|
}
|
|
2665
|
-
if (this.allowEdit && isToolAllowed('implement')) {
|
|
2666
|
-
availableToolsList += '- implement: Implement a feature or fix a bug using aider.\n';
|
|
2667
|
-
}
|
|
2668
2666
|
if (this.allowEdit && isToolAllowed('edit')) {
|
|
2669
|
-
availableToolsList += '- edit: Edit files using
|
|
2667
|
+
availableToolsList += '- edit: Edit files using text replacement, AST-aware symbol operations, or line-targeted editing.\n';
|
|
2670
2668
|
}
|
|
2671
2669
|
if (this.allowEdit && isToolAllowed('create')) {
|
|
2672
2670
|
availableToolsList += '- create: Create new files with specified content.\n';
|
|
@@ -2757,8 +2755,14 @@ Follow these instructions carefully:
|
|
|
2757
2755
|
8. Once the task is fully completed, use the '<attempt_completion>' tool to provide the final result. This is the ONLY way to signal completion.
|
|
2758
2756
|
9. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
|
|
2759
2757
|
10. When modifying files, choose the appropriate tool:
|
|
2760
|
-
- Use 'edit' for
|
|
2761
|
-
|
|
2758
|
+
- Use 'edit' for all code modifications:
|
|
2759
|
+
* For small changes (a line or a few lines), use old_string + new_string — copy old_string verbatim from the file.
|
|
2760
|
+
* For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
|
|
2761
|
+
* For editing specific lines from search/extract output, use start_line (and optionally end_line) with the line numbers shown in the output.${this.hashLines ? ' Line references include content hashes (e.g. "42:ab") for integrity verification.' : ''}
|
|
2762
|
+
* For editing inside large functions: first use extract with the symbol target (e.g. "file.js#myFunction") to see the function with line numbers${this.hashLines ? ' and hashes' : ''}, then use start_line/end_line to surgically edit specific lines within it.
|
|
2763
|
+
- Use 'create' for new files or complete file rewrites.
|
|
2764
|
+
- If an edit fails, read the error message — it tells you exactly how to fix the call and retry.
|
|
2765
|
+
- The system tracks which files you've seen via search/extract. If you try to edit a file you haven't read, or one that changed since you last read it, the edit will fail with instructions to re-read first. Always use extract before editing to ensure you have current file content.` : ''}
|
|
2762
2766
|
</instructions>
|
|
2763
2767
|
`;
|
|
2764
2768
|
|
|
@@ -3420,8 +3424,11 @@ Follow these instructions carefully:
|
|
|
3420
3424
|
validTools.push('attempt_completion');
|
|
3421
3425
|
|
|
3422
3426
|
// Edit tools (require both allowEdit flag AND allowedTools permission)
|
|
3423
|
-
if (this.allowEdit && this.allowedTools.isEnabled('
|
|
3424
|
-
validTools.push('
|
|
3427
|
+
if (this.allowEdit && this.allowedTools.isEnabled('edit')) {
|
|
3428
|
+
validTools.push('edit');
|
|
3429
|
+
}
|
|
3430
|
+
if (this.allowEdit && this.allowedTools.isEnabled('create')) {
|
|
3431
|
+
validTools.push('create');
|
|
3425
3432
|
}
|
|
3426
3433
|
// Bash tool (require both enableBash flag AND allowedTools permission)
|
|
3427
3434
|
if (this.enableBash && this.allowedTools.isEnabled('bash')) {
|
|
@@ -3804,6 +3811,7 @@ Follow these instructions carefully:
|
|
|
3804
3811
|
mcpConfigPath: this.mcpConfigPath, // Inherit MCP config path
|
|
3805
3812
|
enableBash: this.enableBash, // Inherit bash enablement
|
|
3806
3813
|
bashConfig: this.bashConfig, // Inherit bash configuration
|
|
3814
|
+
allowEdit: this.allowEdit, // Inherit edit/create permission
|
|
3807
3815
|
allowedTools: allowedToolsForDelegate, // Inherit allowed tools from parent
|
|
3808
3816
|
debug: this.debug,
|
|
3809
3817
|
tracer: this.tracer
|
|
@@ -4005,6 +4013,33 @@ Follow these instructions carefully:
|
|
|
4005
4013
|
break;
|
|
4006
4014
|
}
|
|
4007
4015
|
|
|
4016
|
+
// Issue #443: Check if response contains valid schema-matching JSON
|
|
4017
|
+
// Before triggering error.no_tool_call, strip markdown fences and validate
|
|
4018
|
+
// This handles cases where AI returns valid JSON without using attempt_completion
|
|
4019
|
+
if (options.schema) {
|
|
4020
|
+
// Remove thinking tags first
|
|
4021
|
+
let contentToCheck = assistantResponseContent;
|
|
4022
|
+
contentToCheck = contentToCheck.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '').trim();
|
|
4023
|
+
contentToCheck = contentToCheck.replace(/<thinking>[\s\S]*$/gi, '').trim();
|
|
4024
|
+
|
|
4025
|
+
// Try to extract and validate JSON
|
|
4026
|
+
const cleanedJson = cleanSchemaResponse(contentToCheck);
|
|
4027
|
+
try {
|
|
4028
|
+
JSON.parse(cleanedJson);
|
|
4029
|
+
const validation = validateJsonResponse(cleanedJson, { debug: this.debug, schema: options.schema });
|
|
4030
|
+
if (validation.isValid) {
|
|
4031
|
+
if (this.debug) {
|
|
4032
|
+
console.log(`[DEBUG] Issue #443: Accepting valid JSON response without attempt_completion (${cleanedJson.length} chars)`);
|
|
4033
|
+
}
|
|
4034
|
+
finalResult = cleanedJson;
|
|
4035
|
+
completionAttempted = true;
|
|
4036
|
+
break;
|
|
4037
|
+
}
|
|
4038
|
+
} catch {
|
|
4039
|
+
// Not valid JSON - continue to standard no_tool_call handling
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4008
4043
|
// Increment consecutive no-tool counter (catches alternating stuck responses)
|
|
4009
4044
|
consecutiveNoToolCount++;
|
|
4010
4045
|
|
package/build/agent/acp/tools.js
CHANGED
|
@@ -162,7 +162,8 @@ export class ACPToolManager {
|
|
|
162
162
|
return ToolCallKind.extract;
|
|
163
163
|
case 'delegate':
|
|
164
164
|
return ToolCallKind.execute;
|
|
165
|
-
case '
|
|
165
|
+
case 'edit':
|
|
166
|
+
case 'create':
|
|
166
167
|
return ToolCallKind.edit;
|
|
167
168
|
default:
|
|
168
169
|
return ToolCallKind.execute;
|
|
@@ -117,7 +117,8 @@ describe('ACPToolManager', () => {
|
|
|
117
117
|
expect(toolManager.getToolKind('query')).toBe(ToolCallKind.query);
|
|
118
118
|
expect(toolManager.getToolKind('extract')).toBe(ToolCallKind.extract);
|
|
119
119
|
expect(toolManager.getToolKind('delegate')).toBe(ToolCallKind.execute);
|
|
120
|
-
expect(toolManager.getToolKind('
|
|
120
|
+
expect(toolManager.getToolKind('edit')).toBe(ToolCallKind.edit);
|
|
121
|
+
expect(toolManager.getToolKind('create')).toBe(ToolCallKind.edit);
|
|
121
122
|
expect(toolManager.getToolKind('unknown')).toBe(ToolCallKind.execute);
|
|
122
123
|
});
|
|
123
124
|
});
|
|
@@ -183,6 +183,16 @@ export function generateSandboxGlobals(options) {
|
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// Issue #444: Auto-coerce object paths to strings for search()
|
|
187
|
+
// AI-generated DSL sometimes passes field objects instead of field.file_path strings
|
|
188
|
+
if (params.path && typeof params.path === 'object') {
|
|
189
|
+
const coercedPath = params.path.file_path || params.path.path || params.path.directory || params.path.filename;
|
|
190
|
+
if (coercedPath && typeof coercedPath === 'string') {
|
|
191
|
+
logFn?.(`[${name}] Warning: Coerced object path to string "${coercedPath}" (issue #444)`);
|
|
192
|
+
params.path = coercedPath;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
186
196
|
const validated = schema.safeParse(params);
|
|
187
197
|
if (!validated.success) {
|
|
188
198
|
throw new Error(`Invalid parameters for ${name}: ${validated.error.message}`);
|
|
@@ -232,6 +242,15 @@ export function generateSandboxGlobals(options) {
|
|
|
232
242
|
// When schema is provided, auto-parse the JSON result for easier downstream processing
|
|
233
243
|
if (llmCall) {
|
|
234
244
|
const rawLLM = async (instruction, data, opts = {}) => {
|
|
245
|
+
// Issue #444: Guard against error strings being passed as data
|
|
246
|
+
// When previous tool calls fail, they return "ERROR: ..." strings
|
|
247
|
+
// Passing these to LLM() spawns useless delegates that can't help
|
|
248
|
+
const dataStr = typeof data === 'string' ? data : JSON.stringify(data);
|
|
249
|
+
if (dataStr && dataStr.startsWith('ERROR:')) {
|
|
250
|
+
logFn?.('[LLM] Blocked: data contains error from previous tool call');
|
|
251
|
+
return 'ERROR: Previous tool call failed - ' + dataStr.substring(0, 200);
|
|
252
|
+
}
|
|
253
|
+
|
|
235
254
|
const result = await llmCall(instruction, data, opts);
|
|
236
255
|
// Auto-parse JSON when schema is provided and result is a string
|
|
237
256
|
if (opts.schema && typeof result === 'string') {
|