# File Operation Nodes

> Applies to: ChengOS v0.2.0+ ｜ Last verified: 2026-09-25 ｜ Source: `crates/cheng-nodes/src/nodes/builtin/tools/file_ops`, `crates/cheng-file-ops/src`

Two ways to use the same core. `tools/file_ops_hub` takes the operation as a field, which is what an
agent needs — one node, many operations, and structured failure output that says whether the call may
be retried unchanged. The individual nodes are for graphs you draw by hand, where the operation is
part of the picture. Mutating operations return a receipt describing what actually changed on disk;
when it reports that the runtime verified the postcondition, a follow-up read to confirm the write is
wasted work.

## File Operations Hub — `tools/file_ops_hub`

File operations aggregate node: only one node needed on canvas, control Agent's available file operation capabilities via toggle switches.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `operation` | string | — | `""` | File operation type to execute |
| `path` | string | — | — | Unified target path for the file operation |
| `sandbox_root` | string | — | — | Static sandbox root directory. Once set, LLM can only access files in this directory and its subdirectories. |
| `config` | object (port) | — | `{"allow_ignored":null,"allow_sensitive":null,...` | Unified configuration object. For LLM tool calls, pass this as a JSON object, not a JSON string. Connection port, not a form field. Control: `text` |
| `allow_read_info` | boolean | ✅ | `true` | 是否允许获取文件元信息（只读）. Control: `switch` |
| `allow_read_content` | boolean | ✅ | `true` | 是否允许读取文件内容（只读）. Control: `switch` |
| `allow_list_directory` | boolean | ✅ | `true` | 是否允许列出目录（只读）. Control: `switch` |
| `allow_search` | boolean | ✅ | `true` | 是否允许搜索文件内容（只读）. Control: `switch` |
| `allow_file_tree` | boolean | ✅ | `true` | 是否允许渲染仓库文件树快照（只读）. Control: `switch` |
| `allow_diff` | boolean | ✅ | `true` | 是否允许生成 diff（只读）. Control: `switch` |
| `allow_validate` | boolean | ✅ | `true` | 是否允许 JSON/YAML 校验（只读）. Control: `switch` |
| `allow_write` | boolean | ✅ | `true` | 是否允许写入/创建文件（中副作用）. Control: `switch` |
| `allow_edit` | boolean | ✅ | `true` | 是否允许精确编辑文件（中副作用）. Control: `switch` |
| `allow_patch` | boolean | ✅ | `true` | 是否允许 JSON/YAML 路径级补丁（中副作用）. Control: `switch` |
| `allow_copy` | boolean | ✅ | `true` | 是否允许复制文件/目录（中副作用）. Control: `switch` |
| `allow_backup` | boolean | ✅ | `true` | 是否允许创建快照（中副作用）. Control: `switch` |
| `allow_move` | boolean | ✅ | `true` | 是否允许移动/重命名（高副作用，会移除源；启用后每次调用需用户审批）. Control: `switch` |
| `allow_rollback` | boolean | ✅ | `true` | 是否允许从快照回滚（高副作用，覆写；启用后每次调用需用户审批）. Control: `switch` |
| `allow_delete` | boolean | ✅ | `true` | 是否允许删除文件（高副作用，不可逆；启用后每次调用需用户审批）. Control: `switch` |
| `allow_batch` | boolean | ✅ | `true` | 是否允许批量操作（混合副作用，逐项二次校验；启用后每次调用需用户审批）. Control: `switch` |
| `require_approval` | boolean | ✅ | `true` | 变更操作是否需要用户审批（静态配置，LLM 不可写）。默认 true。 默认策略下，经验证工作区内的小范围可撤销变更（新建文件、带备份的单文件 编辑/补丁、复制到新路径、建目录、快照）自动执行；其余变更弹出审批卡。 设为 false：write/edit/patch/create_directory/copy/backup 及不含破坏性子操作的 batch 直接执行；delete/move/rollback（及含它们的 batch）仍需审批，除非部署级 受信任自动化策略（`CHENG_TRUSTED_AUTOMATION_POLICY_FILE`）为该工作流授权。 Control: `switch` |

### Outputs

