Skip to content

Conversation

@ethanndickson
Copy link
Member

Adds workspace-level settings for auto-compaction with configurable threshold (50-90%).

Generated with mux

Previously, compact continue messages were handled by a frontend hook
(useAutoCompactContinue) that watched for completed compactions and
then sent the continue message. This introduced complexity, race
conditions, and required tracking processed message IDs.

Now leverages the existing message queue system:
- Backend queues continue message when compaction starts
- Queue auto-sends when compaction stream ends (existing behavior)
- Continue message shown in queue UI during compaction
- Proper cleanup on all error paths
- Strip editMessageId to prevent truncation failures after compaction

Net reduction of 134 lines. Simpler, more reliable, better UX.
Move history compaction handling from WorkspaceStore to agentSession to centralize server-side operations.

Backend changes:
- Added handleCompactionCompletion() to detect and handle compaction stream-end events
- Added handleCompactionAbort() to handle Ctrl+A (accept early with [truncated]) and Ctrl+C (cancel) flows
- Added performCompaction() to atomically replace chat history with summary message
- Added processedCompactionRequestIds Set to dedupe repeated compaction events
- Implemented abandonPartial flag flow from IPC through to StreamAbortEvent
- Extract truncated message content from history instead of partialService
- Wrap compaction handlers in try-catch to ensure stream events always forwarded
- Made performCompaction return Result type for proper error handling

Frontend changes:
- Removed compaction handlers from WorkspaceStore
- Simplified cancelCompaction() to just call interruptStream with abandonPartial flag
- Fixed Ctrl+A keybind to pass abandonPartial: false for early accept

Shared changes:
- Updated StreamAbortEvent to include abandonPartial flag
- historyService.clearHistory() now returns deleted sequence numbers
- Created calculateCumulativeUsage() utility to extract and sum usage from messages
- aiService respects abandonPartial flag - skips committing when true
Move compaction handling from agentSession to dedicated CompactionHandler class.

Changes:
- Created src/node/services/compactionHandler.ts with CompactionHandler class
- Extracted handleAbort, handleCompletion, and performCompaction methods
- Updated agentSession.ts to delegate to CompactionHandler
- Moved frontend compaction handler to browser utils
- Reduced agentSession.ts from 764 to 589 lines

Benefits:
- Better separation of concerns
- Easier to test compaction logic independently
- Cleaner session orchestration code

_Generated with `mux`_
- Show countdown starting at 60% usage (10% before threshold)
- Display 'Context left until Auto-Compact: X% remaining' message
- Switch to urgent message at 70% threshold
- Centralize threshold logic in shouldAutoCompact() utility
- Pass image parts through compaction continue messages
- Structure for future threshold configurability
Previous implementation only checked last message tokens against context window,
effectively disabling auto-compaction unless a single response exceeded 70%.

Now uses WorkspaceUsageState.totalTokens for cumulative conversation tracking.

- Remove lastUsage extraction and manual token calculation
- Use pre-calculated totalTokens from WorkspaceStore
- Simplifies code by ~20 lines
- Auto-compaction now correctly triggers at 70% cumulative usage
Previous calculation summed all usageHistory entries, but each entry
already contains cumulative prompt tokens (full context at that turn).
This caused massive over-counting in multi-turn conversations.

Example:
- Turn 1: 1,000 tokens
- Turn 2: 2,500 tokens (includes turn 1)
- Turn 3: 4,200 tokens (includes turns 1-2)

Before: 1,000 + 2,500 + 4,200 = 7,700 tokens (183% inflated)
After: 4,200 tokens (correct - just use last entry)

This fix ensures auto-compaction triggers at actual 70% usage instead
of triggering far earlier due to double/triple counting.
- Add configurable auto-compaction threshold (50-90%) per workspace
- Extract threshold constants to ui.ts (DRY)
- Create reusable useClampedNumberInput hook for numeric inputs
- Add settings to right sidebar with checkbox and percentage input
- Wire settings through to shouldAutoCompact check
- Use existing HelpIndicator pattern for tooltips
@ethanndickson
Copy link
Member Author

Closing to retarget base branch

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +74 to +78
const partialSummary = lastMessage.parts
.filter((part): part is { type: "text"; text: string } => part.type === "text")
.map((part) => part.text)
.join("");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve partial summary when accepting compaction early

In the Ctrl+A “accept early” path the compaction handler builds the truncated summary solely from the last assistant message already stored in chat history (lastMessage.parts), but aborted streams never write the streamed text to history—they only append an empty placeholder at start (see aiService.ts lines 695‑705) and on abort cancelStreamSafely just flushes a partial file and emits stream-abort without updating history (streamManager.ts lines 170‑207, 420‑437). As a result partialSummary is usually empty, so compaction replaces the conversation with a summary containing only [truncated], discarding the partial summary the user chose to keep. To keep the accepted text, load the partial contents (e.g., from the partial write) or persist them before running performCompaction.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant