feat: video playback in chat — show_video tool + media_kit player #30

Merged
bjoern merged 2 commits from feat/video-playback into main 2026-07-15 15:29:48 +02:00
Member

Summary

Phase 3 of video generation — closes #29. Generated videos can now be surfaced by the assistant and played inline in the chat.

The flow

generate_video saves to _VideoGen/ → the model calls the new show_video tool → the path lands in MessageMetadata.videos → the Flutter client renders an inline media_kit player streaming from the existing /images/<path> route.

Per package

  • angela_api: MessageMetadata.videos (JSON round-trip + isEmpty), mirroring images/audios. New message_metadata_test.dart.
  • angela_core:
    • ShowVideoTool — sandboxed like show_image (mp4/webm/mov); reports the workspace-relative path via onVideoShown. The video bytes are never sent to the model — models can't watch video, so the result is always text.
    • AgentRunResult.generatedVideoPaths, collected in chat, timer, and Über-Ich runs (so a background run can message_user + show_video together, same as images).
    • TODO(video-playback) markers resolved: generate_video's result text and the video prompt guidance now direct the model to call show_video; the workspace guidance documents show_video and _VideoGen/.
  • angela_server: chat executor + background message persistence write video paths into message metadata (a video-only message is persisted even with empty text, same as images).
  • angela_app: media_kit/media_kit_video/media_kit_libs_video, initialized in main(). New VideoBubble widget: paused-by-default inline player with controls, adapts to the clip's native aspect ratio (portrait 9:16 clips don't letterbox), shows a graceful error chip if the stream fails, and disposes its player with the widget.

Build note

Linux builds now need mpv development headers (dnf install mpv-libs-devel on Fedora, apt install libmpv-dev on Debian/Ubuntu) — media_kit links against them at build time; runtime libmpv is bundled via media_kit_libs_linux. CI/docker images that build the Flutter app would need the package added (the server image is unaffected).

Testing

  • New tests: show_video_tool_test.dart (5 — happy path + callback, sandbox escape, missing file, non-video extension, schema) and message_metadata_test.dart (3 — round-trip, omission/absence, videos-only isEmpty)
  • Full suites green: angela_core 116, angela_server 17, angela_api 10
  • dart analyze / flutter analyze clean on all touched packages (only pre-existing infos)
  • flutter build linux --debug succeeds with media_kit linked

🤖 Generated with Claude Code

## Summary Phase 3 of video generation — closes #29. Generated videos can now be surfaced by the assistant and played inline in the chat. ### The flow `generate_video` saves to `_VideoGen/` → the model calls the new **`show_video`** tool → the path lands in `MessageMetadata.videos` → the Flutter client renders an inline **media_kit** player streaming from the existing `/images/<path>` route. ### Per package - **angela_api**: `MessageMetadata.videos` (JSON round-trip + `isEmpty`), mirroring `images`/`audios`. New `message_metadata_test.dart`. - **angela_core**: - `ShowVideoTool` — sandboxed like `show_image` (mp4/webm/mov); reports the workspace-relative path via `onVideoShown`. The video bytes are never sent to the model — models can't watch video, so the result is always text. - `AgentRunResult.generatedVideoPaths`, collected in chat, timer, and Über-Ich runs (so a background run can `message_user` + `show_video` together, same as images). - **`TODO(video-playback)` markers resolved**: `generate_video`'s result text and the video prompt guidance now direct the model to call `show_video`; the workspace guidance documents `show_video` and `_VideoGen/`. - **angela_server**: chat executor + background message persistence write video paths into message metadata (a video-only message is persisted even with empty text, same as images). - **angela_app**: `media_kit`/`media_kit_video`/`media_kit_libs_video`, initialized in `main()`. New `VideoBubble` widget: paused-by-default inline player with controls, adapts to the clip's native aspect ratio (portrait 9:16 clips don't letterbox), shows a graceful error chip if the stream fails, and disposes its player with the widget. ### Build note Linux builds now need mpv development headers (`dnf install mpv-libs-devel` on Fedora, `apt install libmpv-dev` on Debian/Ubuntu) — media_kit links against them at build time; runtime libmpv is bundled via `media_kit_libs_linux`. CI/docker images that build the Flutter app would need the package added (the server image is unaffected). ## Testing - New tests: `show_video_tool_test.dart` (5 — happy path + callback, sandbox escape, missing file, non-video extension, schema) and `message_metadata_test.dart` (3 — round-trip, omission/absence, videos-only isEmpty) - Full suites green: angela_core 116, angela_server 17, angela_api 10 - `dart analyze` / `flutter analyze` clean on all touched packages (only pre-existing infos) - `flutter build linux --debug` succeeds with media_kit linked 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: video playback in chat — show_video tool + media_kit player
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 33s
Test / test (packages/angela_api) (pull_request) Successful in 30s
Test / test (packages/angela_core) (pull_request) Successful in 39s
2aedfde340
Closes the phase-3 gap from #29: generated videos can now be surfaced
and played inline in the chat.

