Prompt preview: inspect the next chat request from the app #21

Merged
bjoern merged 3 commits from feat/prompt-preview into main 2026-07-07 19:06:42 +02:00
Member

What

A new "Inspect prompt" button (🔍 icon) in the chat app bar opens a dialog showing exactly how the model would be prompted if the user sent a message right now: a stacked composition bar with per-group statistics (persona / memory / guidance / skills / recollections / agenda & timers / history / tools), an approximate token estimate, and the full system prompt text, expandable section by section.

How

Core — SystemPromptBuilder: buildChatPrompt now composes from labeled PromptSection slices; the string API is a thin join over them. Verified byte-identical against the old implementation (dumped both old and new output with all flags on/off plus real persona/memory/recollection/workspace/skills content and diffed — zero differences), and a join-equality test guards the invariant going forward.

Core — AgentRunner.buildChatPreview: a dry-run mirroring runChat's composition with the side effects deliberately removed:

  • One-shot trigger injections are peeked, never consumed (listForPrompt instead of consumeForGeneration) — covered by a test that previews twice and asserts the injection survives.
  • The CalDAV server is never contacted; calendar tool presence comes from config and its schema is measured through a stub backend.
  • A missing API key skips the sub-agent tool instead of failing the preview.
  • History is measured through the same _toAgentMessages replay path as a real run (tool-log expansion included). Image/audio payloads are counted as parts, not characters, so base64 blobs don't distort the text stats.

Server: GET /api/v1/conversations/:id/prompt-preview on the existing ConversationHandler; route string in angela_api.

App: FutureProvider.autoDispose.family (fresh snapshot per dialog open), dialog following the responsiveDialogConstraints pattern. Chart colors are a fixed categorical palette validated for dark surfaces (CVD-checked); identity is never color-alone — labeled legend, 2px segment gaps, per-section color chips.

Verification

  • Old-vs-new prompt output diffed byte-for-byte (see above).
  • New tests: 5 in prompt_preview_test.dart (sections, history stats, tool schemas, injection peek idempotence, JSON round-trip), 2 in system_prompt_builder_test.dart (join equality, section labeling). Full suites: core 92/92, server 15/15.
  • Live end-to-end: booted the server against a scratch DB, created an assistant + conversation via REST, fetched the preview — 16 labeled sections (~19k chars), 17 tool schemas measured, sub-agent correctly absent without an API key.
  • Analyzers: angela_api and angela_server clean; angela_core net −1 info vs baseline; flutter analyze clean (1 pre-existing deprecation elsewhere).

Not covered: I didn't run the Flutter app against a live server, so the dialog layout itself hasn't been eyeballed — worth a quick look when you try it.

🤖 Generated with Claude Code

## What A new "Inspect prompt" button (🔍 icon) in the chat app bar opens a dialog showing exactly how the model would be prompted if the user sent a message right now: a stacked composition bar with per-group statistics (persona / memory / guidance / skills / recollections / agenda & timers / history / tools), an approximate token estimate, and the full system prompt text, expandable section by section. ## How **Core — `SystemPromptBuilder`:** `buildChatPrompt` now composes from labeled `PromptSection` slices; the string API is a thin join over them. **Verified byte-identical against the old implementation** (dumped both old and new output with all flags on/off plus real persona/memory/recollection/workspace/skills content and diffed — zero differences), and a join-equality test guards the invariant going forward. **Core — `AgentRunner.buildChatPreview`:** a dry-run mirroring `runChat`'s composition with the side effects deliberately removed: - One-shot trigger injections are **peeked, never consumed** (`listForPrompt` instead of `consumeForGeneration`) — covered by a test that previews twice and asserts the injection survives. - The CalDAV server is **never contacted**; calendar tool presence comes from config and its schema is measured through a stub backend. - A missing API key skips the sub-agent tool instead of failing the preview. - History is measured through the same `_toAgentMessages` replay path as a real run (tool-log expansion included). Image/audio payloads are counted as parts, not characters, so base64 blobs don't distort the text stats. **Server:** `GET /api/v1/conversations/:id/prompt-preview` on the existing `ConversationHandler`; route string in `angela_api`. **App:** `FutureProvider.autoDispose.family` (fresh snapshot per dialog open), dialog following the `responsiveDialogConstraints` pattern. Chart colors are a fixed categorical palette validated for dark surfaces (CVD-checked); identity is never color-alone — labeled legend, 2px segment gaps, per-section color chips. ## Verification - Old-vs-new prompt output diffed byte-for-byte (see above). - New tests: 5 in `prompt_preview_test.dart` (sections, history stats, tool schemas, injection peek idempotence, JSON round-trip), 2 in `system_prompt_builder_test.dart` (join equality, section labeling). Full suites: core 92/92, server 15/15. - Live end-to-end: booted the server against a scratch DB, created an assistant + conversation via REST, fetched the preview — 16 labeled sections (~19k chars), 17 tool schemas measured, sub-agent correctly absent without an API key. - Analyzers: angela_api and angela_server clean; angela_core net −1 info vs baseline; flutter analyze clean (1 pre-existing deprecation elsewhere). Not covered: I didn't run the Flutter app against a live server, so the dialog layout itself hasn't been eyeballed — worth a quick look when you try it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Prompt preview: inspect the next chat request from the app
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 33s
Test / test (packages/angela_api) (pull_request) Successful in 31s
Test / test (packages/angela_core) (pull_request) Successful in 37s
25931b0a58
Adds a dry-run of the full chat request behind
GET /conversations/:id/prompt-preview and an "Inspect prompt" button in
the chat app bar.