| Field | Type | Description |
|---|---|---|
| `operation` | string | Actual operation type executed |
| `summary` | string | Operation summary for quick top-level result reading |
| `error_kind` | string | Machine-readable error type, e.g. operation_not_enabled / invalid_operation |
| `terminal` | boolean | true means retrying the same operation or switching entrypoint will not recover; config or approach change needed |
| `enabled_operations` | array&lt;string&gt; | List of operations enabled by current node config (only filled on terminal errors; omitted on success) |
| `disabled_operations` | array&lt;string&gt; | List of operations disabled by current node config (only filled on terminal errors; omitted on success) |
| `error` | string | Error message (operation denied or execution failed) |
| `result` | any | Operation result (JSON-serialized sub-tool output) |
| `correct_call` | any | A valid call for this operation as structured JSON, copyable as-is |
| `error_field` | string | Caller-notation path of the offending field (e.g. config.content), so the correction targets one argument instead of the whole call |
| `mutation_receipt` | object | Authoritative record of what this mutation actually changed on disk. self_verified=true means the runtime re-read the target and confirmed the postcondition, so a follow-up read to verify the write is unnecessary. Absent for read-only operations |
| `mutation_receipts` | array&lt;object&gt; | Per-child receipts for batch_file_ops, in execution order. Present even when the batch failed, because earlier children may already have committed |
| `mutation_summary` | string | One-line summary of the mutation receipt: what changed and whether it was verified on disk |
| `retry_disposition` | string | One of retry_same_call, retry_after_correction, do_not_retry. Says whether THIS call may be reissued unchanged — a different question from `terminal`, which says whether the approach or config must change |
| `suggested_action` | string | Stable next-action identifier (e.g. use_file_tree, correct_source_path), so the caller does not have to parse prose |
| `workspace_delta` | object | Incremental path-level changes from this operation. Use it to update your picture of the workspace instead of re-running file_tree after an edit |

## View File — `tools/view_file`

Read file content. Four MUTUALLY EXCLUSIVE modes: line (start_line/end_line), tail (tail_lines), byte (offset_bytes/limit_bytes), char (offset_chars/limit_chars). Always returns total_lines and eof so the full picture is visible. Line mode caps at 500 lines AND a character budget; when more remains, the response carries exactly one continuation cursor (next_start_line / next_offset_bytes / next_offset_chars) plus truncated_reason — follow that cursor instead of re-reading the same range. When NO range parameter is given and the file exceeds 500 lines, only the first 200 lines are returned as a structure preview (truncated_reason=large_file_preview) — locate what you need with search_files first.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `end_line` | integer | — | — | LINE MODE. End line number (inclusive), default min(start_line+499, total_lines). Must be >= start_line. Mutually exclusive with tail_lines and with byte/char modes |
| `file_path` | string | ✅ | — | Absolute file path to view |
| `known_modified_at` | string | — | — | Modification time the caller last observed (RFC 3339). A hint only — coarse or equal timestamps never prove the content is unchanged. |
| `known_sha256` | string | — | — | Whole-file SHA-256 the caller already observed. When it still matches on disk, the response is a compact file_unchanged marker instead of the content. |
| `known_size` | integer | — | — | Size the caller last observed. A hint used to skip hashing when it already differs; never proof of equality. |
| `limit_bytes` | integer | — | — | BYTE MODE. Max bytes to read, capped at 1 MiB. The window end is snapped down to a UTF-8 boundary, so a fixed byte budget never fails on multibyte text |
| `limit_chars` | integer | — | — | CHAR MODE. Max characters to read (output still capped at 1 MiB) |
| `offset_bytes` | integer | — | — | BYTE MODE. Byte offset (0-indexed); supplying it selects byte mode. Must land on a UTF-8 boundary — pass the response's `next_offset_bytes` here to continue, which is always a valid boundary. Mutually exclusive with line mode, tail mode, and char mode |
| `offset_chars` | integer | — | — | CHAR MODE. Character offset (0-indexed); supplying it selects char mode. Pass the response's `next_offset_chars` here to continue. REQUIRED to make progress when a response reports truncated_reason=line_too_long. Mutually exclusive with line mode, tail mode, and byte mode |
| `start_line` | integer | — | — | LINE MODE. Start line number (1-indexed), default 1. Mutually exclusive with tail_lines, byte mode (offset_bytes/limit_bytes), and char mode (offset_chars/limit_chars). Continue a truncated line-mode read by passing the response's `next_start_line` here. Note: omitting ALL range parameters on a file longer than 500 lines returns only the first 200 lines as a structure preview (truncated_reason=large_file_preview) |
| `tail_lines` | integer | — | — | TAIL MODE. Return the last N lines in one call (no need to know the file length). Mutually exclusive with start_line/end_line and with byte/char modes. Capped at 500 shown lines (the newest are kept). |

### Outputs

