feat(memory): show agent memory in chat - #1868
Conversation
📝 WalkthroughWalkthroughThis PR adds agent-memory archive and batch-get operations, extends the memory.updated event with contextual metadata (memoryId, sessionId, createdIds), and introduces renderer-side memory activity features: a Pinia store, a per-turn memory review dialog, a memory update chip UI, toolbar integration, and full i18n translations across all locales. ChangesMemory context, archive/getByIds, and chip/turn UI
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant MessageItemAssistant
participant MemoryActivityStore
participant MemoryClient
participant MemoryTurnDialog
User->>MessageItemAssistant: click "memory" toolbar action
MessageItemAssistant->>MemoryActivityStore: openTurnMemories(assistantMessageId)
MemoryActivityStore->>MemoryActivityStore: locate preceding user message
MemoryActivityStore->>MemoryClient: listViewManifests / getByIds
MemoryClient-->>MemoryActivityStore: manifest + memory details
MemoryActivityStore-->>MemoryTurnDialog: selectedTurn (ready/stale/error)
User->>MemoryTurnDialog: click "forget"
MemoryTurnDialog->>MemoryActivityStore: forget(memoryId)
MemoryActivityStore->>MemoryClient: archive(agentId, memoryId)
MemoryClient-->>MemoryActivityStore: ok
MemoryActivityStore-->>MemoryTurnDialog: updated status
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/renderer/src/components/chat/MemoryTurnDialog.vue (1)
70-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRaw memory
kind/categoryvalues rendered without i18n.
detail.memory.kindanddetail.memory.categoryare interpolated directly into the badges, unlikedetail.memory.status === 'archived'which correctly goes throught('chat.memory.status.archived')(Line 83). For the boundedkindenum (semantic/episodic), this leaves raw English/technical values visible to non-English users.🌐 Proposed fix to translate `kind`
- <Badge variant="outline" class="text-[10px]">{{ detail.memory.kind }}</Badge> + <Badge variant="outline" class="text-[10px]">{{ t(`chat.memory.kind.${detail.memory.kind}`) }}</Badge>As per coding guidelines, "All user-facing strings must use vue-i18n keys in
src/renderer/src/i18n".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/src/components/chat/MemoryTurnDialog.vue` around lines 70 - 86, The memory badges in MemoryTurnDialog currently render raw kind/category values instead of localized text. Update the detail.memory.kind and detail.memory.category badge content to use vue-i18n translations, following the same pattern as the archived status badge via t(...). Add or reuse the appropriate i18n keys in src/renderer/src/i18n for the bounded memory kind enum and any category labels so all user-facing text stays localized.Source: Coding guidelines
src/renderer/src/components/chat/MemoryUpdateChip.vue (1)
44-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
kindbadge is not localized.The
status: archivedbadge is translated (t('chat.memory.status.archived')), but thekindbadge (line 46) renders the raw enum value (semantic/episodic) directly. This is inconsistent and will show untranslated English-derived tokens in the other 19 non-English locales this PR adds.♻️ Proposed fix
- <Badge variant="outline" class="text-[10px]">{{ item.memory.kind }}</Badge> + <Badge variant="outline" class="text-[10px]"> + {{ t(`chat.memory.kind.${item.memory.kind}`) }} + </Badge>As per coding guidelines, "All user-facing strings must use vue-i18n keys in
src/renderer/src/i18n."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/src/components/chat/MemoryUpdateChip.vue` around lines 44 - 58, The `kind` badge in `MemoryUpdateChip.vue` is rendering the raw `item.memory.kind` enum instead of a localized label, unlike the archived status badge. Update the badge to use a vue-i18n key from `src/renderer/src/i18n` (for example by mapping the memory kind values in this component or a helper) and keep the `Badge` rendering logic in `MemoryUpdateChip.vue` using translated text for all user-facing strings.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/routes/index.ts`:
- Around line 648-651: The selected count in the route mapping can drift from
the resolved IDs because it is computed from raw record.selected while
deriveSelectedMemoryIds() dedupes and filters invalid entries. Update the
mapping in index.ts so selectedCount is derived from the same normalized
selectedIds value used for selectedIds, keeping the turn dialog count consistent
with the actual resolved memories.
In `@src/renderer/src/stores/ui/memoryActivity.ts`:
- Around line 440-446: The selected count shown by the turn dialog can diverge
from the actual rendered details because `getMemoryTurns` in `memoryActivity.ts`
deduplicates and truncates `manifest.selectedIds` before building `details`,
while `MemoryTurnDialog.vue` still displays `turn.manifest.selectedCount`.
Update the displayed stat to use the effective deduped/truncated selection count
derived from `selectedIds` (or store that count alongside `details`), and handle
cases where `uniqueIds(...)` or `MEMORY_DETAILS_BATCH_LIMIT` changes the visible
list.
---
Nitpick comments:
In `@src/renderer/src/components/chat/MemoryTurnDialog.vue`:
- Around line 70-86: The memory badges in MemoryTurnDialog currently render raw
kind/category values instead of localized text. Update the detail.memory.kind
and detail.memory.category badge content to use vue-i18n translations, following
the same pattern as the archived status badge via t(...). Add or reuse the
appropriate i18n keys in src/renderer/src/i18n for the bounded memory kind enum
and any category labels so all user-facing text stays localized.
In `@src/renderer/src/components/chat/MemoryUpdateChip.vue`:
- Around line 44-58: The `kind` badge in `MemoryUpdateChip.vue` is rendering the
raw `item.memory.kind` enum instead of a localized label, unlike the archived
status badge. Update the badge to use a vue-i18n key from
`src/renderer/src/i18n` (for example by mapping the memory kind values in this
component or a helper) and keep the `Badge` rendering logic in
`MemoryUpdateChip.vue` using translated text for all user-facing strings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bd6c1ca4-6406-45db-a960-3487567d1cd8
📒 Files selected for processing (47)
src/main/presenter/index.tssrc/main/presenter/memoryPresenter/index.tssrc/main/presenter/memoryPresenter/types.tssrc/main/presenter/sqlitePresenter/tables/agentMemory.tssrc/main/routes/index.tssrc/renderer/api/MemoryClient.tssrc/renderer/src/components/chat/MemoryTurnDialog.vuesrc/renderer/src/components/chat/MemoryUpdateChip.vuesrc/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/components/message/MessageToolbar.vuesrc/renderer/src/i18n/da-DK/chat.jsonsrc/renderer/src/i18n/de-DE/chat.jsonsrc/renderer/src/i18n/en-US/chat.jsonsrc/renderer/src/i18n/es-ES/chat.jsonsrc/renderer/src/i18n/fa-IR/chat.jsonsrc/renderer/src/i18n/fr-FR/chat.jsonsrc/renderer/src/i18n/he-IL/chat.jsonsrc/renderer/src/i18n/id-ID/chat.jsonsrc/renderer/src/i18n/it-IT/chat.jsonsrc/renderer/src/i18n/ja-JP/chat.jsonsrc/renderer/src/i18n/ko-KR/chat.jsonsrc/renderer/src/i18n/ms-MY/chat.jsonsrc/renderer/src/i18n/pl-PL/chat.jsonsrc/renderer/src/i18n/pt-BR/chat.jsonsrc/renderer/src/i18n/ru-RU/chat.jsonsrc/renderer/src/i18n/tr-TR/chat.jsonsrc/renderer/src/i18n/vi-VN/chat.jsonsrc/renderer/src/i18n/zh-CN/chat.jsonsrc/renderer/src/i18n/zh-HK/chat.jsonsrc/renderer/src/i18n/zh-TW/chat.jsonsrc/renderer/src/pages/ChatPage.vuesrc/renderer/src/stores/ui/memoryActivity.tssrc/shared/contracts/events/memory.events.tssrc/shared/contracts/routes.tssrc/shared/contracts/routes/memory.routes.tstest/main/presenter/agentMemoryTable.test.tstest/main/presenter/fakes/memoryFakes.tstest/main/presenter/memoryPresenter.test.tstest/main/routes/dispatcher.test.tstest/main/routes/memoryDto.test.tstest/renderer/api/clients.test.tstest/renderer/components/ChatPage.test.tstest/renderer/components/chat/MemoryTurnDialog.test.tstest/renderer/components/chat/MemoryUpdateChip.test.tstest/renderer/components/message/MessageItemAssistant.test.tstest/renderer/components/message/MessageToolbar.trace.test.tstest/renderer/stores/memoryActivityStore.test.ts
| tokenBudget: readNumber(record.tokenBudget), | ||
| estimatedTokens: readNumber(record.estimatedTokens), | ||
| selectedCount: Array.isArray(record.selected) ? record.selected.length : 0, | ||
| selectedIds: deriveSelectedMemoryIds(record.selected), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
selectedCount can diverge from selectedIds.length.
selectedCount counts raw record.selected entries, while selectedIds dedupes and drops invalid/empty entries. If the underlying "selected" payload ever has duplicate or malformed entries, the turn dialog could show a selection count that doesn't match the number of memories actually resolved via selectedIds.
💡 Proposed fix: derive selectedCount from selectedIds
+ const selectedIds = deriveSelectedMemoryIds(record.selected)
return {
sessionId: row.session_id,
messageId,
entryId: row.entry_id,
policyVersion:
typeof record.policyVersion === 'number' && Number.isFinite(record.policyVersion)
? record.policyVersion
: null,
tokenBudget: readNumber(record.tokenBudget),
estimatedTokens: readNumber(record.estimatedTokens),
- selectedCount: Array.isArray(record.selected) ? record.selected.length : 0,
- selectedIds: deriveSelectedMemoryIds(record.selected),
+ selectedCount: selectedIds ? selectedIds.length : Array.isArray(record.selected) ? record.selected.length : 0,
+ selectedIds,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| tokenBudget: readNumber(record.tokenBudget), | |
| estimatedTokens: readNumber(record.estimatedTokens), | |
| selectedCount: Array.isArray(record.selected) ? record.selected.length : 0, | |
| selectedIds: deriveSelectedMemoryIds(record.selected), | |
| const selectedIds = deriveSelectedMemoryIds(record.selected) | |
| selectedCount: selectedIds ? selectedIds.length : Array.isArray(record.selected) ? record.selected.length : 0, | |
| selectedIds, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/routes/index.ts` around lines 648 - 651, The selected count in the
route mapping can drift from the resolved IDs because it is computed from raw
record.selected while deriveSelectedMemoryIds() dedupes and filters invalid
entries. Update the mapping in index.ts so selectedCount is derived from the
same normalized selectedIds value used for selectedIds, keeping the turn dialog
count consistent with the actual resolved memories.
| let details: MemoryTurnDetail[] = [] | ||
| if (manifest?.selectedIds?.length) { | ||
| const selectedIds = uniqueIds(manifest.selectedIds).slice(0, MEMORY_DETAILS_BATCH_LIMIT) | ||
| const memories = await memoryClient.getByIds(agentId, selectedIds) | ||
| const memoryById = new Map(memories.map((memory) => [memory.id, memory])) | ||
| details = selectedIds.map((id) => ({ id, memory: memoryById.get(id) ?? null })) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Displayed detail count can silently diverge from the manifest's selectedCount stat.
manifest.selectedIds is deduplicated via uniqueIds(...) and truncated to MEMORY_DETAILS_BATCH_LIMIT (50) before building details. The turn dialog stat card renders turn.manifest.selectedCount directly (raw backend count, which may include duplicates or exceed 50), while turn.details reflects the deduped/truncated list. When duplicates or >50 selections occur (a scenario the test suite explicitly exercises with selectedIds: ['m2', 'missing', 'm2']), the "Selected" stat shown in MemoryTurnDialog.vue (Line 41) will not match the number of detail cards rendered, which can confuse users.
Consider deriving the displayed stat from the deduped/effective list, or surfacing a note when truncation/deduplication occurred.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/src/stores/ui/memoryActivity.ts` around lines 440 - 446, The
selected count shown by the turn dialog can diverge from the actual rendered
details because `getMemoryTurns` in `memoryActivity.ts` deduplicates and
truncates `manifest.selectedIds` before building `details`, while
`MemoryTurnDialog.vue` still displays `turn.manifest.selectedCount`. Update the
displayed stat to use the effective deduped/truncated selection count derived
from `selectedIds` (or store that count alongside `details`), and handle cases
where `uniqueIds(...)` or `MEMORY_DETAILS_BATCH_LIMIT` changes the visible list.
Summary by CodeRabbit
New Features
Bug Fixes