Add Phase 7: polish and hardening — retry, truncation, sessions, shutdown

- Config extensions: retry backoff, truncation threshold, session persistence
- LLM retry with exponential backoff + jitter on transient errors (5xx, connection)
- Conversation truncation: drops oldest messages preserving first user + recent N
- Session persistence: auto-save/restore with atomic writes, cleanup of old files
- Graceful shutdown: SIGTERM handler, cancel() on AgentLoop, save-on-exit
- Partial message recovery on mid-stream interruption
- New slash commands: /save, /session
- 18 new tests (5 retry, 5 truncation, 4 session, 4 integration workflows)
- README.md and docs/tools.md documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 10:20:16 -05:00
parent 82846d6236
commit 76ba490aa2
16 changed files with 1550 additions and 12 deletions

240
docs/tools.md Normal file
View File

@@ -0,0 +1,240 @@
# Tool Reference
SneakyCode provides 11 agent-callable tools organized into 5 categories. All file path arguments must be **relative to the workspace root**.
## Permission Tiers
| Tier | Behavior | Tools |
|---------------|---------------------------------------|----------------------------------------------------------------|
| Auto-approved | Executed without user confirmation | `read_file`, `list_dir`, `grep_files`, `find_files`, `finish` |
| User confirm | Prompts user before execution | `write_file`, `make_dir`, `delete_file`, `str_replace`, `patch_apply`, `run_command` |
| Denied | Blocked entirely (configurable) | Any tool added to `permissions.deny` in config |
---
## Read Tools
### read_file
Read the full contents of a text file.
| Parameter | Type | Required | Description |
|-------------|------|----------|------------------------------------------------|
| `file_path` | str | Yes | Path to the file to read (relative to workspace) |
**Permission:** Auto-approved
**Example:**
```json
{"file_path": "app/main.py"}
```
**Notes:** Binary files are detected and rejected. Files exceeding `max_file_size_bytes` (default 1 MB) are rejected.
---
### list_dir
List the contents of a directory. Directories are suffixed with `/`. Results are sorted with directories first, then files.
| Parameter | Type | Required | Default | Description |
|------------------|------|----------|---------|--------------------------------------|
| `directory_path` | str | No | `"."` | Path to directory (relative) |
| `recursive` | bool | No | `false` | If true, list entries recursively |
**Permission:** Auto-approved
**Example:**
```json
{"directory_path": "app/tools", "recursive": true}
```
---
## Search Tools
### grep_files
Search for a regex pattern in file contents. Returns matching lines with file paths and line numbers.
| Parameter | Type | Required | Default | Description |
|----------------|------------|----------|---------|------------------------------------------|
| `pattern` | str | Yes | | Regular expression pattern to search for |
| `path` | str | No | `"."` | Directory or file to search in |
| `file_pattern` | str\|null | No | `null` | Glob pattern to filter files (e.g. `*.py`) |
**Permission:** Auto-approved
**Example:**
```json
{"pattern": "def main", "path": "app/", "file_pattern": "*.py"}
```
---
### find_files
Search for files matching a name pattern. Returns relative file paths.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|---------------------------------------------------|
| `pattern` | str | Yes | | File name pattern (e.g. `*.py`, `config.yaml`) |
| `path` | str | No | `"."` | Directory to search in |
**Permission:** Auto-approved
**Example:**
```json
{"pattern": "*.yaml", "path": "config/"}
```
---
## Write Tools
### write_file
Write text content to a file. Creates parent directories if needed. Overwrites existing file content.
| Parameter | Type | Required | Description |
|-------------|------|----------|---------------------------------|
| `file_path` | str | Yes | Path to the file to write |
| `content` | str | Yes | Content to write to the file |
**Permission:** User confirmation required
**Example:**
```json
{"file_path": "app/utils/helpers.py", "content": "def greet():\n return 'hello'\n"}
```
---
### make_dir
Create a directory and any necessary parent directories.
| Parameter | Type | Required | Description |
|------------------|------|----------|----------------------------------|
| `directory_path` | str | Yes | Path to the directory to create |
**Permission:** User confirmation required
**Example:**
```json
{"directory_path": "app/services/new_module"}
```
---
### delete_file
Delete a single file. Does not delete directories.
| Parameter | Type | Required | Description |
|-------------|------|----------|---------------------------------|
| `file_path` | str | Yes | Path to the file to delete |
**Permission:** User confirmation required
**Example:**
```json
{"file_path": "app/utils/deprecated.py"}
```
---
## Edit Tools
### str_replace
Replace exactly one occurrence of `old_str` with `new_str` in a file. Fails if `old_str` is not found or appears more than once.
| Parameter | Type | Required | Description |
|-------------|------|----------|----------------------------------------------------|
| `file_path` | str | Yes | Path to the file to edit |
| `old_str` | str | Yes | The exact string to find and replace (must be unique) |
| `new_str` | str | Yes | The replacement string |
**Permission:** User confirmation required
**Example:**
```json
{
"file_path": "app/main.py",
"old_str": "def old_function():",
"new_str": "def new_function():"
}
```
---
### patch_apply
Apply a unified diff (patch) to a file. The patch must be in standard unified diff format.
| Parameter | Type | Required | Description |
|-------------|------|----------|--------------------------------------------|
| `file_path` | str | Yes | Path to the file to patch |
| `patch` | str | Yes | Unified diff format patch to apply |
**Permission:** User confirmation required
**Example:**
```json
{
"file_path": "app/main.py",
"patch": "--- a/app/main.py\n+++ b/app/main.py\n@@ -1,3 +1,3 @@\n-old line\n+new line\n"
}
```
---
## Shell Tools
### run_command
Run a shell command in the workspace directory. Only allowed commands may be executed; dangerous commands are blocked.
| Parameter | Type | Required | Default | Description |
|-----------|----------|----------|---------|-----------------------------------|
| `command` | str | Yes | | Shell command to execute |
| `timeout` | int\|null | No | `30` | Timeout in seconds |
**Permission:** User confirmation required. Subject to `tools.shell.allowed_commands` and `tools.shell.denied_commands` in config.
**Example:**
```json
{"command": "git status", "timeout": 10}
```
**Notes:** Output is truncated to `max_output_bytes` (default 64 KB). The command's first word is checked against allow/deny lists.
---
## Control Tools
### finish
Signal that the task is complete. Terminates the agent loop.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|--------------------|------------------------------|
| `message` | str | No | `"Task complete."` | Final message to the user |
**Permission:** Auto-approved
**Example:**
```json
{"message": "Created the new module with tests."}
```
---
## Security Notes
- All file paths are resolved against `workspace_root` with path traversal protection
- Binary file detection prevents reading/writing binary files
- File size limits prevent reading/writing excessively large files
- Shell commands are validated against configurable allow/deny lists
- Tool call arguments from the LLM are validated against JSON schema before execution