Combine malformed tool-call handling with placeholder filtering and a
no-tools fallback so a relay that returns tool_use blocks with null
id/name/input can no longer crash a turn or permanently wedge a session.
Adapted to the ContextGovernor architecture (context governance now lives
in nanobot/agent/context_governance.py, not runner.py):
- ToolCallRequest.has_valid_name(): single source of truth for "usable
name" (non-empty string).
- tool_hints.format_tool_hints(): skip tool calls with a non-string/empty
name instead of raising AttributeError on the whole turn.
- ContextGovernor.strip_placeholder_assistant_messages() and
strip_malformed_tool_calls() (plus the _tool_call_name_is_valid helper):
history-cleaning staticmethods invoked at the START of
prepare_for_model() — strip_placeholder, then strip_malformed, then the
existing drop_orphan/backfill chain. Both only repair the model-facing
copy and leave persisted history untouched (return a copy, or the same
list when nothing changes). Also wired into runner's minimal-repair path.
- AgentRunner._drop_malformed_tool_calls(): returns
(dropped, all_dropped, original_finish_reason); clears finish_reason to
"stop" when all calls are dropped.
- AgentRunner._malformed_tool_call_retry_messages() + _request_model
malformed_retry flag: when an all-dropped tool_calls response comes back,
retry once with a corrective note; if the retry STILL comes back
all-dropped, fall back to _request_no_tools for graceful text degradation.
Tests for the history-cleaning methods live with ContextGovernor in
tests/agent/test_runner_governance.py; response-layer and tool-hint tests
stay on AgentRunner / tool_hints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GlobTool is redundant — GrepTool already supports glob-based file
filtering via its `glob` parameter, making a standalone glob-only
tool unnecessary. Removing it simplifies the tool surface and reduces
LLM confusion between glob and grep.
The is_path branch in _fmt_known was not passing max_length to
abbreviate_path, so read_file, write_file, edit, list_dir, and
web_fetch always truncated paths at 40 chars regardless of config.
Now all three branches (is_path, is_command, fallback) honor the
configured toolHintMaxLength.
Add to config (default: 40, range: 20-500).
Controls how many characters of tool hints are shown in progress updates
(e.g. '$ cd …/project && npm test').
Set to 120+ to see full commands instead of truncated hints:
```json
{
"agents": {
"defaults": {
"toolHintMaxLength": 120
}
}
}
```
- Thread max_length through format_tool_hints → _fmt_known/_fmt_mcp/_fmt_fallback
- Make path abbreviation in _abbreviate_command proportional to max_length
- Add TestToolHintMaxLength test class with 5 tests
- All 41 existing tests pass
Preserve path folding for quoted exec command paths with spaces so hint previews do not fall back to mid-path truncation. Add regression coverage for quoted Unix and Windows path cases.
Made-with: Cursor
1. exec tool hints previously used val[:40] blind character truncation,
cutting paths mid-segment. Now detects file paths via regex and
abbreviates them with abbreviate_path. Supports Windows, Unix
absolute, and ~/ home paths.
2. Deduplication now compares fully formatted hint strings instead of
tool names alone. Fixes ls /Desktop and ls /Downloads being
incorrectly merged as "ls /Desktop × 2".
Co-authored-by: xzq.xu <zhiqiang.xu@nodeskai.com>
- Move _tool_hint implementation from loop.py to nanobot/utils/tool_hints.py
- Keep thin delegation in AgentLoop._tool_hint for backward compat
- Update test imports to test format_tool_hints directly
Made-with: Cursor