feat(chat): add manual conversation compaction and automatic-only enablement #339

Merged
rwachters merged 18 commits from feat/conversation-compaction into master 2026-10-10 12:32:06 +00:00
rwachters commented 2026-10-10 12:30:49 +00:00 (Migrated from github.com)

Summary

Adds a user-triggered "Compact Conversation" action to the chat top bar, served by a dedicated per-session WebSocket, and splits compaction enablement so automatic (threshold-triggered) compaction and user-requested compaction are independent: the per-user preference and the per-preset flag are both renamed to automaticCompactionEnabled, ANDed, default to true, and never restrict a manual request.

The branch carries 18 commits: the 9 feature commits and, after them, a 9-commit behavior-preserving refactoring series that decomposes the largest files the feature touched (tool catalog, chat view model, send-message use case, message action row, Koin modules, tests) so the feature can be reviewed against a smaller surface in future work. The refactors change no wire format, no persistence schema, no dependency-injection graph semantics and no user-visible behavior.

Changes

  • Added the compaction event vocabulary (CompactionEvent, skip reasons, the per-session compaction resource) shared by client and server.
  • Added the server-side forced compaction path, including the forced-use rule that injects an eligible retained summary even below the threshold, and the verified single-chunk insert it reuses.
  • Added the WS /api/v1/sessions/{sessionId}/compaction endpoint, where the connection is the request and a client close aborts it; every path ends with the terminal StreamCompleted marker.
  • Added the client transport, the CompactConversationUseCase, the COMPACTING turn state that reuses the composer's stop button, and the top bar action, which is now always available while a session is idle.
  • Renamed the per-user preference and per-preset flag to automaticCompactionEnabled (wire models, built-in tool schemas, persistence), with the additive V36__rename_model_preset_automatic_compaction.sql migration.
  • Split the per-turn compaction resolution into configured / not-configured / rejected, moved the automatic flag into the effective settings, and renamed the auxiliary resolver and preference services to unambiguous names.
  • Covered the behavior with server compaction, preparation, route, websocket and migration tests, client transport tests and settings tests.

Refactoring

These nine commits are ordered after the feature commits and are behavior-preserving: no wire, persistence or runtime behavior changes, only moved code, extracted collaborators and split test suites.

  • refactor(common): split the built-in tool catalog by family — the single tool catalog becomes per-family spec files (agent role, instruction, model preset, model, project, session) plus shared input-schema builders, leaving the catalog as the aggregating entry point.
  • test(server): split session route tests by subject — the 1,760-line session route test becomes CRUD, session-state, agent-role and websocket suites on a shared base.
  • refactor(server): extract compaction auxiliary summarizer — the auxiliary summarization call and its verification move out of the compaction service into a dedicated summarizer.
  • test(server): split compaction service tests by path — the compaction service tests split into an automatic path and a forced path sharing one fixture.
  • refactor(app): extract chat turn and search logic — ChatTurnController and SearchNavigationObserver are extracted from ChatViewModel, which keeps the public surface used by the screens.
  • refactor(app): split send message use case — the 851-line SendMessageUseCase becomes a 390-line orchestrator over a tool-approval event emitter and separate streaming and non-streaming turn collectors.
  • refactor(app): decompose the message action row — the 618-line MessageActionRow becomes a 151-line row over action buttons, a more-actions menu and branch-navigation controls.
  • refactor(app): split the app koin module by feature — the app dependency graph splits into core, API, chat and settings modules.
  • refactor(server): split the service koin module — the server service graph splits into chat, tool and MCP modules with the previous module kept as the aggregating entry point.

Implementation notes

  • Automatic disabled does not disable the forced-use rule: as long as the stored configuration is usable, an eligible and thread-applicable summary is still injected and a manual request still runs.
  • The manual path fails with a typed server error when no usable configuration exists; the client settles on the terminal marker regardless of the outcome.
  • The rename breaks two contracts for older peers: JSON payloads sending compactionEnabled fall back to the default, and the preset column is renamed by V36. Both should be mentioned in the release notes.
  • The agent-role compaction flag and the chat-state gate that briefly existed during development were removed before the change was finished, so they do not appear in this diff.
  • Scope: 18 commits, 154 files changed against master; the refactoring series accounts for roughly half of that churn while adding no behavior.

Validation

The change set is covered by the following suites:

  • ./gradlew :server:testClasses :server:test — compaction services, turn preparation, session and preset routes, websocket handler and the migration tests.
  • ./gradlew :app:desktopTestClasses :app:desktopTest — client socket transport, chat state and turn guard, top bar/composer UI and settings tests.
  • ./gradlew :common:desktopTest — compaction event serialization and tool-schema tests.

Risks and follow-up

  • Known and explicitly accepted for pre-release: stored conversation_compaction rows are not migrated or legacy-decoded, so a previously disarmed row is re-armed under the new default, and a legacy armed-but-incomplete row can still reject a turn.
  • The rename of compactionEnabled is not backwards compatible on the wire; a preference-row migration plus a legacy-decode path is the suggested follow-up.
  • The refactoring commits touch files the feature commits also touched, so the same suites must stay green; review them commit by commit for move-only diffs, and expect the feature commits alone to contain all the intentional behavior changes.