| Field | Type | Description |
|---|---|---|
| `content` | string | File content. line mode has line number prefixes; byte/char mode is raw slice text |
| `encoding` | string | Encoding (always utf-8) |
| `end_line` | integer | Actual end line (line mode only) |
| `eof` | boolean | Whether end of file was reached |
| `error` | string | Error message (when present, other fields are empty/zero) |
| `file_path` | string | Canonicalized absolute path |
| `file_unchanged` | boolean | The caller's known digest still describes this file; content was not resent. |
| `mode` | string | Read mode: line, byte, or char |
| `next_offset_bytes` | integer | Continuation cursor for byte mode: pass this as `offset_bytes` on the next call. Always a valid UTF-8 boundary; absent at EOF |
| `next_offset_chars` | integer | Continuation cursor for char mode: pass this as `offset_chars` on the next call. Also used when truncated_reason=line_too_long, where char mode is the ONLY way to advance; absent at EOF |
| `next_start_line` | integer | Continuation cursor for line mode: pass this as `start_line` on the next call. Present only when truncated on a line boundary; absent at EOF |
| `offset_bytes` | integer | Actual start byte offset (byte mode only) |
| `offset_chars` | integer | Actual start character offset (char mode only) |
| `requested_tail_lines` | integer | Requested tail line count (tail read only; actual range in start_line/end_line) |
| `sha256` | string | Current whole-file SHA-256, returned for conditional reads so the caller can refresh its observation. |
| `shown_bytes` | integer | Actual bytes returned (byte/char mode) |
| `shown_chars` | integer | Actual characters returned (char mode only) |
| `start_line` | integer | Actual start line (line mode only) |
| `total_bytes` | integer | Total file size in bytes |
| `total_chars` | integer | Total character count (char mode only) |
| `total_lines` | integer | Total line count (computed even in byte/char mode) |
| `truncated` | boolean | Whether output was truncated due to exceeding limits |
| `truncated_reason` | string | Why the output was cut short: max_lines, max_output_chars, byte_limit, char_limit, line_too_long, or large_file_preview. Read this together with the matching next_* cursor. large_file_preview means no range was requested on a long file, so only a leading structure preview was returned — prefer locating the region with search_files over paging the whole file |

## Write File — `tools/write_file`

Create new file or overwrite/append existing file. Uses atomic write (temp file + rename) to prevent file corruption from interrupted writes.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `file_path` | string | ✅ | — | Absolute file path to write |
| `content` | string | ✅ | — | File content to write (UTF-8) |
| `content_artifact` | object | — | `null` | Artifact to write to the target path (preferred over inline content) |
| `content_artifact_uri` | string | — | `null` | artifact:// URI for the content to write |
| `source_encoding` | string | — | `null` | utf-8\|text (text export) or binary (byte-exact); default auto-detects |
| `overwrite` | boolean | — | `false` | Whether to allow overwriting existing file, default false |
| `force_overwrite` | boolean | — | `false` | Force overwrite: skip the expected_sha256 confirmation round-trip and imply overwrite for existing files, default false. Backups remain on by default so recovery is preserved |
| `create_dirs` | boolean | — | `true` | Whether to automatically create parent directories, default true |
| `append` | boolean | — | `false` | Append mode (takes priority over overwrite), default false |
| `dry_run` | boolean | — | `false` | Preview the write (diff + current_sha256) without any side effects, default false |
| `expected_sha256` | string | — | — | Confirmation guard for overwriting an existing file: hex SHA-256 of the current file content as observed during preview/read. Required to overwrite an existing file; the write is rejected if it does not match the current file hash |
| `allow_ignored` | boolean | — | `null` | Permit writing to a path matched by .gitignore/.ignore. Default false blocks such targets. |
| `allow_sensitive` | boolean | — | `null` | Permit writing to a sensitive path (.env, private keys, credentials). Normally requires approval; default false blocks. |
| `backup_label` | string | — | — | Optional label for the backup |
| `backup_root` | string | — | — | Optional backup root directory |
| `create_backup` | boolean | — | `null` | Create a recoverable snapshot before overwriting/appending an existing file. Defaults to true for real modifications; never created during dry runs or for new files |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | 备份 ID（create_backup=true 且覆写/追加已有文件时） |
| `bytes_written` | integer | 写入字节数 |
| `created` | boolean | true = new file created; false = overwritten or appended |
| `current_sha256` | string | 当前文件内容的 SHA-256（既有可读文件时填充）。覆写时作为 expected_sha256 回传。 |
| `diff` | string | Unified diff (filled when overwriting/appending existing text files; None if diff generation fails) |
| `dry_run` | boolean | 是否为演练模式 |
| `error` | string | Error message if the operation failed |
| `file_bytes_after` | integer | 写入后磁盘文件的字节数（真实写入成功后填充） |
| `file_path` | string | 实际写入的规范路径 |
| `lines_after` | integer | 写入后磁盘文件的行数（真实写入成功且可作为 UTF-8 读取时填充） |
| `requested_bytes` | integer | 请求写入的内容字节数（dry_run/失败时也填充，便于 LLM 判断是否疑似截断） |
| `requested_lines` | integer | 请求写入的内容行数（基于 UTF-8 文本的 logical line count） |
| `requested_sha256` | string | 请求内容的 SHA-256。用于让调用方确认“传入工具的内容”到底有多长/是什么。 |
| `self_verified` | boolean | 工具侧写后自校验：磁盘内容 SHA-256 是否等于请求内容 SHA-256。 |
| `sha256_after` | string | 真实写入后磁盘文件的 SHA-256。 |
| `summary` | string | Write result summary (suitable for passing to LLM / Preview nodes) |
| `verification_error` | string | 自校验失败或跳过的原因。 |

