feat(tools): salience-aware shell output compression — retain error lines through HeadTail (#67)

HeadTail now accepts an optional salience regex + cap: middle lines matching
it (error|fail|exception|panic|traceback|✗, case-insensitive) survive
truncation in place instead of being silently dropped, so a decisive error
buried in the middle of a long build/test log still reaches the model-facing
context entry. ShellTool's outputCompressor spec wires this in; the raw
build-gate receipt path is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 12:13:31 +04:00
parent 3e31ebcc1e
commit e5a5cddc96
4 changed files with 75 additions and 7 deletions
@@ -62,7 +62,11 @@ class ShellTool(
// head/tail window holds distinct content rather than duplicates. Dedup is lossless
// (count is preserved) and order-preserving, so it is safe as a default — unlike a
// progress-dropping regex, which can silently eat real output.
listOf(StripBlankLines, CollapseConsecutiveDuplicates, HeadTail(HEAD_LINES, TAIL_LINES)),
listOf(
StripBlankLines,
CollapseConsecutiveDuplicates,
HeadTail(HEAD_LINES, TAIL_LINES, salience = SALIENCE_PATTERN),
),
bypassOnError = true,
),
)
@@ -213,6 +217,9 @@ class ShellTool(
private companion object {
const val HEAD_LINES = 40
const val TAIL_LINES = 40
// Case-insensitive: a decisive error line buried in the middle of a long build/test log
// (line 250 of 500) survives the head/tail window instead of being silently dropped.
const val SALIENCE_PATTERN = "error|fail|exception|panic|traceback|✗"
const val EMPTY_MSG = "Missing or empty 'argv' parameter. Expected a JSON array of strings."
val SHELL_BUILTINS = setOf("cd", "export", "source", ".", "pushd", "popd", "umask", "ulimit")
val SHELL_OPERATORS = setOf("&&", "||", "|", ">", ">>", "<", ";", "&", "2>", "2>&1")