feat: compact mode for chat message loading with lazy-load tool content#919
Merged
simple-agent-manager[bot] merged 9 commits intomainfrom May 6, 2026
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip tool_metadata.content on the read path (compact mode) to reduce RPC payload size by 80-90% for tool-heavy sessions. Content is lazy-loaded on demand when users expand individual tool call cards. Backend: - Add stripToolMetadataContent() and parseChatMessageRowCompact() in row-schemas.ts - Add compact parameter to getMessages() (default false) - Add getMessageToolContent() for single-message content fetch - Update ProjectData DO and service layer with compact + tool-content methods - Add GET /:sessionId/messages/:messageId/tool-content endpoint - Session detail route uses compact=true by default - Summarize route explicitly uses compact=false Frontend: - ToolCallItem gains contentSize?, contentLoaded?, messageId? fields - chatMessagesToConversationItems() handles compact metadata - ToolCallCard lazy-loads content on expand with loading state - getMessageToolContent() API client function - AcpConversationItemView and ProjectMessageView wire onLoadToolContent Configuration: - DEFAULT_CHAT_COMPACT_MODE constant (default: true) - CHAT_COMPACT_MODE_DEFAULT env var override Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make getMessageToolContent async on DO class for RPC consistency - Fix parseChatMessageRowCompact to use `!== null` instead of truthy check - Hoist TextEncoder to module scope to avoid repeated allocation - Add 6 unit tests for getMessageToolContent covering all code paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add proper error state for failed lazy-load (loadFailed flag instead of fake content), ARIA labels for loading/error states, and prevent re-fetching after failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DO proxy service layer — splitting creates import complexity without meaningful benefit per .claude/rules/18-file-size-limits.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
tool_metadatastores full tool call content (file contents, command output, search results, diffs).compact=true(default),getMessages()stripstool_metadata.contentarrays and replaces them with acontentSizebyte hint. A newgetMessageToolContent()endpoint fetches individual message content when users expand a tool call card.Key Changes
Backend (
apps/api/):stripToolMetadataContent()pure function — strips content array, addscontentSize(UTF-8 byte count)parseChatMessageRowCompact()— compact row parser using the stripping functiongetMessages()gainscompactparameter (default:truefor chat routes,falsefor MCP)getMessageToolContent()— new DO method + REST endpoint for lazy-loading individual message contentCHAT_COMPACT_MODE_DEFAULTenv var (configurable, default:true)Frontend (
apps/web/,packages/acp-client/):ToolCallCardsupports lazy loading — shows "Load content" hint, fetches on expand, caches in stategetMessageToolContent()API client functionAcpConversationItemViewwiresonLoadContentcallbackValidation
pnpm lintpnpm typecheckpnpm test(6452 tests passing)pnpm buildStaging Verification (REQUIRED for all code changes — merge-blocking)
Deploy Stagingworkflow triggered and passedapp.sammy.partyusing test credentialsStaging Verification Evidence
POST /api/auth/token-loginwith smoke test token — 200 OKapp.sammy.partyloads correctly with project cardsGET /api/projects/:id/sessions/:sid/messages/:mid/tool-contentreturns 404 for non-existent message (confirming route is registered and auth works)UI Compliance Checklist (Required for UI changes)
packages/acp-clientEnd-to-End Verification (Required for multi-component changes)
Data Flow Trace
Compact mode read path:
apps/web/src/components/project-message-view/index.tsx:useEffectfetches messagesapps/api/src/routes/chat.ts:getSessionMessages()passescompact=trueapps/api/src/services/project-data.ts:getMessages()forwards compact flag to DOapps/api/src/durable-objects/project-data/index.ts:getMessages()delegates to messages moduleapps/api/src/durable-objects/project-data/messages.ts:getMessages()usesparseChatMessageRowCompact()when compact=trueapps/api/src/durable-objects/project-data/row-schemas.ts:parseChatMessageRowCompact()callsstripToolMetadataContent()contentSizeinstead ofcontentarrayLazy-load content path:
packages/acp-client/src/components/ToolCallCard.tsx:handleToggle()callsonLoadContent(messageId)apps/web/src/components/project-message-view/index.tsxcallsgetMessageToolContent()apps/web/src/lib/api/sessions.ts:getMessageToolContent()→GET /api/projects/:id/sessions/:sid/messages/:mid/tool-contentapps/api/src/routes/chat.ts→ service layer → DO methodapps/api/src/durable-objects/project-data/messages.ts:getMessageToolContent()queries single row, extractstool_metadata.contentUntested Gaps
Post-Mortem
N/A: not a bug fix
Specialist Review Evidence (Required for agent-authored PRs)
needs-human-reviewlabel added and merge deferred to human — all completedExceptions (If any)
Agent Preflight (Required)
Classification
External References
N/A: all changes use existing Cloudflare Workers patterns and project conventions.
Codebase Impact Analysis
apps/api/— new compact mode in DO messages, new REST endpoint, service layer updatesapps/web/— lazy-load callback wiring, API client functionpackages/acp-client/— ToolCallCard lazy-load UI with error/loading statespackages/shared/—DEFAULT_CHAT_COMPACT_MODEconstantDocumentation & Specs
CLAUDE.mdupdated with compact-mode-lazy-load-tool-content entryapps/api/.env.exampleupdated withCHAT_COMPACT_MODE_DEFAULTdocumentationConstitution & Risk Check
CHAT_COMPACT_MODE_DEFAULTis configurable via env var with default in shared constantscompact=falseto preserve full content for agent tool calls