## Edit File — `tools/edit_file`

Replace file content by exact content matching. Supports multiple replacement operations; writes back atomically only when all succeed; any failure keeps original file unchanged.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `allow_ignored` | boolean | — | `null` | Permit editing a path matched by .gitignore/.ignore. Default false blocks such targets. |
| `allow_sensitive` | boolean | — | `null` | Permit editing a sensitive path (.env, private keys, credentials). Normally requires approval; default false blocks. |
| `backup_label` | string | — | — | Optional label for the backup |
| `backup_root` | string | — | — | Optional backup root directory; defaults next to the target file |
| `confusable_fallback` | boolean | — | `null` | If exact match fails, retry after normalizing a small set of confusables (smart quotes, en/em dash, non-breaking space, selected full-width punctuation) to ASCII. Default false. Exact matching is always tried first. |
| `create_backup` | boolean | — | `null` | Create a recoverable snapshot before writing. Defaults to true for real modifications; never created during dry runs. Pass false to opt out |
| `dry_run` | boolean | — | `false` | Validate and preview diff without writing, default false |
| `expected_sha256` | string | — | — | Optional lost-update guard: hex SHA-256 of the file content as last read; the edit is rejected if the current file hash differs |
| `file_path` | string | ✅ | — | Absolute file path to edit |
| `force_overwrite` | boolean | — | `false` | Force write fast-path: short-circuit the expected_sha256 lost-update guard, default false. Backups remain on by default |
| `ignore_whitespace` | boolean | — | `null` | If exact match fails, retry comparing whitespace-normalized forms (collapse runs of spaces/tabs, ignore leading indentation and trailing whitespace; newlines stay significant). Applies only when exactly ONE normalized candidate exists; multiple candidates fail with ambiguous_whitespace_match. Default false; exact matching is always tried first. |
| `max_diagnostic_candidates` | integer | — | — | Maximum similarity candidates returned in failure diagnostics, default 3 |
| `replace_all` | boolean | — | `null` | Replace all occurrences within the selected scope. Default false preserves the require_unique_match safety model. Use dry_run for broad replace_all edits first. |
| `replacements` | array&lt;object&gt; | — | — | List of replacement operations (executed in order). Can be provided as a direct array OR as a JSON string for convenience. |
| `replacements_json` | string | — | — | Alternative: provide replacements as a JSON string (e.g., '{"old_content":"...","new_content":"..."}'). If provided, overrides the replacements field. |
| `require_unique_match` | boolean | — | — | Require a unique match per replacement, default true. When false, multiple matches replace the first occurrence. |
| `return_diff` | boolean | — | — | Return a unified diff for changed files and dry runs, default true |
| `snippet_context_lines` | integer | — | — | Context lines around each changed range in returned snippets. Default 3. |

### Outputs

| Field | Type | Description |
|---|---|---|
| `applied_count` | integer | 成功应用的替换数（输入数组中产生变更的条目数） |
| `backup_id` | string | 备份 ID（create_backup=true 且成功写入时） |
| `changed` | boolean | Whether the file content changed |
| `diagnostics` | array&lt;object&gt; | Failure diagnostics |
| `diff` | string | Unified diff of changes (filled when return_diff or dry_run) |
| `dry_run` | boolean | 是否为演练模式 |
| `error` | string | Error message (stops on first replacement failure; file remains unchanged) |
| `file_path` | string | 编辑的文件路径 |
| `match_mode` | string | 匹配模式：exact \| whitespace_normalized \| confusable_normalized |
| `replacement_occurrences` | integer | 实际替换的匹配次数（replace_all 时可大于 applied_count） |
| `snippets` | array&lt;object&gt; | Change snippets around each modified range with line numbers (on success or dry run) |
| `total_lines_after` | integer | 编辑后总行数 |
| `total_lines_before` | integer | 编辑前总行数 |
| `used_confusable_fallback` | boolean | 是否使用了视觉混淆字符归一化回退匹配 |

## Patch File — `tools/patch_file`

Apply JSON Pointer path-level patches (set/remove/append/insert/replace) to JSON/YAML, or write whole text/Markdown content. Atomic: all patches validated in memory before writing. Supports dry_run.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `allow_ignored` | boolean | — | — | Permit patching a path matched by .gitignore/.ignore. Default false blocks such targets. |
| `allow_sensitive` | boolean | — | — | Permit patching a sensitive path (.env, keys, credentials). Normally requires approval; default false blocks. |
| `backup_label` | string | — | — | Optional label for the backup |
| `backup_root` | string | — | — | Optional backup root directory |
| `content` | string | — | — | Whole-file replacement content. When provided, patches are ignored. |
| `create_backup` | boolean | — | — | Create a recoverable snapshot before writing. Defaults to true for real modifications; never created during dry runs. Pass false to opt out |
| `dry_run` | boolean | ✅ | — | Preview diff without writing, default false |
| `file_path` | string | ✅ | — | Target JSON/YAML file path |
| `format` | string | — | — | auto \| json \| yaml \| text \| markdown, default auto |
| `patches` | array&lt;object&gt; | — | — | List of patches applied in order |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | 备份 ID（create_backup=true 且成功写入时） |
| `changed` | boolean | Whether the file content changed |
| `diff` | string | Unified diff of changes |
| `dry_run` | boolean | 是否演练模式 |
| `error` | string | Error message if the operation failed |
| `error_details` | array&lt;object&gt; | 补丁级失败详情（patch_index/op/path/message） |
| `file_path` | string | 规范化文件路径 |
| `format` | string | Actual format used |
| `patches_applied` | integer | 成功应用的补丁数 |
| `validation_after_patch` | object | 补丁后序列化结果的格式校验（确认仍可解析） |

## Copy File — `tools/copy_file`

Copy a file or directory to a new location. Content is copied to a temporary staging path and then atomically renamed into place (existing targets are preserved until the swap succeeds). Directories require recursive=true. Refuses to overwrite unless overwrite=true.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `allow_ignored` | boolean | — | `null` | Permit copying to a destination matched by .gitignore/.ignore. Default false blocks such targets. |
| `allow_sensitive` | boolean | — | `null` | Permit copying to a sensitive destination (.env, keys, credentials). Normally requires approval; default false blocks. |
| `backup_label` | string | — | — | Optional label for the backup |
| `backup_root` | string | — | — | Optional backup root directory |
| `create_backup` | boolean | — | `null` | Create a recoverable snapshot of an existing destination file before overwriting it. Defaults to true for real overwrites; never created during dry runs or for new destinations |
| `create_dirs` | boolean | — | `true` | Whether to automatically create destination parent directories, default true |
| `destination_path` | string | ✅ | — | Destination path |
| `dry_run` | boolean | — | `false` | Preview the copy (validation + created/overwritten report) without any side effects, default false |
| `overwrite` | boolean | — | `false` | Whether to allow overwriting an existing destination, default false |
| `recursive` | boolean | — | `false` | Must be true to copy a directory, default false |
| `source_path` | string | ✅ | — | Source file or directory path |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | 覆写前目标文件快照的备份 ID（仅覆写已有目标文件时；dry_run / 新建为 None） |
| `bytes_copied` | integer | 复制的字节数（目录为聚合值） |
| `created` | boolean | true = destination was newly created; false = overwritten |
| `destination_path` | string | 规范化后的目标路径 |
| `dry_run` | boolean | 是否为演练模式 |
| `error` | string | Error message if the operation failed |
| `is_dir` | boolean | Whether the source is a directory |
| `overwritten` | boolean | Whether an existing destination was overwritten |
| `source_path` | string | 规范化后的源路径 |
| `summary` | string | Operation result summary |

## Move File — `tools/move_file`

Move or rename a file or directory. Prefers atomic rename; falls back to copy-then-delete across filesystems (source deleted only after a fully successful copy).

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `allow_ignored` | boolean | — | `null` | Permit moving when the source or destination is matched by .gitignore/.ignore. Default false blocks. |
| `allow_sensitive` | boolean | — | `null` | Permit moving when the source or destination is a sensitive path. Normally requires approval; default false blocks. |
| `backup_label` | string | — | — | Optional label for the backup |
| `backup_root` | string | — | — | Optional backup root directory |
| `create_backup` | boolean | — | `null` | Create a recoverable snapshot of an existing destination file before it is overwritten by the move. Defaults to true for real overwrites; never created during dry runs or when the destination is new |
| `create_dirs` | boolean | — | `true` | Whether to automatically create destination parent directories, default true |
| `destination_path` | string | ✅ | — | Destination path |
| `dry_run` | boolean | — | `false` | Preview the move (validation + overwrite report) without any side effects, default false |
| `overwrite` | boolean | — | `false` | Whether to allow overwriting an existing destination, default false |
| `source_path` | string | ✅ | — | Source file or directory path |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | 覆写前目标文件快照的备份 ID（destination overwrite backup：仅覆写已有 目标文件时创建；dry_run / 新建目标为 None） |
| `destination_path` | string | 规范化后的目标路径 |
| `dry_run` | boolean | 是否为演练模式 |
| `error` | string | Error message if the operation failed |
| `moved` | boolean | Whether the move was successful |
| `overwritten` | boolean | Whether an existing destination was overwritten |
| `source_path` | string | 规范化后的源路径 |
| `summary` | string | Operation result summary |