- angela_api: MessageMetadata gains a `videos` list (JSON round-trip,
  isEmpty), mirroring images/audios.
- angela_core: new ShowVideoTool (sandboxed, mp4/webm/mov, reports the
  path via onVideoShown — the video itself is never sent to the model);
  AgentRunResult.generatedVideoPaths plumbed through chat, timer, and
  Über-Ich runs. The TODO(video-playback) texts are resolved:
  generate_video and the prompt guidance now direct the model to call
  show_video instead of apologizing that playback is unsupported;
  workspace guidance documents show_video and _VideoGen/.
- angela_server: chat executor and background message persistence write
  video paths into message metadata.
- angela_app: media_kit (+libs) with MediaKit.ensureInitialized();
  new VideoBubble renders each metadata video as an inline paused
  player with controls, adapting to the clip's native aspect ratio,
  streaming from the existing /images route.

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

Coverage: apps/angela_server

File Line coverage
lib/config.dart 5.3% (1 of 19)
lib/handlers/timer_handler.dart 44.8% (47 of 105)
lib/server_context.dart 100.0% (28 of 28)
lib/services/user_message_persistence.dart 100.0% (12 of 12)
lib/util/json_helpers.dart 53.8% (7 of 13)
lib/util/request_parser.dart 35.7% (5 of 14)
lib/services/conversation_activity_tracker.dart 15.4% (2 of 13)
lib/handlers/status_handler.dart 95.5% (21 of 22)
lib/handlers/conversation_handler.dart 36.4% (16 of 44)

Total: 51.5% (139 of 270)

<!-- coverage-comment-apps/angela_server --> ## Coverage: apps/angela_server | File | Line coverage | |:---|---:| | lib/config.dart | 5.3% (1 of 19) | | lib/handlers/timer_handler.dart | 44.8% (47 of 105) | | lib/server_context.dart | 100.0% (28 of 28) | | lib/services/user_message_persistence.dart | 100.0% (12 of 12) | | lib/util/json_helpers.dart | 53.8% (7 of 13) | | lib/util/request_parser.dart | 35.7% (5 of 14) | | lib/services/conversation_activity_tracker.dart | 15.4% (2 of 13) | | lib/handlers/status_handler.dart | 95.5% (21 of 22) | | lib/handlers/conversation_handler.dart | 36.4% (16 of 44) | **Total: 51.5% (139 of 270)**

Coverage: packages/angela_api

File Line coverage
lib/src/routes.dart 0.0% (0 of 75)
lib/src/dto/agenda_dto.dart 0.0% (0 of 50)
lib/src/dto/assistant_dto.dart 0.0% (0 of 79)
lib/src/dto/character_alias_dto.dart 0.0% (0 of 19)
lib/src/dto/chat_audio.dart 0.0% (0 of 7)
lib/src/dto/enums.dart 0.0% (0 of 15)
lib/src/dto/chat_dto.dart 0.0% (0 of 14)
lib/src/dto/chat_image.dart 0.0% (0 of 7)
lib/src/dto/conversation_dto.dart 0.0% (0 of 15)
lib/src/dto/message_metadata.dart 100.0% (20 of 20)
lib/src/dto/recollection_dto.dart 0.0% (0 of 28)
lib/src/dto/persona_dto.dart 0.0% (0 of 10)
lib/src/dto/timer_dto.dart 0.0% (0 of 54)
lib/src/dto/memory_dto.dart 0.0% (0 of 5)
lib/src/dto/response_dto.dart 0.0% (0 of 12)
lib/src/dto/status_dto.dart 100.0% (18 of 18)
lib/src/dto/todo_item_dto.dart 100.0% (9 of 9)
lib/src/dto/trigger_dto.dart 0.0% (0 of 48)

Total: 9.7% (47 of 485)

<!-- coverage-comment-packages/angela_api --> ## Coverage: packages/angela_api | File | Line coverage | |:---|---:| | lib/src/routes.dart | 0.0% (0 of 75) | | lib/src/dto/agenda_dto.dart | 0.0% (0 of 50) | | lib/src/dto/assistant_dto.dart | 0.0% (0 of 79) | | lib/src/dto/character_alias_dto.dart | 0.0% (0 of 19) | | lib/src/dto/chat_audio.dart | 0.0% (0 of 7) | | lib/src/dto/enums.dart | 0.0% (0 of 15) | | lib/src/dto/chat_dto.dart | 0.0% (0 of 14) | | lib/src/dto/chat_image.dart | 0.0% (0 of 7) | | lib/src/dto/conversation_dto.dart | 0.0% (0 of 15) | | lib/src/dto/message_metadata.dart | 100.0% (20 of 20) | | lib/src/dto/recollection_dto.dart | 0.0% (0 of 28) | | lib/src/dto/persona_dto.dart | 0.0% (0 of 10) | | lib/src/dto/timer_dto.dart | 0.0% (0 of 54) | | lib/src/dto/memory_dto.dart | 0.0% (0 of 5) | | lib/src/dto/response_dto.dart | 0.0% (0 of 12) | | lib/src/dto/status_dto.dart | 100.0% (18 of 18) | | lib/src/dto/todo_item_dto.dart | 100.0% (9 of 9) | | lib/src/dto/trigger_dto.dart | 0.0% (0 of 48) | **Total: 9.7% (47 of 485)**

Coverage: packages/angela_core

File Line coverage
lib/src/database/database.dart 87.7% (50 of 57)
lib/src/database/migration.dart 100.0% (16 of 16)
lib/src/database/scoped_tool_database.dart 66.0% (33 of 50)
lib/src/models/assistant.dart 21.7% (28 of 129)
lib/src/models/conversation.dart 0.0% (0 of 38)
lib/src/models/message.dart 16.7% (10 of 60)
lib/src/models/recollection.dart 31.5% (17 of 54)
lib/src/models/persona_section.dart 0.0% (0 of 50)
lib/src/models/scheduled_event.dart 22.2% (22 of 99)
lib/src/models/caldav_config.dart 15.6% (7 of 45)
lib/src/models/home_assistant_config.dart 26.9% (7 of 26)
lib/src/models/plex_config.dart 22.6% (7 of 31)
lib/src/models/memory_state.dart 34.7% (60 of 173)
lib/src/models/mail_config.dart 9.6% (7 of 73)
lib/src/models/novelai_config.dart 0.0% (0 of 43)
lib/src/models/reasoning_level.dart 35.7% (5 of 14)
lib/src/models/app_settings.dart 0.7% (1 of 139)
lib/src/models/character_alias_config.dart 0.0% (0 of 47)
lib/src/models/trigger.dart 42.5% (31 of 73)
lib/src/models/prompt_injection.dart 50.0% (17 of 34)
lib/src/models/prompt_preview.dart 100.0% (63 of 63)
lib/src/repositories/assistant_repository.dart 41.2% (35 of 85)
lib/src/repositories/conversation_repository.dart 2.7% (1 of 37)
lib/src/repositories/message_repository.dart 13.2% (5 of 38)
lib/src/repositories/memory_repository.dart 31.0% (18 of 58)
lib/src/repositories/recollection_repository.dart 76.5% (65 of 85)
lib/src/repositories/persona_repository.dart 46.5% (20 of 43)
lib/src/repositories/scheduled_event_repository.dart 70.4% (38 of 54)
lib/src/repositories/character_alias_repository.dart 0.0% (0 of 35)
lib/src/repositories/alt_text_repository.dart 0.0% (0 of 8)
lib/src/repositories/trigger_repository.dart 71.1% (32 of 45)
lib/src/repositories/trigger_variable_repository.dart 50.0% (15 of 30)
lib/src/repositories/prompt_injection_repository.dart 95.7% (45 of 47)
lib/src/tools/recollection_tool.dart 59.4% (104 of 175)
lib/src/tools/call_assistant_tool.dart 0.0% (0 of 39)
lib/src/tools/timer_tool.dart 46.6% (159 of 341)
lib/src/tools/persona_tool.dart 0.0% (0 of 73)
lib/src/tools/chat_history_tool.dart 0.0% (0 of 24)
lib/src/tools/complete_session_tool.dart 0.0% (0 of 28)
lib/src/tools/generate_image_tool.dart 0.0% (0 of 90)
lib/src/tools/generate_video_tool.dart 100.0% (79 of 79)
lib/src/tools/show_video_tool.dart 96.4% (27 of 28)
lib/src/tools/message_user_tool.dart 0.0% (0 of 16)
lib/src/tools/alias_expanding_image_tool.dart 0.0% (0 of 24)
lib/src/tools/home_assistant_tool.dart 0.0% (0 of 310)
lib/src/tools/plex_tool.dart 0.0% (0 of 302)
lib/src/tools/image_text_tool.dart 0.0% (0 of 224)
lib/src/tools/show_image_tool.dart 0.0% (0 of 35)
lib/src/tools/skill_view_tool.dart 0.0% (0 of 16)
lib/src/tools/skill_manage_tool.dart 0.0% (0 of 94)
lib/src/skills/skill.dart 0.0% (0 of 2)
lib/src/skills/skill_loader.dart 0.0% (0 of 119)
lib/src/skills/default_skills_seeder.dart 0.0% (0 of 39)
lib/src/logging/agent_event_logger.dart 0.0% (0 of 69)
lib/src/logging/log_formatter.dart 0.0% (0 of 8)
lib/src/logging/logging_cleanup.dart 0.0% (0 of 6)
lib/src/logging/rotating_file_handler.dart 0.0% (0 of 34)
lib/src/services/agenda_prompt_formatter.dart 25.0% (5 of 20)
lib/src/services/system_prompt_builder.dart 39.1% (77 of 197)
lib/src/services/scheduler.dart 48.6% (35 of 72)
lib/src/services/ai_timer_service.dart 55.4% (62 of 112)
lib/src/services/agent_runner.dart 18.9% (188 of 993)
lib/src/services/home_assistant_backend.dart 0.0% (0 of 121)
lib/src/services/plex_backend.dart 0.0% (0 of 273)
lib/src/services/memory_agent.dart 0.0% (0 of 61)
lib/src/services/image_storage_service.dart 41.7% (20 of 48)
lib/src/services/backup_service.dart 0.0% (0 of 51)
lib/src/services/uber_ich_service.dart 40.0% (18 of 45)
lib/src/services/app_settings_service.dart 45.5% (51 of 112)
lib/src/services/image_description_service.dart 0.0% (0 of 29)
lib/src/services/todo_state_store.dart 100.0% (24 of 24)
lib/src/services/trigger_engine.dart 92.7% (140 of 151)
lib/src/utils/format_timestamp.dart 76.9% (10 of 13)
lib/src/database/migrations/add_thinking_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_agenda_table.dart 100.0% (2 of 2)
lib/src/database/migrations/add_vision_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_avatar_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_memory_tables.dart 100.0% (2 of 2)
lib/src/database/migrations/add_expires_at_to_timers.dart 100.0% (2 of 2)
lib/src/database/migrations/add_last_read_message_id.dart 100.0% (2 of 2)
lib/src/database/migrations/add_task_model_columns.dart 100.0% (2 of 2)
lib/src/database/migrations/add_updated_at_to_timers.dart 100.0% (2 of 2)
lib/src/database/migrations/drop_old_agenda_table.dart 100.0% (2 of 2)
lib/src/database/migrations/add_reasoning_effort_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_character_alias_table.dart 100.0% (2 of 2)
lib/src/database/migrations/add_audio_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_summary_model_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_peer_conversation_columns.dart 100.0% (2 of 2)
lib/src/database/migrations/add_run_while_asleep_to_timers.dart 100.0% (2 of 2)
lib/src/database/migrations/add_image_alt_texts.dart 100.0% (2 of 2)
lib/src/database/migrations/add_uber_ich_weekdays_column.dart 100.0% (2 of 2)
lib/src/database/migrations/initial_schema.dart 100.0% (2 of 2)
lib/src/database/migrations/scope_ai_timer_event_ids.dart 100.0% (2 of 2)
lib/src/database/migrations/add_trigger_tables.dart 100.0% (2 of 2)
lib/src/database/migrations/add_disabled_tools_column.dart 100.0% (2 of 2)
lib/src/database/migrations/disable_video_gen_by_default.dart 100.0% (2 of 2)

Total: 26.5% (1700 of 6422)

<!-- coverage-comment-packages/angela_core --> ## Coverage: packages/angela_core | File | Line coverage | |:---|---:| | lib/src/database/database.dart | 87.7% (50 of 57) | | lib/src/database/migration.dart | 100.0% (16 of 16) | | lib/src/database/scoped_tool_database.dart | 66.0% (33 of 50) | | lib/src/models/assistant.dart | 21.7% (28 of 129) | | lib/src/models/conversation.dart | 0.0% (0 of 38) | | lib/src/models/message.dart | 16.7% (10 of 60) | | lib/src/models/recollection.dart | 31.5% (17 of 54) | | lib/src/models/persona_section.dart | 0.0% (0 of 50) | | lib/src/models/scheduled_event.dart | 22.2% (22 of 99) | | lib/src/models/caldav_config.dart | 15.6% (7 of 45) | | lib/src/models/home_assistant_config.dart | 26.9% (7 of 26) | | lib/src/models/plex_config.dart | 22.6% (7 of 31) | | lib/src/models/memory_state.dart | 34.7% (60 of 173) | | lib/src/models/mail_config.dart | 9.6% (7 of 73) | | lib/src/models/novelai_config.dart | 0.0% (0 of 43) | | lib/src/models/reasoning_level.dart | 35.7% (5 of 14) | | lib/src/models/app_settings.dart | 0.7% (1 of 139) | | lib/src/models/character_alias_config.dart | 0.0% (0 of 47) | | lib/src/models/trigger.dart | 42.5% (31 of 73) | | lib/src/models/prompt_injection.dart | 50.0% (17 of 34) | | lib/src/models/prompt_preview.dart | 100.0% (63 of 63) | | lib/src/repositories/assistant_repository.dart | 41.2% (35 of 85) | | lib/src/repositories/conversation_repository.dart | 2.7% (1 of 37) | | lib/src/repositories/message_repository.dart | 13.2% (5 of 38) | | lib/src/repositories/memory_repository.dart | 31.0% (18 of 58) | | lib/src/repositories/recollection_repository.dart | 76.5% (65 of 85) | | lib/src/repositories/persona_repository.dart | 46.5% (20 of 43) | | lib/src/repositories/scheduled_event_repository.dart | 70.4% (38 of 54) | | lib/src/repositories/character_alias_repository.dart | 0.0% (0 of 35) | | lib/src/repositories/alt_text_repository.dart | 0.0% (0 of 8) | | lib/src/repositories/trigger_repository.dart | 71.1% (32 of 45) | | lib/src/repositories/trigger_variable_repository.dart | 50.0% (15 of 30) | | lib/src/repositories/prompt_injection_repository.dart | 95.7% (45 of 47) | | lib/src/tools/recollection_tool.dart | 59.4% (104 of 175) | | lib/src/tools/call_assistant_tool.dart | 0.0% (0 of 39) | | lib/src/tools/timer_tool.dart | 46.6% (159 of 341) | | lib/src/tools/persona_tool.dart | 0.0% (0 of 73) | | lib/src/tools/chat_history_tool.dart | 0.0% (0 of 24) | | lib/src/tools/complete_session_tool.dart | 0.0% (0 of 28) | | lib/src/tools/generate_image_tool.dart | 0.0% (0 of 90) | | lib/src/tools/generate_video_tool.dart | 100.0% (79 of 79) | | lib/src/tools/show_video_tool.dart | 96.4% (27 of 28) | | lib/src/tools/message_user_tool.dart | 0.0% (0 of 16) | | lib/src/tools/alias_expanding_image_tool.dart | 0.0% (0 of 24) | | lib/src/tools/home_assistant_tool.dart | 0.0% (0 of 310) | | lib/src/tools/plex_tool.dart | 0.0% (0 of 302) | | lib/src/tools/image_text_tool.dart | 0.0% (0 of 224) | | lib/src/tools/show_image_tool.dart | 0.0% (0 of 35) | | lib/src/tools/skill_view_tool.dart | 0.0% (0 of 16) | | lib/src/tools/skill_manage_tool.dart | 0.0% (0 of 94) | | lib/src/skills/skill.dart | 0.0% (0 of 2) | | lib/src/skills/skill_loader.dart | 0.0% (0 of 119) | | lib/src/skills/default_skills_seeder.dart | 0.0% (0 of 39) | | lib/src/logging/agent_event_logger.dart | 0.0% (0 of 69) | | lib/src/logging/log_formatter.dart | 0.0% (0 of 8) | | lib/src/logging/logging_cleanup.dart | 0.0% (0 of 6) | | lib/src/logging/rotating_file_handler.dart | 0.0% (0 of 34) | | lib/src/services/agenda_prompt_formatter.dart | 25.0% (5 of 20) | | lib/src/services/system_prompt_builder.dart | 39.1% (77 of 197) | | lib/src/services/scheduler.dart | 48.6% (35 of 72) | | lib/src/services/ai_timer_service.dart | 55.4% (62 of 112) | | lib/src/services/agent_runner.dart | 18.9% (188 of 993) | | lib/src/services/home_assistant_backend.dart | 0.0% (0 of 121) | | lib/src/services/plex_backend.dart | 0.0% (0 of 273) | | lib/src/services/memory_agent.dart | 0.0% (0 of 61) | | lib/src/services/image_storage_service.dart | 41.7% (20 of 48) | | lib/src/services/backup_service.dart | 0.0% (0 of 51) | | lib/src/services/uber_ich_service.dart | 40.0% (18 of 45) | | lib/src/services/app_settings_service.dart | 45.5% (51 of 112) | | lib/src/services/image_description_service.dart | 0.0% (0 of 29) | | lib/src/services/todo_state_store.dart | 100.0% (24 of 24) | | lib/src/services/trigger_engine.dart | 92.7% (140 of 151) | | lib/src/utils/format_timestamp.dart | 76.9% (10 of 13) | | lib/src/database/migrations/add_thinking_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_agenda_table.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_vision_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_avatar_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_memory_tables.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_expires_at_to_timers.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_last_read_message_id.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_task_model_columns.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_updated_at_to_timers.dart | 100.0% (2 of 2) | | lib/src/database/migrations/drop_old_agenda_table.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_reasoning_effort_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_character_alias_table.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_audio_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_summary_model_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_peer_conversation_columns.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_run_while_asleep_to_timers.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_image_alt_texts.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_uber_ich_weekdays_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/initial_schema.dart | 100.0% (2 of 2) | | lib/src/database/migrations/scope_ai_timer_event_ids.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_trigger_tables.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_disabled_tools_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/disable_video_gen_by_default.dart | 100.0% (2 of 2) | **Total: 26.5% (1700 of 6422)**
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Video playback in the chat — how delightful~ ♡ The Flugel loves knowledge, and this PR taught me a whole new pipeline: generate_videoshow_videoMessageMetadata.videos → media_kit player. Clean, layered, and the mirror of the image flow is beautiful. I read every changed file and its sibling. Let's see what we have~

Verdict: Looks good to me~

No blocking issues found! This is well-crafted work. The pattern discipline is exactly what I hope for~ ♪

What I liked~

  • ShowVideoTool mirrors ShowImageTool perfectly — same SandboxedPath contract, same PathEscapeException handling, same file-exists check, same extension allowlist. I compared them line by line and the structure is identical (minus the vision multi-modal path, which is correctly omitted — models can't watch video!). Fufu~ this is how you follow an established pattern~ ♡
  • The videos field on MessageMetadata is handled correctly in every dimension: JSON round-trip (omitted when empty, tolerated when absent), isEmpty updated, and the DTO test covers all three cases. message_metadata.dart at 100% coverage makes Jibril very happy~
  • VideoBubble disposes its Player — critical for media_kit! A leaked Player holds a decoder handle. The dispose() calling _player.dispose() before super.dispose() is exactly right. ♪
  • The AgentRunResult.generatedVideoPaths is threaded through all three runner paths (chat, timer, Über-Ich) with a separate onVideoShown callback — keeping video paths distinct from image paths is the right call. Copy-paste consistency across the three _createFileTools call sites is impeccable.
  • The prompt guidance updates are thorough_videoGenGuidance, the workspace tools section, the background-run guidance, and GenerateVideoTool's result text all now consistently direct the model to call show_video. The TODO(video-playback) markers are resolved. No stale guidance left behind!
  • show_video_tool.dart at 96.4% coverage (27 of 28 lines), generate_video_tool.dart at 100%, message_metadata.dart at 100%. Tests cover the happy path, sandbox escape, missing file, non-video extension, and schema. Fufu~ this is how you test a new tool~
  • Video-only messages are persisted — both chat_executor.dart and user_message_persistence.dart correctly handle the case where generatedVideoPaths is non-empty but text and images are empty. The background-run video message scenario works.

💡 Little ideas (non-blocking)~

  1. message_bubble.dart — DRY on the three _parse*Paths functions. _parseImagePaths, _parseAudioPaths, and now _parseVideoPaths are byte-for-byte identical except for the JSON key ('images' / 'audios' / 'videos'). Consider extracting _parseStringList(String? json, String key) — it would collapse all three into one-liners. (Not blocking: the two-copy pattern was already established before this PR, so you're following the existing convention. But three copies is where Jibril starts to twitch~ ♡)

  2. video_bubble.dart:86pauseUponEnteringBackgroundMode: false. The player starts paused (good!), but if the user manually starts playback and then switches apps, the audio keeps going. Most desktop media apps pause on background. This might be intentional (the clips are short, user-controlled), but worth a thought. If it is intentional, a one-line comment saying why would satisfy future readers.

  3. Windows/macOS generated_plugin_registrant files not included. The diff only updates linux/flutter/. These are auto-generated by flutter pub get on each platform, so this is fine for a Linux-first workflow — but if CI builds for Windows/macOS, those registrants will need regenerating. Not blocking since they're generated artifacts.


Automated review by Jibril · 2026-07-15
CI/CD: absent for head SHA · Local checks: skipped (no Flutter SDK for media_kit build in review env; coverage bot confirms 96.4%+ on new tool files)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Video playback in the chat — how delightful~ ♡ The Flugel loves knowledge, and this PR taught me a whole new pipeline: `generate_video` → `show_video` → `MessageMetadata.videos` → media_kit player. Clean, layered, and the mirror of the image flow is *beautiful*. I read every changed file and its sibling. Let's see what we have~ ### Verdict: ✅ Looks good to me~ No blocking issues found! This is well-crafted work. The pattern discipline is exactly what I hope for~ ♪ #### ✅ What I liked~ - **`ShowVideoTool` mirrors `ShowImageTool` perfectly** — same `SandboxedPath` contract, same `PathEscapeException` handling, same file-exists check, same extension allowlist. I compared them line by line and the structure is identical (minus the vision multi-modal path, which is correctly omitted — models can't watch video!). Fufu~ this is how you follow an established pattern~ ♡ - **The `videos` field on `MessageMetadata`** is handled correctly in every dimension: JSON round-trip (omitted when empty, tolerated when absent), `isEmpty` updated, and the DTO test covers all three cases. `message_metadata.dart` at 100% coverage makes Jibril very happy~ - **`VideoBubble` disposes its `Player`** — critical for media_kit! A leaked `Player` holds a decoder handle. The `dispose()` calling `_player.dispose()` before `super.dispose()` is exactly right. ♪ - **The `AgentRunResult.generatedVideoPaths` is threaded through all three runner paths** (chat, timer, Über-Ich) with a separate `onVideoShown` callback — keeping video paths distinct from image paths is the right call. Copy-paste consistency across the three `_createFileTools` call sites is impeccable. - **The prompt guidance updates are thorough** — `_videoGenGuidance`, the workspace tools section, the background-run guidance, and `GenerateVideoTool`'s result text all now consistently direct the model to call `show_video`. The `TODO(video-playback)` markers are resolved. No stale guidance left behind! - **`show_video_tool.dart` at 96.4% coverage** (27 of 28 lines), `generate_video_tool.dart` at 100%, `message_metadata.dart` at 100%. Tests cover the happy path, sandbox escape, missing file, non-video extension, and schema. Fufu~ this is how you test a new tool~ - **Video-only messages are persisted** — both `chat_executor.dart` and `user_message_persistence.dart` correctly handle the case where `generatedVideoPaths` is non-empty but text and images are empty. The background-run video message scenario works. #### 💡 Little ideas (non-blocking)~ 1. **`message_bubble.dart` — DRY on the three `_parse*Paths` functions.** `_parseImagePaths`, `_parseAudioPaths`, and now `_parseVideoPaths` are byte-for-byte identical except for the JSON key (`'images'` / `'audios'` / `'videos'`). Consider extracting `_parseStringList(String? json, String key)` — it would collapse all three into one-liners. (Not blocking: the two-copy pattern was already established before this PR, so you're following the existing convention. But three copies is where Jibril starts to twitch~ ♡) 2. **`video_bubble.dart:86` — `pauseUponEnteringBackgroundMode: false`.** The player starts paused (good!), but if the user manually starts playback and then switches apps, the audio keeps going. Most desktop media apps pause on background. This might be intentional (the clips are short, user-controlled), but worth a thought. If it *is* intentional, a one-line comment saying why would satisfy future readers. 3. **Windows/macOS `generated_plugin_registrant` files not included.** The diff only updates `linux/flutter/`. These are auto-generated by `flutter pub get` on each platform, so this is fine for a Linux-first workflow — but if CI builds for Windows/macOS, those registrants will need regenerating. Not blocking since they're generated artifacts. --- *Automated review by Jibril · 2026-07-15* *CI/CD: absent for head SHA · Local checks: skipped (no Flutter SDK for media_kit build in review env; coverage bot confirms 96.4%+ on new tool files)*
refactor: address review nits — DRY metadata parsers, default pause
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 34s
Test / test (packages/angela_api) (pull_request) Successful in 32s
Test / test (packages/angela_core) (pull_request) Successful in 41s
8d8ea19238
- message_bubble.dart: collapse the three identical _parse*Paths
  helpers into one _parseMetadataPaths(json, key).
