Complete implementation of all three native Anthropic tools:
- bash_20250124: Shell command execution
- text_editor_20250124: File editing operations
- computer_20251124: VNC desktop control (all 17 actions)
Includes provider updates, test improvements, and registry changes.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Registry now supports executing both native Anthropic tools (via __call__)
and function tools (via execute). Native tools return ToolResult/CLIResult
objects instead of strings.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add test confirming BashTool20250124, EditTool20250728, and
ComputerTool20251124 are registered in AgentLoop on initialization.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Verify that screenshots from computer tool are tracked in
media_paths_for_turn and included in OutboundMessage.media.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Apply same result type handling logic to _process_system_message
to support native tools in subagent/system contexts.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaced by BashTool20250124 and EditTool20250728 which provide
better functionality via model-trained behaviors.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BashTool20250124, EditTool20250728, and ComputerTool20251124 are now
automatically registered alongside existing function tools.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Screenshots from computer tool are saved to disk and included in
OutboundMessage.media for channel delivery to users.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comprehensive tests for:
- ToolResult with output field
- ToolResult with error field
- ToolResult with base64_image field
- CLIResult handling
- Legacy string results (backward compatibility)
- Combined output and error fields
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Agent loop now processes ToolResult and CLIResult from native tools,
while maintaining backward compatibility with string results.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
CLIResult now requires exit_code, output, and error fields after
EditTool implementation. Update test to match new signature.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add computer_20251124 for VNC desktop control. Supports keyboard,
mouse, and screenshots via vncdotool.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add test_edit_tool_requires_absolute_path and test_edit_tool_to_params
to complete test coverage per specification.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Make test more specific by expecting exact sorted order
instead of checking both permutations.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extract beta_flag from tool objects before conversion and add
to API request headers. Supports multiple flags via comma-join.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
_convert_tools_to_anthropic now passes through native tool format
(bash_20250124, etc.) while still converting function tools.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Registry now supports both function tools (to_schema) and native
tools (to_params) via hasattr checks. Enables mixed tool types.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add BaseAnthropicTool, ToolResult, CLIResult, and ToolError.
These support native tools with version-coupled behaviors.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Design for integrating bash_20250124, text_editor_20250728, and
computer_20251124 native tools into nanobot. These tools leverage
model-trained behaviors instead of instruction-following.
Key approach: Duck-typed registry supporting both function tools
and native tools, with beta flag management and ToolResult handling.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: send_document and send_audio were receiving raw bytes
without filename metadata, causing Telegram to use generic
"application.octet-stream" name.
Solution: Extract filename from path and pass via filename parameter
to send_document/send_audio and their InputMedia counterparts.
Images/videos unaffected as Telegram infers names from content type.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The _send_with_media method uses Path but the import was missing from the top-level imports, causing "name 'Path' is not defined" error.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add missing media parameter to message tool, enabling agents to send media attachments via the message() tool.
Changes:
- message.py: Add media parameter to tool schema and execute() method
- telegram.py: Restore _send_with_media() method with album support
This completes the three-layer media architecture:
- Agent Loop: message tool now accepts media parameter
- Provider Layer: OutboundMessage carries media list
- Telegram Channel: _send_with_media processes and sends media
Fixes the root cause where agents had no way to specify media attachments, resulting in invented [file: ...] syntax in message content.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add context manager to PIL Image.open() to prevent file handle leaks
- Fix caption logic for separate media (track first non-album item)
- Improve exception handling with size checks and specific types
- Add constants for magic numbers (REMOTE_MEDIA_SIZE_LIMIT)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Processes local files and remote URLs
Optimizes images automatically
Groups 2+ images/videos into albums
Handles caption overflow (>1024 chars)
Routes to correct Telegram API methods
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Downloads from URLs with 10s timeout
Detects MIME from response headers or content
Enforces size limit
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Classifies MIME types into IMAGE/VIDEO/AUDIO/DOCUMENT
Used for routing to correct Telegram API methods
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Required for media handling:
- pillow-heif: HEIC to JPEG conversion
- python-magic: MIME type detection
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated descriptions to make it clear the message tool sends to the
main agent (who processes and decides how to respond), not directly
to the user.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Subagents can now send messages to users via the message tool.
Messages are routed through the main agent via the bus and preserve
metadata (e.g. suppress_output) from the originating message.
Changes:
1. Created SubagentMessageTool that creates InboundMessages
2. Publishes to bus with preserved metadata
3. Registered in subagent tool registry
4. Updated subagent system prompt to mention message capability
This allows subagents to communicate findings during execution while
respecting suppression flags (e.g. heartbeat subagents won't spam Telegram).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When a subagent completes and announces its result, it now inherits the
metadata (including suppress_output) from the original message that spawned it.
Changes:
1. SpawnTool.set_context() now accepts metadata parameter
2. SubagentManager.spawn() now accepts origin_metadata parameter
3. Origin dict now includes 'metadata' field
4. _announce_result() includes metadata when creating InboundMessage
5. All set_context() calls pass msg.metadata
This fixes the bug where heartbeat suppression was lost when subagents
announced their results - the subagent announcement would be visible on
Telegram even though the heartbeat itself was suppressed.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Store results in _task_results for ALL subagents, regardless of origin.
Then only announce to bus if it's a top-level subagent (not a child).
This fixes the bug where wait_for_subagents would return 'No result found'
for subagents spawned from the main telegram session, because their results
went to the bus instead of _task_results.
Now:
- All subagents store results in _task_results (so wait_for can find them)
- Child subagents (origin[channel] == 'subagent') return early (no announcement)
- Top-level subagents continue and announce via bus (so main agent gets notified)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The heartbeat service was incorrectly counting ALL messages with role="user"
as user activity, including system-generated messages (heartbeat prompts,
subagent announcements). This caused the idle detection to never trigger
because heartbeat's own messages were counted as user activity.
Changes:
1. Store sender_id in session messages (loop.py)
- Added sender_id=msg.sender_id to session.add_message() call
- Allows distinguishing real user messages from system-generated ones
2. Filter by sender_id in heartbeat idle detection (service.py)
- Real Telegram messages have sender_id like "239824268|username"
- System messages via process_direct have sender_id="user" (hardcoded)
- Heartbeat now skips messages with sender_id="user"
- Backwards compatible: messages without sender_id are treated as real
This is a robust, source-based solution that checks how messages are
CREATED rather than pattern-matching their content.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated existing tests to work with cryptographic visibility markers:
1. test_agent_loop_metadata.py:
- Updated test_suppress_mode_adds_hidden_prefix to verify [HIDDEN:signature] format
- Added validation for 8-character hex signature
- Updated test_normal_mode_no_hidden_prefix to check for [HIDDEN: prefix
2. test_idle_heartbeat_integration.py:
- Updated test_idle_heartbeat_end_to_end to search for [HIDDEN: prefix
- Added signature format validation (8-char hex)
- Updated docstring to reflect signed markers
All 86 tests now pass (excluding OAuth tests as specified).
The changes maintain backwards compatibility while enforcing
the new cryptographic signing requirement.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extends signed marker support to _process_system_message() for subagent
announcements. Ensures consistency across all suppress mode paths.
- Add forgery detection in system message loop
- Sign content before saving to session when suppressed
- Return unsuppressed content with suppressed metadata
- Add comprehensive tests for system message signing and forgery rejection
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Detects forged [HIDDEN:*] markers in model output and triggers rejection
with retry. Includes correction message to model and fallback stripping
if model persists. Prevents accumulation from model forgery attempts.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces simple [HIDDEN] prefix with cryptographically signed markers
in _process_message(). Strips any forged markers from model output
before signing with system key.
Includes comprehensive accumulation test that verifies:
- Markers are properly signed when suppress_output=True
- Model seeing markers in context doesn't copy them
- No accumulation of markers across multiple messages
- Each hidden message gets exactly one signed marker
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Verifies that system prompt includes documentation about cryptographically
signed visibility markers.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Documents the purpose of [HIDDEN:{sig}] markers and explicitly forbids
model from generating them. Sets clear expectations for rejection behavior.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements verify_signature() to check HMAC validity, has_forged_marker()
to detect forgery attempts, and strip_all_hidden_markers() for cleanup.
Comprehensive test coverage for all verification scenarios.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements sign_content() to cryptographically sign message content
with HMAC-SHA256 (8-char truncated). This prevents models from forging
visibility markers as they cannot generate valid signatures.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Override SessionManager.sessions_dir to use tmp_path, preventing
session accumulation across test runs.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Use tmp_path fixture for automatic cleanup
- Assert exactly 1 heartbeat message (not > 0)
- Use test-specific session key instead of production key
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Bug discovered during integration test: suppress mode was
prefixing content AFTER saving to session, so session stored
unprefixed content while only the outbound message was prefixed.
Fix: Move suppress check before session save and use prefixed
content when adding to session messages.
test: add end-to-end integration test for idle heartbeat
Verifies complete flow:
- Idle detection triggers heartbeat
- Heartbeat runs in main session
- Output is suppressed with [HIDDEN] prefix
- Session contains full context
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add future annotations import for type hints
- Add TYPE_CHECKING import for SessionManager forward reference
- Remove unused response variable in _tick method
- Fix whitespace in docstring
Tests directory changes (removed unused imports) remain in working tree
but are not committed due to .gitignore.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Heartbeat now:
- Checks last user message timestamp in target session
- Only triggers if >30min elapsed since last user message
- Passes suppress_output metadata to callback
- Removes HEARTBEAT_OK check (unnecessary with suppress mode)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes bug where suppressed messages would leave typing indicator
running forever. Now _stop_typing() is called before early return.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Messages with metadata['suppressed']=True are logged but not sent
to Telegram API, enabling heartbeat to run without spamming user.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Allows passing metadata through process_direct() for features like
suppress mode in heartbeat.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- MessageTool now writes sent messages to session history via SessionManager
- Agent loop wires SessionManager into MessageTool constructor
- Session.get_history() returns full history (removed max_messages limit)
Server-side context editing API handles trimming, so we send full history
This ensures messages sent via the message() tool (e.g., from heartbeat forks)
are visible in the main conversational agent's context.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sending a slice of history can cut in the middle of a tool chain, causing
'unexpected tool_use_id' 400 errors when the API receives an orphaned
tool_result without its preceding assistant tool_use block.
The server-side context editing API (clear_tool_uses_20250919) handles
trimming safely at token thresholds while respecting tool chain boundaries.
Let the server manage context length; send the full history from the client.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- session/manager.py: add add_raw_message() to persist tool chain messages;
get_history() now passes all API-relevant fields (tool_calls, tool_call_id,
name, reasoning_content) instead of stripping to role+content only
- loop.py: after each turn, save the complete message sequence (tool_use,
tool_results, thinking blocks, final reply) instead of just the final text;
remove automatic consolidation trigger — server-side context editing handles
the token window now; _consolidate_memory (runs on /new) updated to handle
list content, tool messages, and new message formats
- anthropic_oauth.py: add context_management parameter to chat() and
_make_request(); log context edits applied by Anthropic; log context_mgmt
strategies in request log line
- oauth_utils.py: add context-management-2025-06-27 beta header
- base.py, litellm_provider.py: propagate context_management parameter
CONTEXT_MANAGEMENT config on every agent call:
- clear_thinking_20251015 keep="all" → preserve all thinking blocks for cache
- clear_tool_uses_20250919 trigger=80k tokens, keep=5 recent tool uses
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
SubagentManager was receiving model=self.model (Opus) from the main
agent loop, overriding the intended "claude-sonnet-4-6" fallback in
SubagentManager.__init__. Subagents should default to Sonnet unless
explicitly overridden via the spawn tool call.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Restore f-string prefix so {self.workspace} interpolates correctly
- Add task parameter back to _build_subagent_prompt signature
- Instruct subagents to run exec date as first action (avoids
injecting dynamic timestamp into system prompt that busts cache)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
MEMORY.md is updated by the Haiku consolidator after every session,
changing the system prompt and busting the 1h cache. Replace it with
KNOWLEDGE.md — a static, manually-curated file that stays stable.
MEMORY.md remains accessible to the agent via read/grep tools.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
session.add_message was storing raw msg.content without the [Current time: ...]
prefix, causing cache key mismatches on subsequent turns since the API received
the prefixed version but history replayed the raw version.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The system prompt included a minute-resolution timestamp that changed every
call, busting the 1h cache on every request. Move current time to a [Current
time: ...] prefix on each user message instead, keeping the system prompt
static for cache hits. Also clarify the time-gap notice text.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Cache last user message on every API call (5m TTL) so full conversation
history is a cache read on subsequent turns
- Skip "Reflect on the results" interleave prompt when thinking_budget > 0
since extended thinking already handles reflection internally; keeps
message caching valid across tool iterations
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Cache system prompt and tool definitions on every API call to reduce
quota burn. Uses 1-hour TTL so context stays warm across conversations.
Also logs cache_write/cache_read token counts in response log line.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously wait_for_subagents was only registered inside _run_subagent
(spawned orchestrators). Main conversation agent had no access to it.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>