## Delete File — `tools/delete_file`

Delete file (high side-effect, irreversible). Only allows deleting files, not directories. Supports dry_run rehearsal mode.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `allow_ignored` | boolean | — | `null` | Permit deleting a path matched by .gitignore/.ignore (e.g. generated output). Requires explicit intent; default false blocks. |
| `allow_sensitive` | boolean | — | `null` | Permit deleting a sensitive path (.env, keys, credentials). Normally requires approval; default false blocks. |
| `backup_label` | string | — | — | Optional label for the backup |
| `backup_root` | string | — | — | Optional backup root directory |
| `create_backup` | boolean | — | `null` | Create a recoverable snapshot before deleting the file. Defaults to true for real deletions; never created during dry runs |
| `dry_run` | boolean | — | `false` | Dry run mode (validate only, no actual deletion), default false |
| `file_path` | string | ✅ | — | Absolute file path to delete |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | 删除前快照的备份 ID（可用 rollback_file 恢复；dry_run / 未删除时为 None） |
| `deleted` | boolean | true = successfully deleted (dry_run=true means deletable) |
| `dry_run` | boolean | 是否为演练模式 |
| `error` | string | Error message if the operation failed |
| `file_path` | string | 操作的文件路径 |
| `file_size_bytes` | integer | 被删除文件的大小（删除前记录） |

## Diff File — `tools/diff_file`

Generate a unified diff between two files, or between a file and proposed new content. Read-only.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `compare_path` | string | — | — | File to compare against |
| `context_lines` | integer | — | — | Context lines, default 3 |
| `file_path` | string | ✅ | — | Base file path |
| `new_content` | string | — | — | Proposed new content to diff against |

### Outputs

| Field | Type | Description |
|---|---|---|
| `added_lines` | integer | 新增行数 |
| `compare_path` | string | 对比文件路径（file-to-content 模式为 null） |
| `diff` | string | Unified diff text |
| `error` | string | Error message if the operation failed |
| `file_path` | string | 规范化后的基准文件路径 |
| `has_changes` | boolean | Whether there are differences |
| `removed_lines` | integer | 删除行数 |
| `truncated` | boolean | Whether the diff was truncated due to exceeding output limit |

## Validate File — `tools/validate_file`

Validate that a file's content (or inline content) is well-formed JSON or YAML. Read-only.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `content` | string | — | — | Inline content to validate |
| `file_path` | string | — | — | File path to validate |
| `format` | string | — | — | auto \| json \| yaml, default auto |

### Outputs

| Field | Type | Description |
|---|---|---|
| `column` | integer | Error column number (when provided by parser) |
| `error` | string | Node-level error (path resolution / read failure etc.) |
| `error_message` | string | Validation error message when validation fails |
| `file_path` | string | Canonical file path (empty for inline content) |
| `format` | string | Actual format used (json or yaml) |
| `line` | integer | Error line number (when provided by parser) |
| `summary` | string | Result summary |
| `valid` | boolean | Whether the content is valid |

## File Info — `tools/file_info`

Get file or directory metadata (size, line count, modification time, binary or not, etc.) without reading file content.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `path` | string | ✅ | — | Absolute path of file or directory |

### Outputs

| Field | Type | Description |
|---|---|---|
| `created` | string | Creation time (ISO 8601; None on some OSes) |
| `entry_type` | enum | Entry type: File, Dir, or Symlink. Options: `file`, `dir`, `symlink` |
| `error` | string | Error message if the operation failed |
| `exists` | boolean | Whether the path exists |
| `is_binary` | boolean | Whether the file is binary |
| `is_readable` | boolean | Whether the current process has read permission |
| `is_writable` | boolean | Whether the current process has write permission |
| `metadata` | string | Aggregated metadata summary (suitable for passing to LLM / Preview nodes) |
| `mime_type` | string | Inferred MIME type (e.g. text/x-rust, text/plain) |
| `modified` | string | Modification time (ISO 8601) |
| `path` | string | Canonical absolute path |
| `size_bytes` | integer | File size in bytes; None for directories |
| `total_lines` | integer | Total line count for text files; None for binary or directories |

## File Tree — `tools/file_tree`

Render a character-budgeted repository tree snapshot with structured stats. Respects .gitignore, never follows symlinks. Ideal for cold-start project orientation.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `exclude_patterns` | array | — | — | Gitignore-style glob patterns to exclude. A recursive directory pattern such as ["target/**"] removes both target/ and all descendants; real empty directories not matched by an exclude remain visible. |
| `include_patterns` | array | — | — | Gitignore-style glob whitelist with search_files override semantics. Patterns match entries, not a separate file-only namespace: ["*.rs"] keeps Rust files (and traverses their parent directories), while ["*/"] matches directories only and therefore renders a directory-only view. |
| `include_stats` | boolean | — | — | Compute extension_stats and largest_dirs, default false |
| `max_chars` | integer | — | — | Character budget for the rendered tree string. Default 20000, hard cap 200000. |
| `max_depth` | integer | — | — | Depth convention: the root is depth 0; max_depth = 1 renders only direct root entries, max_depth = 2 adds one more level, and so on. Default 64, hard cap 256. Entries beyond the limit are never rendered, but their existence is reported via truncated/truncated_reason. |
| `max_file_size` | integer | — | — | Files larger than this (bytes) are omitted from the tree (still scanned). None = no limit. |
| `max_scan_ms` | integer | — | — | Wall-clock budget for the scan in milliseconds. Default 5000, hard cap 60000. |
| `max_scanned_files` | integer | — | — | Upper bound on filesystem entries visited during traversal. Default 20000. |
| `offset` | integer | — | — | Continuation offset into the deterministic rendered entries, excluding the root line. Default 0. When next_offset is returned, call again with the same options and offset=next_offset. |
| `root_path` | string | ✅ | — | Root directory to snapshot (must be a directory, not a file) |
| `show_hidden` | boolean | — | `false` | Include dotfiles/dot-directories, default false |
| `stats_scope` | enum | — | — | Statistics scope: 'rendered' (cheap) or 'scanned' (walks past render budget). Default rendered. Options: `rendered`, `scanned` |

### Outputs

| Field | Type | Description |
|---|---|---|
| `elapsed_ms` | integer | Operation duration in milliseconds |
| `error` | string | Error message when the operation failed |
| `extension_stats` | array&lt;object&gt; | Per-extension file counts and sizes (populated when include_stats=true) |
| `largest_dirs` | array&lt;object&gt; | Largest directories by recursive file count (populated when include_stats=true) |
| `next_offset` | integer | Continuation offset when max_chars truncates after at least one entry. Pass it as offset with all other options unchanged. Absent at the end or when no entry can fit. |
| `omitted_entries` | integer | Number of entries omitted from the tree (scanned_total - rendered_total) |
| `rendered_dirs` | integer | Number of directories appearing in the tree string (including root) |
| `rendered_files` | integer | Number of files appearing in the tree string |
| `root_path` | string | Canonical root directory path |
| `scan_cancelled` | boolean | True when the filesystem scan was cancelled due to max_scan_ms |
| `scan_truncated` | boolean | True when the filesystem scan was truncated due to max_scanned_files |
| `scanned_dirs` | integer | Number of directories counted during traversal (including root, per stats_scope) |
| `scanned_files` | integer | Number of files counted during traversal (per stats_scope) |
| `snapshot_fingerprint` | string | Stable fingerprint of the rendered structure; equal fingerprints mean the caller's cached tree is still current. |
| `stats_complete` | boolean | True when statistics are complete (scan was not truncated or cancelled) |
| `stats_scope` | enum | Effective statistics scope used for this result: 'rendered' or 'scanned'. Echoes the input or the 'rendered' default. Options: `rendered`, `scanned` |
| `tree` | string | Rendered directory tree string (character-budgeted) |
| `truncated` | boolean | True when the tree was truncated due to max_chars or max_depth |
| `truncated_reason` | string | Human-readable reason for truncation or cancellation |

## List Directory — `tools/list_directory`

List directory contents, return structured entry list. Supports glob filtering, recursive depth control, and offset pagination.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `directory_path` | string | ✅ | — | Absolute directory path to list |
| `max_depth` | integer | — | — | Recursion depth (0=directory itself only, 1=direct children). Default 1, max 5. |
| `offset` | integer | — | `0` | Pagination offset into matched entries, default 0 |
| `page_size` | integer | — | — | Entries to return for this page, default 50, max 200 |
| `pattern` | string | — | — | Filename glob filter (e.g. "*.rs"), matches filename only, not full path |
| `show_hidden` | boolean | — | `false` | Whether to show hidden files/directories (starting with .), default false |
| `verbose` | boolean | — | `false` | Return full per-entry metadata (absolute_path, modified, is_binary) when true. Default false returns only name/relative_path/entry_type/size_bytes/depth to keep directory browsing cheap; set true only when you actually need timestamps, binary detection, or absolute paths. |

### Outputs

| Field | Type | Description |
|---|---|---|
| `directory_path` | string | 规范化后的目录路径 |
| `elapsed_ms` | integer | 操作耗时（毫秒） |
| `entries` | array&lt;object&gt; | Entry list (depth-first, directories first, sorted by hierarchy) |
| `error` | string | Error message if the operation failed |
| `next_offset` | integer | Next page offset; None when no more entries |
| `offset` | integer | Current pagination offset |
| `page_size` | integer | Current page size |
| `returned_entries` | integer | 本页实际返回条目数 |
| `total_entries` | integer | 匹配的总条目数（分页前） |
| `truncated` | boolean | Whether more entries are available via offset=next_offset |