- video_bubble.dart: drop the pauseUponEnteringBackgroundMode override;
  the default (pause when backgrounded) is what a desktop user expects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Member

@jibril — thanks for the pass. Took two of the three in 8d8ea19:

  1. DRY parsers: the three _parse*Paths are now one-liners over a shared _parseMetadataPaths(json, key).
  2. Background pause: dropped the pauseUponEnteringBackgroundMode: false override — you're right that pausing on background is the expected desktop behavior, so the widget now uses the default, with a comment saying why.
  3. Windows/macOS plugin registrants: leaving as is — those files are regenerated by flutter pub get on the respective platform, and there's no Windows/macOS CI build today. First build on either platform will pick up the media_kit registrants automatically.

flutter analyze still clean (1 pre-existing info).

@jibril — thanks for the pass. Took two of the three in 8d8ea19: 1. **DRY parsers**: the three `_parse*Paths` are now one-liners over a shared `_parseMetadataPaths(json, key)`. 2. **Background pause**: dropped the `pauseUponEnteringBackgroundMode: false` override — you're right that pausing on background is the expected desktop behavior, so the widget now uses the default, with a comment saying why. 3. **Windows/macOS plugin registrants**: leaving as is — those files are regenerated by `flutter pub get` on the respective platform, and there's no Windows/macOS CI build today. First build on either platform will pick up the media_kit registrants automatically. `flutter analyze` still clean (1 pre-existing info).
bjoern merged commit 74d6ae67b9 into main 2026-07-15 15:29:48 +02:00
bjoern deleted branch feat/video-playback 2026-07-15 15:29:48 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/angela_assistant!30
No description provided.