From bd1053a3bca97842c2565ac3e9c26eae0ba7a755 Mon Sep 17 00:00:00 2001 From: worldmozara Date: Tue, 11 Aug 2026 18:56:28 +0800 Subject: [PATCH] core/config: document default truncate_token everywhere --- CLAUDE.md | 2 +- README.md | 2 +- src/plyngent/agent/directive_checkpoint.py | 2 +- src/plyngent/config/models.py | 4 ++-- tests/test_tools/test_process.py | 17 +++++++++++++++++ 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fe98fb2..6d7b90f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ Async SQLAlchemy + aiosqlite. `MemoryStore`: schema init (+ lightweight SQLite ` Module-level `@tool` handlers. Call `set_workspace_root()` before use. - **`workspace`**: path resolve under primary root **or** temporary allowlist roots; path substring denylist; command basename denylist (CLI: timed human allow override, default deny on timeout; **not** skipped by YOLO); clearer deny messages. -- **`file`**: `read_file` (`with_lineno`, `max_chars`; results start with a 1-based `L{begin}-{end}` range), `write_file`, `listdir`, `tree` (VCS + default noise dirs + optional `skip_dirs` / denylist walk), `glob_paths`, `grep_files` (regex, skip VCS/binary), `edit_replace` (`max_replaces`, reports remaining matches), `edit_lineno` (1-based range; only lines read via `with_lineno`; read state resets on any file write/edit/copy/move/delete), `copy_path` / `move_path` / `delete_path`, `new_temporary_workspace` (system temp allowlist; cleanup on chat exit; no session rebind), `get_truncated` (resume truncated `read_file`/`fetch` via `TRUNCATE_TOKEN`). +- **`file`**: `read_file` (`with_lineno`, `max_chars` default 32k; results start with a 1-based `L{begin}-{end}` range), `write_file`, `listdir`, `tree` (VCS + default noise dirs + optional `skip_dirs` / denylist walk), `glob_paths`, `grep_files` (regex, skip VCS/binary), `edit_replace` (`max_replaces`, reports remaining matches), `edit_lineno` (1-based range; only lines read via `with_lineno`; read state resets on any file write/edit/copy/move/delete), `copy_path` / `move_path` / `delete_path`, `new_temporary_workspace` (system temp allowlist; cleanup on chat exit; no session rebind), `get_truncated` (resume any truncated result via its `truncate_token=...`). - **`process`**: `run_argv` (argv, no shell, timeout, optional stdin/env); `run_argv_batch` (serial steps, `pipe_out` on provider, `mix_stderr`, `stop_on_error`); PTY `open_pty` / `read_pty` / `write_pty` (literal) / `write_pty_keys` (escapes) / `ask_into_pty` (human→PTY only; `secret` no-echo; answer never in tool result) / `close_pty` (POSIX: `pty`+`fork`; Windows: ConPTY via `pywinpty` env marker dep). - PTY: backend in `pty_backend.py`; structured status (`alive`/`exit_code`/`data`); `read_pty(..., until=)` (**CSI sanitized** so host TTY is never reset); keys via `write_pty_keys` only (`\xHH`, `ctrl+x`, `key=esc`); secrets via `ask_into_pty` (not `write_pty` data); session limit/idle TTL/output budget; close terminate→kill. - CLI limit hooks: interactive confirm to raise tool-loop rounds, PTY session cap, or PTY output budget. diff --git a/README.md b/README.md index a95e9e4..370e13c 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ User messages are saved immediately. On API error or Ctrl+C, partial assistant/t ## Tools (when enabled) -Default registry: file ops (including `tree` with default noise-dir skips), `run_argv` / `run_argv_batch` / PTY (POSIX openpty; Windows ConPTY via pywinpty), read-only VCS (git), HTTP `fetch` (GET/POST/PUT/DELETE via niquests; private/loopback hosts need a human policy grant, not YOLO), human prompts (`ask_user_line` / `ask_user_choice` / `ask_user_form`), `wait` (line prompt with timeout; Enter disturbs), `get_truncated` (resume truncated results via `TRUNCATE_TOKEN`), and todo stack tools (`todo_list` / `todo_push` / `todo_pop` / `todo_update` / `todo_clear`). +Default registry: file ops (including `tree` with default noise-dir skips), `run_argv` / `run_argv_batch` / PTY (POSIX openpty; Windows ConPTY via pywinpty), read-only VCS (git), HTTP `fetch` (GET/POST/PUT/DELETE via niquests; private/loopback hosts need a human policy grant, not YOLO), human prompts (`ask_user_line` / `ask_user_choice` / `ask_user_form`), `wait` (line prompt with timeout; Enter disturbs), `get_truncated` (resume any truncated result via its `truncate_token=...`), and todo stack tools (`todo_list` / `todo_push` / `todo_pop` / `todo_update` / `todo_clear`). Safety defaults: diff --git a/src/plyngent/agent/directive_checkpoint.py b/src/plyngent/agent/directive_checkpoint.py index a50c855..c99b178 100644 --- a/src/plyngent/agent/directive_checkpoint.py +++ b/src/plyngent/agent/directive_checkpoint.py @@ -22,7 +22,7 @@ use `run_argv_batch` for ordered pipelines. - `edit_replace`: fix match / `max_replaces`; `edit_lineno` edits only lines read via \ `read_file(with_lineno=true)`, and line numbers reset after any file write. - `read_file` results start with `L{begin}-{end}` (1-based lines; offset is 0-based). -- Truncated results carry a `TRUNCATE_TOKEN`; continue with `get_truncated`. +- Truncated results carry a `truncate_token=...`; continue with `get_truncated`. - Prefer `fetch` for HTTP(S); private/LAN hosts need human policy allow (not YOLO). - PTY secrets only via `ask_into_pty`; denylists and confirms still apply. - `wait` pauses; pressing Enter disturbs early (optional reason). diff --git a/src/plyngent/config/models.py b/src/plyngent/config/models.py index 1f819af..1239bf2 100644 --- a/src/plyngent/config/models.py +++ b/src/plyngent/config/models.py @@ -34,8 +34,8 @@ re-read with line numbers after any `edit_*` / `write_file` / copy/move/delete, since line numbers go stale. - `read_file` results start with a 1-based line range `L{begin}-{end}` (`offset` \ is 0-based); `with_lineno` shows per-line numbers instead. -- Truncated `read_file` / `fetch` results carry a `TRUNCATE_TOKEN`; use \ -`get_truncated` with it to keep reading (chains through truncations). +- Truncated results (any tool) carry a `truncate_token=...`; use `get_truncated` \ +with it to keep reading (chains through truncations). ### Commands - Prefer `run_argv` / `run_argv_batch` (argv lists, no shell) over `bash -c` or similar. diff --git a/tests/test_tools/test_process.py b/tests/test_tools/test_process.py index 2975ecf..349ab62 100644 --- a/tests/test_tools/test_process.py +++ b/tests/test_tools/test_process.py @@ -190,6 +190,23 @@ async def test_run_argv_timeout_keeps_partial_output(workspace: object) -> None: assert "partial-out" in out +async def test_run_argv_stdout_cap_embeds_token(workspace: object) -> None: + del workspace + # Per-stream cap default is 32000 chars: a bigger stdout embeds a token. + out = await call_async(run_argv, _py("print('x' * 40000)")) + assert "truncate_token=" in out + token_line = next(ln for ln in out.splitlines() if "truncate_token=" in ln) + assert "[Truncated (32000 chars max.;" in token_line + assert "[Truncated" in out + + +async def test_run_argv_small_stdout_no_marker(workspace: object) -> None: + del workspace + out = await call_async(run_argv, _py("print('tiny')")) + assert "Truncated" not in out + assert "truncate_token=" not in out + + async def test_run_argv_stdin(workspace: object) -> None: del workspace out = await call_async(