## Summary Adds a user-triggered "Compact Conversation" action to the chat top bar, served by a dedicated per-session WebSocket, and splits compaction enablement so automatic (threshold-triggered) compaction and user-requested compaction are independent: the per-user preference and the per-preset flag are both renamed to `automaticCompactionEnabled`, ANDed, default to `true`, and never restrict a manual request. The branch carries 18 commits: the 9 feature commits and, after them, a 9-commit behavior-preserving refactoring series that decomposes the largest files the feature touched (tool catalog, chat view model, send-message use case, message action row, Koin modules, tests) so the feature can be reviewed against a smaller surface in future work. The refactors change no wire format, no persistence schema, no dependency-injection graph semantics and no user-visible behavior. ## Changes - Added the compaction event vocabulary (`CompactionEvent`, skip reasons, the per-session `compaction` resource) shared by client and server. - Added the server-side forced compaction path, including the forced-use rule that injects an eligible retained summary even below the threshold, and the verified single-chunk insert it reuses. - Added the `WS /api/v1/sessions/{sessionId}/compaction` endpoint, where the connection is the request and a client close aborts it; every path ends with the terminal `StreamCompleted` marker. - Added the client transport, the `CompactConversationUseCase`, the `COMPACTING` turn state that reuses the composer's stop button, and the top bar action, which is now always available while a session is idle. - Renamed the per-user preference and per-preset flag to `automaticCompactionEnabled` (wire models, built-in tool schemas, persistence), with the additive `V36__rename_model_preset_automatic_compaction.sql` migration. - Split the per-turn compaction resolution into configured / not-configured / rejected, moved the automatic flag into the effective settings, and renamed the auxiliary resolver and preference services to unambiguous names. - Covered the behavior with server compaction, preparation, route, websocket and migration tests, client transport tests and settings tests. ## Refactoring These nine commits are ordered after the feature commits and are behavior-preserving: no wire, persistence or runtime behavior changes, only moved code, extracted collaborators and split test suites. - `refactor(common): split the built-in tool catalog by family` — the single tool catalog becomes per-family spec files (agent role, instruction, model preset, model, project, session) plus shared input-schema builders, leaving the catalog as the aggregating entry point. - `test(server): split session route tests by subject` — the 1,760-line session route test becomes CRUD, session-state, agent-role and websocket suites on a shared base. - `refactor(server): extract compaction auxiliary summarizer` — the auxiliary summarization call and its verification move out of the compaction service into a dedicated summarizer. - `test(server): split compaction service tests by path` — the compaction service tests split into an automatic path and a forced path sharing one fixture. - `refactor(app): extract chat turn and search logic` — `ChatTurnController` and `SearchNavigationObserver` are extracted from `ChatViewModel`, which keeps the public surface used by the screens. - `refactor(app): split send message use case` — the 851-line `SendMessageUseCase` becomes a 390-line orchestrator over a tool-approval event emitter and separate streaming and non-streaming turn collectors. - `refactor(app): decompose the message action row` — the 618-line `MessageActionRow` becomes a 151-line row over action buttons, a more-actions menu and branch-navigation controls. - `refactor(app): split the app koin module by feature` — the app dependency graph splits into core, API, chat and settings modules. - `refactor(server): split the service koin module` — the server service graph splits into chat, tool and MCP modules with the previous module kept as the aggregating entry point. ## Implementation notes - Automatic disabled does not disable the forced-use rule: as long as the stored configuration is usable, an eligible and thread-applicable summary is still injected and a manual request still runs. - The manual path fails with a typed server error when no usable configuration exists; the client settles on the terminal marker regardless of the outcome. - The rename breaks two contracts for older peers: JSON payloads sending `compactionEnabled` fall back to the default, and the preset column is renamed by `V36`. Both should be mentioned in the release notes. - The agent-role compaction flag and the chat-state gate that briefly existed during development were removed before the change was finished, so they do not appear in this diff. - Scope: 18 commits, 154 files changed against `master`; the refactoring series accounts for roughly half of that churn while adding no behavior. ## Validation The change set is covered by the following suites: - `./gradlew :server:testClasses :server:test` — compaction services, turn preparation, session and preset routes, websocket handler and the migration tests. - `./gradlew :app:desktopTestClasses :app:desktopTest` — client socket transport, chat state and turn guard, top bar/composer UI and settings tests. - `./gradlew :common:desktopTest` — compaction event serialization and tool-schema tests. ## Risks and follow-up - Known and explicitly accepted for pre-release: stored `conversation_compaction` rows are not migrated or legacy-decoded, so a previously disarmed row is re-armed under the new default, and a legacy armed-but-incomplete row can still reject a turn. - The rename of `compactionEnabled` is not backwards compatible on the wire; a preference-row migration plus a legacy-decode path is the suggested follow-up. - The refactoring commits touch files the feature commits also touched, so the same suites must stay green; review them commit by commit for move-only diffs, and expect the feature commits alone to contain all the intentional behavior changes.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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
Torvian.eu/chatbot!339
No description provided.