## Search Files — `tools/search_files`

Search text in files or directories, like grep, returns structured matching results. Supports regex, case-insensitive, glob filtering and context lines.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `case_insensitive` | boolean | — | `false` | Whether to ignore case, default false |
| `context_lines` | integer | — | — | Show N lines of context before/after each match (0-3), default 0 (content mode only) |
| `exclude_patterns` | array | — | — | Exclude filename glob list (e.g. ["*.bak", "target/**"]) |
| `file_types` | array | — | — | Language type filters (e.g. ["rust","ts","tsx"]). Recognized names map to common extensions; any unrecognized name is treated as a file extension glob (e.g. "vue" → *.vue). |
| `include_patterns` | array | — | — | Filename glob filter list (e.g. ["*.rs", "*.toml"]), only search matching files |
| `is_regex` | boolean | — | `false` | Whether query is a regex pattern, default false |
| `max_files` | integer | — | — | files_with_matches mode: max returned files, default 500, hard cap 5000 |
| `max_match_span_bytes` | integer | — | — | multiline: skip matches spanning more than this many bytes |
| `max_match_span_lines` | integer | — | — | multiline: skip matches spanning more than this many lines |
| `max_results` | integer | — | — | content mode: max returned matches, default 200, hard cap 1000 |
| `max_scanned_bytes` | integer | — | — | Upper bound on total text bytes scanned, default 256MB |
| `max_scanned_files` | integer | — | — | Upper bound on files opened/searched, default 50000 |
| `multiline` | boolean | — | `false` | Evaluate regex against full file text (spanning lines), default false |
| `output_mode` | enum | — | — | Output mode: content (default, returns matching lines), files_with_matches (only file paths + counts), count (only counts) |
| `query` | string | ✅ | — | Search keyword or regular expression |
| `search_path` | string | ✅ | — | Starting path for search (file or directory absolute path) |
| `timeout_ms` | integer | — | — | multiline: wall-clock timeout in ms, default 5000, hard cap 30000 |

### Outputs

| Field | Type | Description |
|---|---|---|
| `elapsed_ms` | integer | Operation elapsed time in milliseconds |
| `error` | string | Error message if the operation failed |
| `file_match_counts` | array&lt;object&gt; | Per-file match counts (files_with_matches / count mode) |
| `files_with_matches` | array&lt;string&gt; | List of file paths that contain matches (files_with_matches mode) |
| `matched_files` | integer | Number of files with matches |
| `matches` | array&lt;object&gt; | Match results list (content mode; sorted by file path + line number) |
| `searched_files` | integer | Number of files actually scanned |
| `total_matches` | integer | Number of matches returned (content mode: returned entries; other modes: total hits) |
| `truncated` | boolean | Whether results were truncated due to any budget limit |
| `truncated_reason` | string | Human-readable reason for truncation |

## Backup File — `tools/backup_file`

Create an explicit snapshot of a file under a hidden backup directory, for later rollback_file restore.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `backup_root` | string | — | — | Optional backup root directory |
| `file_path` | string | ✅ | — | File path to back up |
| `label` | string | — | — | Optional label for the backup |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | Unique backup identifier for use with rollback_file |
| `backup_path` | string | Path to the backup content file |
| `bytes_copied` | integer | Number of bytes copied to the backup |
| `error` | string | Error message when the backup failed |
| `file_path` | string | Canonical source file path that was backed up |

## Rollback File — `tools/rollback_file`

Restore a file from a snapshot created by backup_file. Atomic write; supports dry_run to preview the diff first. High side-effect.

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `allow_target_override` | boolean | — | `false` | Allow target path to differ from backup metadata, default false |
| `backup_id` | string | ✅ | — | backup_id from backup_file, or "last" to restore the most recent snapshot of the file |
| `backup_root` | string | — | — | Optional backup root directory |
| `dry_run` | boolean | ✅ | — | Preview diff without writing, default false |
| `file_path` | string | ✅ | — | File path to restore |

### Outputs

| Field | Type | Description |
|---|---|---|
| `backup_id` | string | Backup ID used for restoration |
| `diff` | string | Diff between current content and backup content |
| `dry_run` | boolean | Whether this was a dry run |
| `error` | string | Error message if the operation failed |
| `file_path` | string | Canonical file path |
| `restored` | boolean | Whether the file was restored (dry_run=true means restorable) |

## Next steps

- [Workspace Tool Nodes](/docs/nodes-workspace-tools)
- [Code Execution Nodes](/docs/nodes-code-runtime)
- [Using the CLI](/docs/cli-usage)
