personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Callosum error notifications for audio and sense handler failures

When audio capture encounters a fatal format error or a sense handler
process fails, emit a notification event via Callosum so the user gets
visible feedback instead of silent log entries.

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

+68 -3
+15 -1
observe/hear.py
··· 84 84 block_count += 1 85 85 except (TypeError, ValueError, AttributeError) as e: 86 86 # Audio device returned unexpected format - trigger clean shutdown 87 + error_msg = f"Fatal audio format error: {e}" 87 88 logging.error( 88 - f"Fatal audio format error - triggering clean shutdown: {e}\n" 89 + f"{error_msg} - triggering clean shutdown\n" 89 90 f" mic_chunk type={type(mic_chunk)}, " 90 91 f"shape={getattr(mic_chunk, 'shape', 'N/A')}, " 91 92 f"dtype={getattr(mic_chunk, 'dtype', 'N/A')}\n" ··· 93 94 f"shape={getattr(sys_chunk, 'shape', 'N/A')}, " 94 95 f"dtype={getattr(sys_chunk, 'dtype', 'N/A')}" 95 96 ) 97 + 98 + # Notify user via Callosum 99 + from think.callosum import callosum_send 100 + 101 + callosum_send( 102 + "notification", 103 + "show", 104 + message=error_msg, 105 + title="Audio Capture Error", 106 + icon="🔇", 107 + app="hear", 108 + ) 109 + 96 110 # Stop recording thread 97 111 self._running = False 98 112 # Send SIGTERM to trigger graceful shutdown (same as Ctrl-C)
+22 -2
observe/sense.py
··· 393 393 ) 394 394 except ValueError: 395 395 log_rel = handler_proc.managed.log_writer.path 396 + 397 + error_msg = f"{handler_proc.handler_name} failed with exit {exit_code}" 396 398 logger.error( 397 - f"{handler_proc.handler_name} failed for {handler_proc.file_path.name} " 398 - f"with exit code {exit_code} ({elapsed:.1f}s) - see log {log_rel}" 399 + f"{error_msg} for {handler_proc.file_path.name} " 400 + f"({elapsed:.1f}s) - see log {log_rel}" 399 401 ) 402 + 403 + # Notify user via Callosum 404 + if self.callosum: 405 + icon = "🤖" 406 + if handler_proc.handler_name == "transcribe": 407 + icon = "🎙️" 408 + elif handler_proc.handler_name == "describe": 409 + icon = "👁️" 410 + 411 + self.callosum.emit( 412 + "notification", 413 + "show", 414 + message=f"{handler_proc.handler_name.capitalize()} failed for {handler_proc.file_path.name}", 415 + title=f"{handler_proc.handler_name.capitalize()} Error", 416 + icon=icon, 417 + app="sense", 418 + action=f"/health/logs?path={log_rel}", 419 + ) 400 420 401 421 # Mark file as done so segment can still complete 402 422 self._check_segment_observed(
+31
tests/test_sense.py
··· 451 451 assert test_file not in sensor.running 452 452 453 453 454 + def test_file_sensor_failing_process_notifies(tmp_path): 455 + """Test that a failing handler process emits a notification event.""" 456 + sensor = FileSensor(tmp_path) 457 + # Mock callosum on sensor to capture emitted events 458 + sensor.callosum = MagicMock() 459 + 460 + test_file = tmp_path / "test.txt" 461 + test_file.write_text("content") 462 + 463 + # Spawn a command that will fail 464 + sensor._spawn_handler(test_file, "fail", ["false"]) 465 + 466 + # Wait for process to complete and monitor thread to run 467 + time.sleep(0.5) 468 + 469 + # Check that a notification event was emitted 470 + # sensor.callosum.emit is called with ('notification', 'show', ...) 471 + # Search for a call where the first two args are 'notification' and 'show' 472 + notif_call = None 473 + for call in sensor.callosum.emit.call_args_list: 474 + args, kwargs = call 475 + if len(args) >= 2 and args[0] == "notification" and args[1] == "show": 476 + notif_call = call 477 + break 478 + 479 + assert notif_call is not None 480 + _, kwargs = notif_call 481 + assert "fail failed" in kwargs.get("message").lower() 482 + assert kwargs.get("title") == "Fail Error" 483 + 484 + 454 485 def test_file_sensor_handle_file(tmp_path): 455 486 """Test file handling dispatches to correct handler.""" 456 487 with patch.object(FileSensor, "_spawn_handler") as mock_spawn: