a digital person for bluesky

Filter out failed bluesky_reply tool calls and improve logging

- Only process bluesky_reply tool calls with status: "success"
- Skip tool calls with status: "error" to prevent sending failed replies
- Add two-pass processing: collect tool results first, then filter tool calls
- Increase log preview content from 100 to 150 characters for better visibility
- Add clear warning messages when skipping failed or unknown status tool calls
- Prevent sending replies that failed validation (e.g. >300 char limit)

๐Ÿค– Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+45 -26
+45 -26
bsky.py
··· 327 327 # Log condensed chunk info 328 328 if hasattr(chunk, 'message_type'): 329 329 if chunk.message_type == 'reasoning_message': 330 - logger.info(f"๐Ÿง  Reasoning: {chunk.reasoning[:100]}...") 330 + logger.info(f"๐Ÿง  Reasoning: {chunk.reasoning[:150]}...") 331 331 elif chunk.message_type == 'tool_call_message': 332 - logger.info(f"๐Ÿ”ง Tool call: {chunk.tool_call.name}({chunk.tool_call.arguments[:50]}...)") 332 + logger.info(f"๐Ÿ”ง Tool call: {chunk.tool_call.name}({chunk.tool_call.arguments[:150]}...)") 333 333 elif chunk.message_type == 'tool_return_message': 334 334 logger.info(f"๐Ÿ“‹ Tool result: {chunk.name} - {chunk.status}") 335 335 elif chunk.message_type == 'assistant_message': 336 - logger.info(f"๐Ÿ’ฌ Assistant: {chunk.content[:100]}...") 336 + logger.info(f"๐Ÿ’ฌ Assistant: {chunk.content[:150]}...") 337 337 else: 338 - logger.info(f"๐Ÿ“จ {chunk.message_type}: {str(chunk)[:100]}...") 338 + logger.info(f"๐Ÿ“จ {chunk.message_type}: {str(chunk)[:150]}...") 339 339 else: 340 340 logger.info(f"๐Ÿ“ฆ Stream status: {chunk}") 341 341 ··· 400 400 logger.debug("Successfully received response from Letta API") 401 401 logger.debug(f"Number of messages in response: {len(message_response.messages) if hasattr(message_response, 'messages') else 'N/A'}") 402 402 403 - # Extract all bluesky_reply tool calls from the agent's response 403 + # Extract successful bluesky_reply tool calls from the agent's response 404 404 reply_candidates = [] 405 + tool_call_results = {} # Map tool_call_id to status 406 + 405 407 logger.debug(f"Processing {len(message_response.messages)} response messages...") 406 408 409 + # First pass: collect tool return statuses 410 + for message in message_response.messages: 411 + if hasattr(message, 'tool_call_id') and hasattr(message, 'status') and hasattr(message, 'name'): 412 + if message.name == 'bluesky_reply': 413 + tool_call_results[message.tool_call_id] = message.status 414 + logger.debug(f"Tool result: {message.tool_call_id} -> {message.status}") 415 + 416 + # Second pass: process messages and check for successful tool calls 407 417 for i, message in enumerate(message_response.messages, 1): 408 418 # Log concise message info instead of full object 409 419 msg_type = getattr(message, 'message_type', 'unknown') ··· 415 425 elif hasattr(message, 'tool_return'): 416 426 tool_name = getattr(message, 'name', 'unknown_tool') 417 427 return_preview = str(message.tool_return)[:100] if message.tool_return else "None" 418 - logger.debug(f" {i}. {msg_type}: {tool_name} -> {return_preview}...") 428 + status = getattr(message, 'status', 'unknown') 429 + logger.debug(f" {i}. {msg_type}: {tool_name} -> {return_preview}... (status: {status})") 419 430 elif hasattr(message, 'text'): 420 431 logger.debug(f" {i}. {msg_type}: {message.text[:100]}...") 421 432 else: ··· 439 450 logger.info("=== BOT TERMINATED BY AGENT ===") 440 451 exit(0) 441 452 442 - # Collect bluesky_reply tool calls 453 + # Collect bluesky_reply tool calls - only if they were successful 443 454 if hasattr(message, 'tool_call') and message.tool_call: 444 455 if message.tool_call.name == 'bluesky_reply': 445 - try: 446 - args = json.loads(message.tool_call.arguments) 447 - # Handle both old format (message) and new format (messages) 448 - reply_messages = args.get('messages', []) 449 - if not reply_messages: 450 - # Fallback to old format for backward compatibility 451 - old_message = args.get('message', '') 452 - if old_message: 453 - reply_messages = [old_message] 454 - 455 - reply_lang = args.get('lang', 'en-US') 456 - if reply_messages: # Only add if there's actual content 457 - reply_candidates.append((reply_messages, reply_lang)) 458 - if len(reply_messages) == 1: 459 - logger.info(f"Found bluesky_reply candidate: {reply_messages[0][:50]}... (lang: {reply_lang})") 460 - else: 461 - logger.info(f"Found bluesky_reply thread candidate with {len(reply_messages)} messages (lang: {reply_lang})") 462 - except json.JSONDecodeError as e: 463 - logger.error(f"Failed to parse tool call arguments: {e}") 456 + tool_call_id = message.tool_call.tool_call_id 457 + tool_status = tool_call_results.get(tool_call_id, 'unknown') 458 + 459 + if tool_status == 'success': 460 + try: 461 + args = json.loads(message.tool_call.arguments) 462 + # Handle both old format (message) and new format (messages) 463 + reply_messages = args.get('messages', []) 464 + if not reply_messages: 465 + # Fallback to old format for backward compatibility 466 + old_message = args.get('message', '') 467 + if old_message: 468 + reply_messages = [old_message] 469 + 470 + reply_lang = args.get('lang', 'en-US') 471 + if reply_messages: # Only add if there's actual content 472 + reply_candidates.append((reply_messages, reply_lang)) 473 + if len(reply_messages) == 1: 474 + logger.info(f"Found successful bluesky_reply candidate: {reply_messages[0][:50]}... (lang: {reply_lang})") 475 + else: 476 + logger.info(f"Found successful bluesky_reply thread candidate with {len(reply_messages)} messages (lang: {reply_lang})") 477 + except json.JSONDecodeError as e: 478 + logger.error(f"Failed to parse tool call arguments: {e}") 479 + elif tool_status == 'error': 480 + logger.info(f"โš ๏ธ Skipping failed bluesky_reply tool call (status: error)") 481 + else: 482 + logger.warning(f"โš ๏ธ Skipping bluesky_reply tool call with unknown status: {tool_status}") 464 483 465 484 if reply_candidates: 466 485 logger.info(f"Found {len(reply_candidates)} bluesky_reply candidates, trying each until one succeeds...")