fix: use ai to determine memory injection#53
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR updates RxCode’s memory and diff UX by (1) using an AI-backed classifier to decide which saved memories should be injected into future system prompts, and (2) improving diff presentation (full-file diffs, unified diffs with gutters) across desktop and mobile, including better MCP tool-call rendering.
Changes:
- Add AI-based “memory injection intent” determination (with caching + fallback heuristics), and wire it into prompt construction.
- Improve diff rendering and navigation: full-file diff mode for edit links, unified/hunk diff gutters, and mobile support for full-file diffs.
- UI/UX refinements: streaming/in-progress badges for briefing thread rows, persisted right-inspector width, and localized enum label plumbing (
LocalizedStringResource+displayNameText).
Reviewed changes
Copilot reviewed 64 out of 65 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| RxCodeTests/ThreadStoreThreadSummaryTests.swift | Adds tests for new thread summary title seeding/title updates. |
| RxCodeTests/MemoryIntentTests.swift | Updates tests to cover fallback injection + parsing decisions. |
| RxCodeTests/BriefingThreadRowTests.swift | Adds ViewInspector tests for the new desktop briefing row component. |
| RxCodeTests/BranchBriefingPromptContextTests.swift | Adds tests for branch briefing + background-context prompt formatting. |
| RxCodeMobileTests/MobileBriefingThreadCardTests.swift | Adds ViewInspector tests for streaming badge in mobile briefing cards. |
| RxCodeMobile/Views/ThreadChangesSheet.swift | Prefer unified full-file diff when available; fallback to hunks. |
| RxCodeMobile/Views/NewThreadSheet.swift | Switches provider/mode labels to localized-resource helpers. |
| RxCodeMobile/Views/MobileBriefingDetailView.swift | Introduces MobileBriefingThreadCard with streaming badge + accessibility label. |
| RxCode/Views/Sidebar/BriefingView.swift | Extracts BriefingThreadRow view and adds in-progress/todo badge support. |
| RxCode/Views/Settings/MCPSettingsTab.swift | Uses displayNameText for transport badge. |
| RxCode/Views/Settings/MCPServerEditorSheet.swift | Migrates scope/transport labels to localized-resource helpers. |
| RxCode/Views/Settings/ACPClientSettingsTab.swift | Changes page title type to LocalizedStringResource. |
| RxCode/Views/ProjectWindowView.swift | Plumbs showFullFileDiff into FileDiffView. |
| RxCode/Views/MainView.swift | Adds localized tab titles and plumbs showFullFileDiff into FileDiffView. |
| RxCode/Views/Inspector/ThisThreadDiffView.swift | Forces full-file diff mode when opening diffs from “This thread”. |
| RxCode/Views/Inspector/RightInspectorPanel.swift | Persists/restores right inspector width via @AppStorage. |
| RxCode/Views/Inspector/RightInspectorHeaderControls.swift | Migrates tab/mode titles to localized resources. |
| RxCode/Views/ChatToolbarComponents.swift | Migrates permission/provider labels + help strings to displayNameText. |
| RxCode/Views/ChatSettingsTab.swift | Migrates permission mode picker labels to localized resources. |
| RxCode/Services/ThreadStore.swift | Adds upsertThreadSummaryTitle to persist titled thread summaries early. |
| RxCode/Services/OpenAISummarizationService.swift | Adds OpenAI-compatible endpoint method + prompt for injection-intent classification. |
| RxCode/Services/IDEServer/AppState+IDEToolHandling.swift | Removes explicit “memory intent” rejection for memory_add. |
| RxCode/Services/FoundationModelSummarizationService.swift | Adds injection-intent classification via Apple Foundation Model. |
| RxCode/Services/CodexAppServer+Turn.swift | Improves tool completion output/error parsing for Codex events. |
| RxCode/Services/CodexAppServer+Summaries.swift | Adds Codex injection-intent classification helper. |
| RxCode/Services/CodexAppServer+Parsing.swift | Improves MCP tool name normalization + tool output/error detection helpers. |
| RxCode/Services/ClaudeService+Summaries.swift | Adds Claude injection-intent classification helper. |
| RxCode/App/RxCodeApp.swift | Migrates theme/provider labels to localized-resource helpers. |
| RxCode/App/AppState+SessionLifecycle.swift | Stores thread summary title after auto-title generation. |
| RxCode/App/AppState+Model.swift | Migrates agent provider section titles to displayNameText. |
| RxCode/App/AppState+MobileSnapshots.swift | Adds optional full-file diff generation for mobile thread changes payloads. |
| RxCode/App/AppState+MemoryIntent.swift | Keeps only fallback heuristics for injection; removes explicit-intent logic. |
| RxCode/App/AppState+CrossProject.swift | Adds branch briefing background context + new shared prompt context wrapper. |
| RxCode/App/AppState+Agents.swift | Adds AI-backed memory injection decision + caching; removes explicit-intent gate for auto-extraction. |
| RxCode/App/AppState.swift | Adds displayNameText for summarization providers + injection intent cache storage. |
| RxCode.xcodeproj/xcshareddata/xcschemes/RxCodeMobile.xcscheme | Adds mobile unit test plan to scheme. |
| RxCode.xcodeproj/project.pbxproj | Registers new tests/resources; adds ViewInspector to mobile tests. |
| Packages/Tests/RxCodeSyncTests/PayloadTests.swift | Verifies optional fullFileDiff encoding/decoding in sync payloads. |
| Packages/Tests/RxCodeCoreTests/SyntaxHighlighterTests.swift | Adds regression test for unterminated escaped strings. |
| Packages/Tests/RxCodeCoreTests/RightInspectorPanelLayoutTests.swift | Tests persisted/restored inspector width behavior. |
| Packages/Tests/RxCodeCoreTests/MCPToolCallTests.swift | Tests MCP categorization and empty-result retention. |
| Packages/Tests/RxCodeCoreTests/LocalizedEnumLabelTests.swift | Tests localized resource/text helper consistency for enums. |
| Packages/Tests/RxCodeChatKitTests/ToolResultViewDiffTests.swift | Tests edit diff links enable full-file diff mode. |
| Packages/Tests/RxCodeChatKitTests/MCPToolResultViewTests.swift | Tests MCP tool detail sheet rendering/interaction. |
| Packages/Tests/RxCodeChatKitTests/FileDiffViewTests.swift | Tests full-file diff reconstruction/orphan handling. |
| Packages/Tests/RxCodeChatKitTests/ChangeDiffViewTests.swift | Tests line-number gutter rendering for unified/hunk diffs. |
| Packages/Sources/RxCodeSync/Protocol/Payload+Sessions.swift | Adds fullFileDiff to SyncFileEdit payload model. |
| Packages/Sources/RxCodeCore/WindowState.swift | Adds right-inspector width storage key + layout helpers; adds localized titles for inspector enums. |
| Packages/Sources/RxCodeCore/Utilities/SyntaxHighlighter.swift | Refactors string scanning to avoid escape-edge-case issues. |
| Packages/Sources/RxCodeCore/Theme/AppTheme.swift | Changes theme display name to localized resource + adds displayNameText. |
| Packages/Sources/RxCodeCore/Models/ThreadSummaryRecord.swift | Adds title seeding + title-update helpers for thread summaries. |
| Packages/Sources/RxCodeCore/Models/PreviewFile.swift | Adds showFullFileDiff flag to preview file model. |
| Packages/Sources/RxCodeCore/Models/PermissionRequest.swift | Makes ToolCategory equatable; improves MCP detection. |
| Packages/Sources/RxCodeCore/Models/PermissionMode.swift | Migrates display name to localized resource + adds displayNameText. |
| Packages/Sources/RxCodeCore/Models/MCPServer.swift | Migrates MCP labels/subtitles to localized resources + adds text helpers. |
| Packages/Sources/RxCodeCore/Models/ChatMessage.swift | Keeps empty results for MCP tool calls. |
| Packages/Sources/RxCodeCore/Models/AgentModel.swift | Migrates provider display name to localized resource + adds displayNameText. |
| Packages/Sources/RxCodeChatKit/ToolResultView.swift | Adds MCP tool detail sheet; updates diff links to open full-file diff mode. |
| Packages/Sources/RxCodeChatKit/StatusLineView.swift | Uses displayNameText for provider help string. |
| Packages/Sources/RxCodeChatKit/SlashCommandManagerView.swift | Fixes provider badge rendering with localized resources. |
| Packages/Sources/RxCodeChatKit/FileDiffView.swift | Adds full-file diff building mode and reconstruction logic. |
| Packages/Sources/RxCodeChatKit/ChangeDiffView.swift | Adds line-number gutter rendering for diffs. |
| MobileUnitTestPlan.xctestplan | Adds a dedicated mobile unit test plan. |
Comments suppressed due to low confidence (1)
RxCode/App/AppState+Agents.swift:223
scheduleMemoryExtractionno longer requires an explicit user "remember" intent, so whenmemoryAutoCreateEnabledis on it will attempt extraction after every completed turn. This can quickly accumulate low-signal memories, increase embedding work, and capture transient/task-specific data the user didn’t intend to save. Consider reinstating an intent gate (e.g., user-requested remember) or adding a lightweight classifier/threshold before callingextractAndStoreMemories.
func scheduleMemoryExtraction(
sessionId: String,
projectId: UUID,
messages: [ChatMessage]
) {
guard memoryEnabled, memoryAutoCreateEnabled else { return }
let userMessage = lastUserMessageText(in: messages)
let finalResponse = lastAssistantResponseText(in: messages)
guard !userMessage.isEmpty, !finalResponse.isEmpty else { return }
let sourceMessageId = messages.last(where: { $0.role == .user && !$0.isError })?.id
let summary = allSessionSummaries.first(where: { $0.id == sessionId })
?? summaryFor(sessionId: sessionId, projectId: projectId)
Task { [weak self] in
guard let self else { return }
await self.extractAndStoreMemories(
sessionId: sessionId,
projectId: projectId,
sourceMessageId: sourceMessageId,
userMessage: userMessage,
finalResponse: finalResponse,
summary: summary
)
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -298,7 +320,10 @@ extension AppState { | |||
| projectPath: cwd, | |||
| bridgeCommand: bridge | |||
| ) | |||
| resolvedPrompt = memoryContextPromptPrefix(for: resolvedMemoryContext, prompt: resolvedPrompt) | |||
| resolvedPrompt = Self.promptWithBackgroundContext( | |||
| [branchBriefingContext, resolvedMemoryContext], | |||
| prompt: resolvedPrompt | |||
| ) | |||
| if let skillContext = await marketplace.promptContext(for: .acp) { | |||
| resolvedPrompt = "\(skillContext)\n\nUser request:\n\(resolvedPrompt)" | |||
| } | |||
| func systemPromptMemoryItems(projectId: UUID?, provider: AgentProvider, model: String?) async -> [MemoryItem] { | ||
| let items = await memoryService.allMemories() | ||
| return items.filter { item in | ||
| var injected: [MemoryItem] = [] | ||
| for item in items { | ||
| if let memoryProjectId = item.projectId { | ||
| guard memoryProjectId == projectId else { return false } | ||
| guard memoryProjectId == projectId else { continue } | ||
| } | ||
| return Self.shouldInjectMemoryIntoSystemPrompt(item) | ||
| if await shouldInjectMemoryIntoSystemPrompt(item, provider: provider, model: model) { | ||
| injected.append(item) | ||
| } | ||
| } | ||
| return injected | ||
| } | ||
|
|
||
| func shouldInjectMemoryIntoSystemPrompt(_ item: MemoryItem, provider: AgentProvider, model: String?) async -> Bool { | ||
| let cacheKey = memoryInjectionIntentCacheKey(for: item, provider: provider, model: model) | ||
| if let cached = memoryInjectionIntentCache[cacheKey] { | ||
| return cached | ||
| } | ||
|
|
||
| let rawDecision = await generateMemoryInjectionIntent( | ||
| content: item.content, | ||
| kind: item.kind, | ||
| scope: item.scope, | ||
| provider: provider, | ||
| model: model | ||
| ) | ||
| let decision = Self.parseMemoryInjectionDecision(rawDecision) | ||
| ?? Self.fallbackShouldInjectMemoryIntoSystemPrompt(item) | ||
| memoryInjectionIntentCache[cacheKey] = decision | ||
| return decision | ||
| } |
| private nonisolated static func computeUnifiedDiffLines( | ||
| old: [String], | ||
| new: [String] | ||
| ) -> [DiffLine] { | ||
| let diff = new.difference(from: old) | ||
| var removalIndices = Set<Int>() | ||
| var insertionElements: [Int: String] = [:] | ||
| for change in diff { | ||
| switch change { | ||
| case .remove(let offset, _, _): | ||
| removalIndices.insert(offset) | ||
| case .insert(let offset, let element, _): | ||
| insertionElements[offset] = element | ||
| } | ||
| } | ||
|
|
||
| var lines: [DiffLine] = [] | ||
| var oldIdx = 0 | ||
| var newIdx = 0 | ||
|
|
||
| while oldIdx < old.count || newIdx < new.count { | ||
| if oldIdx < old.count, removalIndices.contains(oldIdx) { | ||
| lines.append(DiffLine(text: "-\(old[oldIdx])", kind: .removed)) | ||
| oldIdx += 1 | ||
| } else if newIdx < new.count, let added = insertionElements[newIdx] { | ||
| lines.append(DiffLine(text: "+\(added)", kind: .added)) | ||
| newIdx += 1 | ||
| } else if oldIdx < old.count, newIdx < new.count { | ||
| lines.append(DiffLine(text: " \(new[newIdx])", kind: .context)) | ||
| oldIdx += 1 | ||
| newIdx += 1 | ||
| } else if oldIdx < old.count { | ||
| lines.append(DiffLine(text: " \(old[oldIdx])", kind: .context)) | ||
| oldIdx += 1 | ||
| } else if newIdx < new.count { | ||
| lines.append(DiffLine(text: " \(new[newIdx])", kind: .context)) | ||
| newIdx += 1 | ||
| } | ||
| } |
| .background { | ||
| GeometryReader { proxy in | ||
| Color.clear | ||
| .preference(key: RightInspectorPanelWidthPreferenceKey.self, value: proxy.size.width) | ||
| } | ||
| } | ||
| .onPreferenceChange(RightInspectorPanelWidthPreferenceKey.self) { width in | ||
| if let width = RightInspectorPanelLayout.persistedWidth(from: width, isVisible: showRightSidebar) { | ||
| rightInspectorWidth = width | ||
| } | ||
| } |
|
🎉 This PR is included in version 1.11.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
No description provided.