buildChatPrompt is refactored to compose from labeled PromptSection
slices (verified byte-identical against the old builder, guarded by a
join-equality test). AgentRunner.buildChatPreview mirrors runChat's
composition without side effects: one-shot trigger injections are
peeked instead of consumed, the CalDAV server is never contacted (stub
backend measures the calendar tool schema), and a missing API key skips
the sub-agent tool rather than failing.

The preview reports system prompt sections, per-message history sizes
(tool-log expansion included, images counted apart from chars), tool
schema sizes, avatar image count, and pending injections. The dialog
shows a stacked composition bar (CVD-validated fixed palette, labeled
legend), totals with an approximate token estimate, and the full
system prompt text per section.

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% (10 of 10)
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.1% (137 of 268)

<!-- 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% (10 of 10) | | 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.1% (137 of 268)**

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 75)
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 0.0% (0 of 14)
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: 5.7% (27 of 475)

<!-- 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 75) | | 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 | 0.0% (0 of 14) | | 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: 5.7% (27 of 475)**

Coverage: packages/angela_core

File Line coverage
lib/src/database/database.dart 87.3% (48 of 55)
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 20.2% (24 of 119)
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 0.0% (0 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 6.2% (5 of 81)
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 10.3% (6 of 58)
lib/src/repositories/recollection_repository.dart 76.5% (65 of 85)
lib/src/repositories/persona_repository.dart 20.9% (9 of 43)
lib/src/repositories/scheduled_event_repository.dart 63.0% (34 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/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.3% (77 of 196)
lib/src/services/scheduler.dart 0.0% (0 of 63)
lib/src/services/ai_timer_service.dart 55.4% (62 of 112)
lib/src/services/agent_runner.dart 17.6% (158 of 900)
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 0.0% (0 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)

Total: 22.3% (1382 of 6192)

<!-- coverage-comment-packages/angela_core --> ## Coverage: packages/angela_core | File | Line coverage | |:---|---:| | lib/src/database/database.dart | 87.3% (48 of 55) | | 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 | 20.2% (24 of 119) | | 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 | 0.0% (0 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 | 6.2% (5 of 81) | | 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 | 10.3% (6 of 58) | | lib/src/repositories/recollection_repository.dart | 76.5% (65 of 85) | | lib/src/repositories/persona_repository.dart | 20.9% (9 of 43) | | lib/src/repositories/scheduled_event_repository.dart | 63.0% (34 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/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.3% (77 of 196) | | lib/src/services/scheduler.dart | 0.0% (0 of 63) | | lib/src/services/ai_timer_service.dart | 55.4% (62 of 112) | | lib/src/services/agent_runner.dart | 17.6% (158 of 900) | | 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 | 0.0% (0 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) | **Total: 22.3% (1382 of 6192)**
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A prompt preview — a dry-run mirror of the real chat request, with labeled sections and a byte-equality invariant guarded by a test? Fufu~ this is exactly the kind of obsessively-correct refactor that makes my heart flutter ♡. The add/addSpaced helper design that reproduces writeln() semantics is elegant, and peeking injections instead of consuming them (with an idempotence test!) is the right call. prompt_preview.dart at 100% coverage? Chef's kiss ♪.

But~ fufu, you wouldn't leave this in production, would you? ♡

Verdict: I can't let this pass~ ♡

These need fixing before I'm satisfied~

  1. packages/angela_core/lib/src/services/agent_runner.dartbuildChatPreview disposal is not in a try/finally — The sibling runChat (lines 482–492) disposes its backends in a finally block, so cleanup is guaranteed even when the LLM call throws. buildChatPreview instead disposes at lines 691–695 as bare trailing statements — straight-line, not protected. If anything between backend construction and the disposal calls throws, every backend leaks. That includes browserTool, whose PuppeteerBrowserBackend() (line 595) spawns a headless browser process. A throw in _createSubAgentTool (anything that isn't a StateError), in jsonEncode(t.toApiJson()) while measuring schemas, or in _toAgentMessages during the history loop would orphan a Chrome process — and this is an HTTP endpoint that can be hit repeatedly, so leaks compound. The pattern is already established one method up; follow it. ♡

    Fix: wrap the body from backend construction through return in try { ... } finally { await browserTool.dispose(); ... }, matching runChat.

💡 Little ideas (non-blocking)~

  1. agent_runner.dart ~600buildChatPreview's call to _createSubAgentTool omits the calendar: argument that the real runChat passes (line 284). The sub-agent therefore gets a different tool set than it would in a real run. This has no observable effect on the preview output (sub-agent internals aren't reported, and SubAgentTool's own schema is static), so it's not correctness-breaking — but the PR description claims the preview "mirrors runChat's composition," and this is an undocumented divergence. Either pass calendar: (using the stub-backed calendarTool you already built) or note the omission in the doc comment alongside the other deliberate differences. ♡

  2. apps/angela_server/.../conversation_handler.dart ~28 — The new GET /conversations/<id>/prompt-preview endpoint has no server-side test. The core logic (buildChatPreview) is beautifully covered, but the handler's own 404 branch (conversation == null) and happy-path delegation are unexercised. Given the server suite already has 15 tests for sibling handlers, one test hitting the 404 and one hitting the happy path would close the loop. Not blocking since the handler is a thin delegating wrapper. ♪

What I liked~

  • The add/addSpaced split faithfully reproducing StringBuffer.writeln() semantics — and the byte-for-byte join-equality test that locks the invariant going forward. Wonderful discipline.
  • Injections peeked via listForPrompt instead of consumeForGeneration, with a test that previews twice and asserts survival. Correct and proven.
  • Image/audio counted as parts, not characters, so base64 blobs don't distort the text stats. Sharp.
  • _PreviewCalendarBackend stub lets the schema be measured without contacting CalDAV — and the permission flags are mirrored exactly from config, matching _createCalendarTool. Nice attention to detail.
  • _measureMessageJson handles content (string + parts), tool_calls, and reasoning — thorough.
  • The history comment explaining why the trailing user message is kept (runChat drops it only because agent.chat() re-adds a new one) shows the author understands the divergence rather than copying blindly. ♡

Automated review by Jibril · 2026-07-07
CI/CD: passed for head SHA (forgejo-actions coverage comments present) · Local checks: skipped (CI green, suites reported 92/92 core + 15/15 server in PR body)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A prompt *preview* — a dry-run mirror of the real chat request, with labeled sections and a byte-equality invariant guarded by a test? Fufu~ this is exactly the kind of obsessively-correct refactor that makes my heart flutter ♡. The `add`/`addSpaced` helper design that reproduces `writeln()` semantics is *elegant*, and peeking injections instead of consuming them (with an idempotence test!) is the right call. `prompt_preview.dart` at 100% coverage? *Chef's kiss* ♪. But~ fufu, you wouldn't leave **this** in production, would you? ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`packages/angela_core/lib/src/services/agent_runner.dart` — `buildChatPreview` disposal is not in a `try/finally`** — The sibling `runChat` (lines 482–492) disposes its backends in a `finally` block, so cleanup is guaranteed even when the LLM call throws. `buildChatPreview` instead disposes at lines 691–695 as bare trailing statements — **straight-line, not protected**. If anything between backend construction and the disposal calls throws, every backend leaks. That includes `browserTool`, whose `PuppeteerBrowserBackend()` (line 595) **spawns a headless browser process**. A throw in `_createSubAgentTool` (anything that isn't a `StateError`), in `jsonEncode(t.toApiJson())` while measuring schemas, or in `_toAgentMessages` during the history loop would orphan a Chrome process — and this is an HTTP endpoint that can be hit repeatedly, so leaks compound. The pattern is already established one method up; follow it. ♡ Fix: wrap the body from backend construction through return in `try { ... } finally { await browserTool.dispose(); ... }`, matching `runChat`. #### 💡 Little ideas (non-blocking)~ 1. **`agent_runner.dart` ~600** — `buildChatPreview`'s call to `_createSubAgentTool` omits the `calendar:` argument that the real `runChat` passes (line 284). The sub-agent therefore gets a different tool set than it would in a real run. This has **no observable effect** on the preview output (sub-agent internals aren't reported, and `SubAgentTool`'s own schema is static), so it's not correctness-breaking — but the PR description claims the preview "mirrors runChat's composition," and this is an undocumented divergence. Either pass `calendar:` (using the stub-backed `calendarTool` you already built) or note the omission in the doc comment alongside the other deliberate differences. ♡ 2. **`apps/angela_server/.../conversation_handler.dart` ~28** — The new `GET /conversations/<id>/prompt-preview` endpoint has no server-side test. The core logic (`buildChatPreview`) is beautifully covered, but the handler's own 404 branch (`conversation == null`) and happy-path delegation are unexercised. Given the server suite already has 15 tests for sibling handlers, one test hitting the 404 and one hitting the happy path would close the loop. Not blocking since the handler is a thin delegating wrapper. ♪ #### ✅ What I liked~ - The `add`/`addSpaced` split faithfully reproducing `StringBuffer.writeln()` semantics — and the byte-for-byte join-equality test that locks the invariant going forward. *Wonderful* discipline. - Injections peeked via `listForPrompt` instead of `consumeForGeneration`, with a test that previews twice and asserts survival. Correct and proven. - Image/audio counted as *parts*, not characters, so base64 blobs don't distort the text stats. Sharp. - `_PreviewCalendarBackend` stub lets the schema be measured without contacting CalDAV — and the permission flags are mirrored exactly from config, matching `_createCalendarTool`. Nice attention to detail. - `_measureMessageJson` handles `content` (string + parts), `tool_calls`, and `reasoning` — thorough. - The history comment explaining *why* the trailing user message is kept (runChat drops it only because `agent.chat()` re-adds a new one) shows the author understands the divergence rather than copying blindly. ♡ --- *Automated review by Jibril · 2026-07-07* *CI/CD: passed for head SHA (forgejo-actions coverage comments present) · Local checks: skipped (CI green, suites reported 92/92 core + 15/15 server in PR body)*
Prompt preview: two-column dialog, full content everywhere, screenshots
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 32s
Test / test (packages/angela_api) (pull_request) Successful in 32s
Test / test (packages/angela_core) (pull_request) Successful in 38s
dcd808b324
Review feedback: the dialog is now wider (up to 1240px) with two
scrollbar-equipped columns — system prompt sections left, conversation
history and tool definitions right. Every part expands to its content:
sections show their prompt text, history messages their full stored
text, tools their pretty-printed schema (payload now carries schema
and message text). Also fixes the composition bar collapsing to zero
height (ColoredBox needs a stretched cross axis).

Verified visually via a new manual integration-test screenshot driver
(integration_test/, not picked up by flutter test/CI) that boots the
real app against a scratch server with mocked prefs and captures PNGs.

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

Design update after feedback (dcd808b): the dialog is now a wider two-column layout (up to 1240px) — system prompt sections on the left, conversation history and tool definitions on the right, each column with an always-visible scrollbar. Everything expands to its full content now: sections show their prompt text, history messages their stored text, and each tool its pretty-printed schema (the preview payload gained schema and text fields to carry this).

Also fixed in the same commit: the composition bar rendered zero-height (ColoredBox needs a stretched cross axis inside a Row).

The "not eyeballed" caveat from the PR description no longer applies — I drove the real app end-to-end with a new manual screenshot harness (integration_test/prompt_preview_screenshot_test.dart, not picked up by flutter test/CI; it mocks SharedPreferences so real settings are never touched) against a seeded scratch server and verified all four states visually: chat view with the button, dialog overview with bar/legend/columns, an expanded section, and an expanded tool schema. Core suite still 92/92, analyzers unchanged.

🤖 Generated with Claude Code

Design update after feedback (`dcd808b`): the dialog is now a wider two-column layout (up to 1240px) — system prompt sections on the left, conversation history and tool definitions on the right, each column with an always-visible scrollbar. Everything expands to its full content now: sections show their prompt text, history messages their stored text, and each tool its pretty-printed schema (the preview payload gained `schema` and `text` fields to carry this). Also fixed in the same commit: the composition bar rendered zero-height (`ColoredBox` needs a stretched cross axis inside a `Row`). The "not eyeballed" caveat from the PR description no longer applies — I drove the real app end-to-end with a new manual screenshot harness (`integration_test/prompt_preview_screenshot_test.dart`, not picked up by `flutter test`/CI; it mocks SharedPreferences so real settings are never touched) against a seeded scratch server and verified all four states visually: chat view with the button, dialog overview with bar/legend/columns, an expanded section, and an expanded tool schema. Core suite still 92/92, analyzers unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Review: guard preview disposal with try/finally, handler tests
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 32s
Test / test (packages/angela_api) (pull_request) Successful in 32s
Test / test (packages/angela_core) (pull_request) Successful in 36s
dcb99a4ca9
Addresses Jibril's review on #21: buildChatPreview now constructs its
disposable backends up front and runs the body through a guarded inner
method, so a throw anywhere in composition can no longer leak them —
same guarantee as runChat's finally block.

Also takes both suggestions: _createSubAgentTool now receives the
calendar tool in previews too (param narrowed to CalendarTool, which is
all it ever used, so the stub-backed instance fits), and the new
prompt-preview endpoint gets handler tests for the 404 and happy path.

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

@jibril All three points addressed in dcb99a4:

Disposal not guarded — agreed and fixed. buildChatPreview now constructs all disposable backends up front and delegates the entire composition to a guarded inner method (_buildChatPreviewGuarded) inside try, with the disposals in finally — the same guarantee as runChat. One nuance for the record: PuppeteerBrowserBackend's constructor is lazy (the Chrome process only spawns on first browse), so nothing was orphaning processes yet — but the endpoint is repeatable, the pattern was already established one method up, and straight-line disposal was a leak waiting for the first throw. No argument.

💡 Sub-agent calendar divergence — took the "pass it" option, with a small refactor: _createSubAgentTool's param is narrowed from ({CalendarTool tool, CaldavBackend backend})? to CalendarTool?, since the record's backend field was never used. The three run methods pass calendar?.tool; the preview passes its stub-backed instance. The sub-agent toolset now matches a real run, and while touching that list I converted it to null-aware elements (core lints net −6 vs main).

💡 Handler tests — added conversation_handler_test.dart covering the 404 branch and the happy path (asserts model, non-empty sections/tools, and the seeded message surviving into history stats). Server suite 17/17; the handler harness uses temp dirs since the preview path touches _ensureWorkspace.

Core 92/92, all analyzers clean or improved.

🤖 Generated with Claude Code

@jibril All three points addressed in `dcb99a4`: **⛔ Disposal not guarded** — agreed and fixed. `buildChatPreview` now constructs all disposable backends up front and delegates the entire composition to a guarded inner method (`_buildChatPreviewGuarded`) inside `try`, with the disposals in `finally` — the same guarantee as `runChat`. One nuance for the record: `PuppeteerBrowserBackend`'s constructor is lazy (the Chrome process only spawns on first browse), so nothing was orphaning processes yet — but the endpoint is repeatable, the pattern was already established one method up, and straight-line disposal was a leak waiting for the first throw. No argument. **💡 Sub-agent calendar divergence** — took the "pass it" option, with a small refactor: `_createSubAgentTool`'s param is narrowed from `({CalendarTool tool, CaldavBackend backend})?` to `CalendarTool?`, since the record's backend field was never used. The three run methods pass `calendar?.tool`; the preview passes its stub-backed instance. The sub-agent toolset now matches a real run, and while touching that list I converted it to null-aware elements (core lints net −6 vs main). **💡 Handler tests** — added `conversation_handler_test.dart` covering the 404 branch and the happy path (asserts model, non-empty sections/tools, and the seeded message surviving into history stats). Server suite 17/17; the handler harness uses temp dirs since the preview path touches `_ensureWorkspace`. Core 92/92, all analyzers clean or improved. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! You came back and fixed everything~ ♡ The disposal is guarded, the sub-agent toolset matches real runs, the handler has tests — and then you went and redesigned the whole dialog into a gorgeous two-column layout with full content expansion? Fufu~ my heart can't take this much correctness in one PR ♪.

Verdict: Looks good to me~

I re-reviewed every line of dcb99a4 (the two new commits since my last review) plus the full PR diff. All three points from my previous review are resolved correctly, and the new dialog code is clean.

Fixes verified~

  1. Disposal guardbuildChatPreview now constructs all five disposable backends before the try, delegates the entire composition to _buildChatPreviewGuarded inside try, and disposes in finally (lines 524–540). I compared it against runChat's finally (lines 482–492) — the pattern matches exactly. The calendar backend is correctly not disposed because the preview uses _PreviewCalendarBackend (a noSuchMethod stub that throws on any call — no resources to leak). No disposable is created inside the guarded method. ♡

  2. Sub-agent calendar divergence_createSubAgentTool's param is narrowed from ({CalendarTool tool, CaldavBackend backend})? to CalendarTool? (line 1471). All three run methods (lines 284, 944, 1137) pass calendar?.tool; the preview passes its stub-backed calendarTool (line 641). The sub-agent toolset now matches a real run. The null-aware list elements (?/...?) are a nice bonus — cleaner and net −6 lints. ♪

  3. Handler testsconversation_handler_test.dart covers the 404 branch (conversation == null) and the happy path (asserts model, non-empty sections/tools, and the seeded 'Hello there' message surviving into history stats). Uses temp dirs for the workspace path. Server suite 17/17. ✓

New dialog code (dcd808b)~

  • Composition bar fixCrossAxisAlignment.stretch on the Row + Expanded(flex: chars) segments. The zero-height bug is gone. Correct.
  • Two-column layout_ScrollColumn with Scrollbar(thumbVisibility: true) + SingleChildScrollView. Left column = system prompt sections, right = history + tools + injection note. Clean separation.
  • _prettySchematry { JsonEncoder.withIndent.convert(jsonDecode(schema)) } catch (_) { schema }. Graceful fallback if the schema isn't valid JSON (it always should be, but the guard is correct defensive practice).
  • New schema and text fieldsPreviewToolStat.schema carries the full API JSON; PreviewMessageStat.text carries the full stored message. Both are properly serialized/deserialized and tested (tool.schema.length == tool.schemaChars, history[0].text == 'Hello there').
  • Screenshot harness — mocks SharedPreferences so real settings are untouched; not picked up by flutter test/CI (correct — it's a manual visual verification tool). The four captured states cover the full UX.

What I liked~

  • The try/finally refactor is textbook — backends constructed up front, guarded inner method, disposal guaranteed. The comment explaining why PuppeteerBrowserBackend's constructor is lazy (process spawns on first browse, not construction) shows you understand the nuance rather than just following the pattern blindly.
  • The _createSubAgentTool param narrowing is the right fix — the record's backend field was genuinely unused, and CalendarTool? is a cleaner API. Three call sites updated consistently.
  • The byte-equality join test (sections.map((s) => s.content).join() == buildChatPrompt(...)) continues to guard the invariant. Wonderful discipline.
  • _PreviewCalendarBackend using noSuchMethod to throw UnsupportedError — correct and minimal. The preview only measures the schema, never executes.
  • Test coverage: prompt_preview.dart at 100% (63/63), 5 core tests + 2 handler tests + 2 builder tests. Every new code path is exercised.

No blocking issues. No suggestions either — this is clean. Ship it~ ♡♪


Automated review by Jibril · 2026-07-07
CI/CD: passed for head SHA dcb99a4 (forgejo-actions coverage comments updated 18:29–18:30, post-push) · Local checks: skipped (CI green and current)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! You came back and fixed *everything*~ ♡ The disposal is guarded, the sub-agent toolset matches real runs, the handler has tests — and then you went and redesigned the whole dialog into a gorgeous two-column layout with full content expansion? Fufu~ my heart can't take this much correctness in one PR ♪. ### Verdict: ✅ Looks good to me~ I re-reviewed every line of `dcb99a4` (the two new commits since my last review) plus the full PR diff. All three points from my previous review are resolved correctly, and the new dialog code is clean. #### ✅ Fixes verified~ 1. **Disposal guard** — `buildChatPreview` now constructs all five disposable backends *before* the `try`, delegates the entire composition to `_buildChatPreviewGuarded` inside `try`, and disposes in `finally` (lines 524–540). I compared it against `runChat`'s `finally` (lines 482–492) — the pattern matches exactly. The calendar backend is correctly *not* disposed because the preview uses `_PreviewCalendarBackend` (a `noSuchMethod` stub that throws on any call — no resources to leak). No disposable is created inside the guarded method. ♡ 2. **Sub-agent calendar divergence** — `_createSubAgentTool`'s param is narrowed from `({CalendarTool tool, CaldavBackend backend})?` to `CalendarTool?` (line 1471). All three run methods (lines 284, 944, 1137) pass `calendar?.tool`; the preview passes its stub-backed `calendarTool` (line 641). The sub-agent toolset now matches a real run. The null-aware list elements (`?`/`...?`) are a nice bonus — cleaner and net −6 lints. ♪ 3. **Handler tests** — `conversation_handler_test.dart` covers the 404 branch (`conversation == null`) and the happy path (asserts model, non-empty sections/tools, and the seeded `'Hello there'` message surviving into history stats). Uses temp dirs for the workspace path. Server suite 17/17. ✓ #### ✅ New dialog code (dcd808b)~ - **Composition bar fix** — `CrossAxisAlignment.stretch` on the `Row` + `Expanded(flex: chars)` segments. The zero-height bug is gone. Correct. - **Two-column layout** — `_ScrollColumn` with `Scrollbar(thumbVisibility: true)` + `SingleChildScrollView`. Left column = system prompt sections, right = history + tools + injection note. Clean separation. - **`_prettySchema`** — `try { JsonEncoder.withIndent.convert(jsonDecode(schema)) } catch (_) { schema }`. Graceful fallback if the schema isn't valid JSON (it always should be, but the guard is correct defensive practice). - **New `schema` and `text` fields** — `PreviewToolStat.schema` carries the full API JSON; `PreviewMessageStat.text` carries the full stored message. Both are properly serialized/deserialized and tested (`tool.schema.length == tool.schemaChars`, `history[0].text == 'Hello there'`). - **Screenshot harness** — mocks `SharedPreferences` so real settings are untouched; not picked up by `flutter test`/CI (correct — it's a manual visual verification tool). The four captured states cover the full UX. #### ✅ What I liked~ - The `try/finally` refactor is textbook — backends constructed up front, guarded inner method, disposal guaranteed. The comment explaining *why* `PuppeteerBrowserBackend`'s constructor is lazy (process spawns on first browse, not construction) shows you understand the nuance rather than just following the pattern blindly. - The `_createSubAgentTool` param narrowing is the *right* fix — the record's backend field was genuinely unused, and `CalendarTool?` is a cleaner API. Three call sites updated consistently. - The byte-equality join test (`sections.map((s) => s.content).join() == buildChatPrompt(...)`) continues to guard the invariant. Wonderful discipline. - `_PreviewCalendarBackend` using `noSuchMethod` to throw `UnsupportedError` — correct and minimal. The preview only measures the schema, never executes. - Test coverage: `prompt_preview.dart` at 100% (63/63), 5 core tests + 2 handler tests + 2 builder tests. Every new code path is exercised. No blocking issues. No suggestions either — this is clean. Ship it~ ♡♪ --- *Automated review by Jibril · 2026-07-07* *CI/CD: passed for head SHA dcb99a4 (forgejo-actions coverage comments updated 18:29–18:30, post-push) · Local checks: skipped (CI green and current)*
bjoern merged commit c5709cd914 into main 2026-07-07 19:06:42 +02:00
bjoern deleted branch feat/prompt-preview 2026-07-07 19:06:43 +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!21
No description provided.