Add read_file skill, session persistence, and update script

- New read_file skill: paginated file reading with line ranges,
  path restricted to /workspace, binary detection, directory listing
- Session persistence via SQLite + FTS5: conversation history survives
  agent restarts, last N messages restored into deque on boot,
  auto-prune to 1000 messages
- Update truncation hint to reference read_file instead of run_command
- New scripts/update.sh for patching rootfs + rebuilding snapshot

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 14:49:54 +00:00
parent 6673210ff0
commit 5b312e34de
6 changed files with 262 additions and 1 deletions

17
skills/read_file/SKILL.md Normal file
View File

@@ -0,0 +1,17 @@
---
name: read_file
description: Read a file from the workspace with optional line range. Use this to view large tool outputs, logs, or any file in /workspace.
parameters:
path:
type: string
description: Path to the file to read (must be under /workspace)
required: true
offset:
type: integer
description: Start reading from this line number (default 1)
required: false
limit:
type: integer
description: Maximum number of lines to return (default 200)
required: false
---

67
skills/read_file/run.py Normal file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Read a file from /workspace with optional line range."""
import json
import os
import sys
args = json.loads(sys.stdin.read())
path = args.get("path", "")
offset = max(int(args.get("offset", 1)), 1)
limit = max(int(args.get("limit", 200)), 1)
WORKSPACE = os.environ.get("WORKSPACE", "/workspace")
# Resolve to absolute and ensure it stays under /workspace
resolved = os.path.realpath(path)
if not resolved.startswith(WORKSPACE + "/") and resolved != WORKSPACE:
print(f"[error: path must be under {WORKSPACE}]")
sys.exit(0)
if not os.path.exists(resolved):
print(f"[error: file not found: {path}]")
sys.exit(0)
if os.path.isdir(resolved):
entries = sorted(os.listdir(resolved))
print(f"Directory listing of {path} ({len(entries)} entries):")
for entry in entries[:100]:
full = os.path.join(resolved, entry)
kind = "dir" if os.path.isdir(full) else "file"
print(f" {entry} ({kind})")
if len(entries) > 100:
print(f" ... and {len(entries) - 100} more")
sys.exit(0)
# Binary detection — check first 512 bytes
try:
with open(resolved, "rb") as f:
chunk = f.read(512)
if b"\x00" in chunk:
size = os.path.getsize(resolved)
print(f"[binary file: {path} ({size} bytes)]")
sys.exit(0)
except Exception as e:
print(f"[error reading file: {e}]")
sys.exit(0)
# Read with line range
try:
with open(resolved) as f:
lines = f.readlines()
except Exception as e:
print(f"[error reading file: {e}]")
sys.exit(0)
total = len(lines)
start_idx = offset - 1 # 0-based
end_idx = min(start_idx + limit, total)
if start_idx >= total:
print(f"[file has {total} lines — offset {offset} is beyond end of file]")
sys.exit(0)
for i in range(start_idx, end_idx):
print(f"{i + 1}\t{lines[i]}", end="")
if end_idx < total:
print(f"\n[showing lines {offset}-{end_idx} of {total} — use offset={end_idx + 1} to read more]")