feat: add memory management commands to CLI#567
feat: add memory management commands to CLI#567yanurag-dev wants to merge 7 commits intotruffle-ai:mainfrom
Conversation
|
@yanurag-dev is attempting to deploy a commit to the Shaunak's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds interactive memory management to the CLI: new /memory command and overlays, memory utilities for AGENTS.md-style instruction files, Ink UI wizards (add/remove/manager), wiring into containers/state, and an exported discoverAgentInstructionFile API. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as CLI Commands
participant Container as OverlayContainer
participant MemoryUI as MemoryManager UI
participant MemoryUtils as Memory Utils
participant FileSystem as File System
User->>CLI: Input /memory
CLI->>Container: Command result with __triggerOverlay: memory-manager
Container->>MemoryUI: Show MemoryManager overlay
MemoryUI->>User: Display add/show/remove options
User->>MemoryUI: Select 'add'
MemoryUI->>Container: handleMemoryManagerAction('add')
Container->>MemoryUI: Show MemoryAddWizard
MemoryUI->>User: Step 1: Select scope
User->>MemoryUI: Choose scope
MemoryUI->>User: Step 2: Enter content
User->>MemoryUI: Input memory text and submit
MemoryUI->>Container: onComplete(content, scope)
Container->>MemoryUtils: addMemoryEntry(content, scope)
MemoryUtils->>FileSystem: Read/Create instruction file & parse Memory section
MemoryUtils->>FileSystem: Append entry and write file
FileSystem-->>MemoryUtils: Write success
MemoryUtils-->>Container: Success message
Container->>User: Display success message
sequenceDiagram
participant User
participant CLI as CLI Commands
participant Container as OverlayContainer
participant RemoveUI as MemoryRemoveWizard
participant MemoryUtils as Memory Utils
participant FileSystem as File System
User->>CLI: Input /memory remove
CLI->>Container: Command result with __triggerOverlay: memory-remove-wizard
Container->>MemoryUtils: listMemoryEntries()
MemoryUtils->>FileSystem: Read project & global instruction files
FileSystem-->>MemoryUtils: Return entries
MemoryUtils-->>Container: Entries by scope
Container->>RemoveUI: Show MemoryRemoveWizard with entries
RemoveUI->>User: Select scope then entry
User->>RemoveUI: Choose entry index
RemoveUI->>Container: onComplete(index, scope)
Container->>MemoryUtils: removeMemoryEntry(index, scope)
MemoryUtils->>FileSystem: Read, modify, write file
FileSystem-->>MemoryUtils: Write success
MemoryUtils-->>Container: Success message
Container->>User: Display success message
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 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: 3
🤖 Fix all issues with AI agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-command.ts`:
- Around line 93-97: The handler function's parameter list contains an unused
parameter named agent which triggers ESLint; update the handler signature in
memory-command.ts to either rename agent to _agent (e.g., handler: async (args:
string[], _agent: DextoAgent, _ctx: CommandContext) => ...) or remove the
parameter entirely if the type system permits, and ensure all internal
references (if any) are updated accordingly so the lint warning is resolved.
In `@packages/cli/src/cli/commands/interactive-commands/memory-utils.ts`:
- Around line 182-236: The removal logic in removeMemoryEntry currently looks
for an exact string `- ${entryToRemove}` which can fail if bullets have extra
whitespace; update removeMemoryEntry to match bullets by comparing the parsed
bullet content rather than raw line equality: when scanning lines inside the
MEMORY_SECTION_HEADER block, normalize each bullet line (trim leading '-' and
whitespace) and compare to entryToRemove (or use a regex that allows arbitrary
spaces like /^\s*-\s*(.+)$/ and compare captured group) and only skip/write the
line when a match is found; set a removed flag and only call writeFileSync if
removed is true, and if removed remains false return { success: false, filePath,
error: 'Entry not found' }; reference functions/constants: parseMemoryEntries,
MEMORY_SECTION_HEADER, entryToRemove, removeMemoryEntry.
- Around line 14-37: findInstructionFile() currently checks
AGENT_INSTRUCTION_FILES with existsSync (case-sensitive) causing divergence;
replace its body to call the shared discoverAgentInstructionFile() and return
its result (string | null) so behavior matches the shared helper; ensure
getOrCreateInstructionFile() continues to use findInstructionFile() (or call
discoverAgentInstructionFile() directly) and preserve return types; update any
references to AGENT_INSTRUCTION_FILES only if still needed.
🧹 Nitpick comments (2)
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts (1)
89-176: Consider standard Result helpers for these utilities.These helpers return bespoke
{ success, error }objects. In packages/cli we standardize onok()/fail()helpers for consistent error handling and composition. Consider switching these utilities (and callers) to the Result helpers pattern for alignment. As per coding guidelines, Use standardized Result helpers from packages/core/src/utils/result.ts: ok(data, issues?), fail(issues), hasErrors(issues), splitIssues(issues), zodToIssues(zodError).packages/cli/src/cli/commands/interactive-commands/command-parser.ts (1)
92-106: Consider narrowing the#trigger to avoid hijacking normal prompts.Currently any input starting with
#becomes a memory-add command. That blocks sending prompts that begin with markdown headings or code (e.g.,#include). You could require#(or other delimiter) to reduce accidental captures.💡 Example tweak
- if (trimmed.startsWith('#')) { - const memoryContent = trimmed.slice(1).trim(); + const memoryMatch = trimmed.match(/^#\s+(.*)$/); + if (memoryMatch) { + const memoryContent = memoryMatch[1]; return { type: 'command', command: 'memory-add', args: [memoryContent], rawInput: trimmed, }; }
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-utils.ts`:
- Around line 170-246: The removal currently matches bullet content which breaks
on duplicate entries; in removeMemoryEntry track a bulletCounter while scanning
the MEMORY_SECTION_HEADER section instead of comparing content to entryToRemove,
increment the counter whenever a bulletRegex match is found, and when
bulletCounter === index skip that specific bullet line (set removed = true) so
the exact indexed entry is removed; keep the existing index bounds check (using
entries.length), keep writeFileSync only when removed, and remove the
content-based equality check for deletion.
🧹 Nitpick comments (1)
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts (1)
77-81: Consider using standardized Result helpers for these exports.Aligning add/list/remove with
ok/failkeeps error handling consistent across CLI utilities and reduces bespoke result shapes.
As per coding guidelines:packages/{core,server,cli}/src/**/*.ts: Use standardized Result helpers frompackages/core/src/utils/result.ts:ok(data, issues?),fail(issues),hasErrors(issues),splitIssues(issues),zodToIssues(zodError).
| export function removeMemoryEntry(index: number): { | ||
| success: boolean; | ||
| filePath: string; | ||
| error?: string; | ||
| } { | ||
| try { | ||
| const filePath = findInstructionFile(); | ||
|
|
||
| if (!filePath || !existsSync(filePath)) { | ||
| return { success: false, filePath: '', error: 'No instruction file found' }; | ||
| } | ||
|
|
||
| const content = readFileSync(filePath, 'utf-8'); | ||
| const entries = parseMemoryEntries(content); | ||
|
|
||
| if (index < 0 || index >= entries.length) { | ||
| return { success: false, filePath, error: 'Invalid entry index' }; | ||
| } | ||
|
|
||
| // Remove the entry | ||
| const entryToRemove = entries[index]; | ||
| if (!entryToRemove) { | ||
| return { success: false, filePath, error: 'Entry not found' }; | ||
| } | ||
|
|
||
| const lines = content.split('\n'); | ||
| const newLines: string[] = []; | ||
| let inMemorySection = false; | ||
| let removed = false; | ||
|
|
||
| // Regex to extract bullet content: matches "- content" with any whitespace | ||
| const bulletRegex = /^\s*-\s*(.+)$/; | ||
|
|
||
| for (const line of lines) { | ||
| const trimmed = line.trim(); | ||
|
|
||
| if (trimmed === MEMORY_SECTION_HEADER) { | ||
| inMemorySection = true; | ||
| newLines.push(line); | ||
| continue; | ||
| } | ||
|
|
||
| if (inMemorySection && trimmed.startsWith('##')) { | ||
| inMemorySection = false; | ||
| } | ||
|
|
||
| // Check if this line is a bullet point that matches the entry to remove | ||
| if (inMemorySection && !removed) { | ||
| const match = trimmed.match(bulletRegex); | ||
| if (match) { | ||
| const bulletContent = match[1]?.trim(); | ||
| if (bulletContent === entryToRemove.trim()) { | ||
| removed = true; | ||
| continue; // Skip this line | ||
| } | ||
| } | ||
| } | ||
|
|
||
| newLines.push(line); | ||
| } | ||
|
|
||
| // Only write to file if we actually removed something | ||
| if (!removed) { | ||
| return { success: false, filePath, error: 'Entry not found' }; | ||
| } | ||
|
|
||
| writeFileSync(filePath, newLines.join('\n'), 'utf-8'); | ||
|
|
||
| return { success: true, filePath }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| filePath: '', | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Remove by index to avoid duplicate-entry mismatches.
Currently removal matches the first bullet with matching content, so if entries are duplicated, removing by index can delete the wrong one. Track bullet index while scanning the Memory section and remove the exact target index.
🛠️ Proposed fix (index-based removal)
- // Remove the entry
- const entryToRemove = entries[index];
- if (!entryToRemove) {
- return { success: false, filePath, error: 'Entry not found' };
- }
+ const targetIndex = index;
@@
- let removed = false;
+ let removed = false;
+ let currentIndex = -1;
@@
- if (inMemorySection && !removed) {
- const match = trimmed.match(bulletRegex);
- if (match) {
- const bulletContent = match[1]?.trim();
- if (bulletContent === entryToRemove.trim()) {
- removed = true;
- continue; // Skip this line
- }
- }
- }
+ if (inMemorySection && !removed) {
+ const match = trimmed.match(bulletRegex);
+ if (match) {
+ currentIndex += 1;
+ if (currentIndex === targetIndex) {
+ removed = true;
+ continue; // Skip this line
+ }
+ }
+ }🤖 Prompt for AI Agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-utils.ts` around
lines 170 - 246, The removal currently matches bullet content which breaks on
duplicate entries; in removeMemoryEntry track a bulletCounter while scanning the
MEMORY_SECTION_HEADER section instead of comparing content to entryToRemove,
increment the counter whenever a bulletRegex match is found, and when
bulletCounter === index skip that specific bullet line (set removed = true) so
the exact indexed entry is removed; keep the existing index bounds check (using
entries.length), keep writeFileSync only when removed, and remove the
content-based equality check for deletion.
|
Nice! I think this is slightly different from our core memory layer but since adding memory into .md is also useful, let's add this version too. Can we add options for creating memory with user/project scope? Currently this always adds it to the project scope only. It would be helpful if after creating the memory, it prompts the user if the scope should be project or user/global. (Refer /plugin command) |
- Introduced memory command to manage agent memory, allowing users to list, add, and remove memory entries. - Implemented functionality to discover and create agent instruction files for storing memory entries. - Updated command parser to recognize memory commands prefixed with '#' for adding entries. - Enhanced user feedback with appropriate messages for memory operations.
- Updated memory command handler to use a placeholder for the agent parameter. - Refactored instruction file discovery logic to utilize shared functionality for consistent case-insensitive matching. - Enhanced the removeMemoryEntry function to accurately identify and remove bullet points, ensuring only valid entries are processed.
…d and manager components.
…izards - Renamed the memory list command to show for clarity. - Improved the remove command to support both project and global memory entries with an interactive wizard. - Updated memory command usage documentation and user feedback messages. - Added MemoryRemoveWizard component for guided memory entry removal. - Refactored memory command handlers to accommodate new functionality.
b316288 to
d4112d4
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/cli/src/cli/commands/interactive-commands/memory-command.ts`:
- Around line 1-7: Remove the unused imports causing ESLint warnings by deleting
the `import * as path from 'path';` line and the `import {
discoverAgentInstructionFile } from '@dexto/agent-management';` line in
memory-command.ts; verify there are no references to `path` or
`discoverAgentInstructionFile` anywhere in this file (e.g., inside functions
that call them) and if references exist, either remove or replace those usages
accordingly so the module builds cleanly.
In `@packages/cli/src/cli/ink-cli/containers/InputContainer.tsx`:
- Around line 792-794: Add the missing overlay key 'memory-remove-wizard' to the
overlaysWithOwnInput array in InputContainer.tsx so the main input is disabled
when the remove wizard (which uses BaseSelector and manages its own text input)
is active; edit the overlaysWithOwnInput constant (where 'memory-manager' and
'memory-add-wizard' are listed) to include 'memory-remove-wizard' so input
conflicts are prevented.
🧹 Nitpick comments (5)
packages/cli/src/cli/commands/interactive-commands/memory-utils.ts (1)
181-184: Consider clarifyingfilePathwhen no project file exists.When
projectPathisnull,result.project.filePathis set to a path that doesn't exist yet. This could mislead callers expecting the path to reference an actual file. Consider setting it tonullor clearly documenting that the path is the default location where a file would be created.const result: MemoryListResult = { - project: { entries: [], filePath: projectPath || path.join(process.cwd(), 'AGENTS.md') }, + project: { entries: [], filePath: projectPath }, global: { entries: [], filePath: globalPath }, };packages/cli/src/cli/ink-cli/components/overlays/MemoryAddWizard.tsx (1)
68-71: Provide feedback when content is empty on submit.When the user presses Enter with empty content, the submission is silently ignored. Consider providing visual feedback (e.g., a brief error message or highlighting) so users understand why nothing happened.
packages/cli/src/cli/commands/interactive-commands/memory-command.ts (1)
65-68: Consider formalizing the overlay trigger return type.Multiple locations use
as anyto return overlay trigger objects ({ __triggerOverlay: '...' }). This bypasses type safety. Consider extendingCommandHandlerResultto include an overlay trigger variant, or create a helper function that returns a properly typed result.// Example: Add to command-parser.ts export interface OverlayTrigger { __triggerOverlay: OverlayType; args?: string[]; } export type CommandHandlerResult = boolean | string | StyledOutput | SendMessageMarker | OverlayTrigger;Also applies to: 161-164, 168-170
packages/cli/src/cli/ink-cli/containers/OverlayContainer.tsx (1)
2698-2710: DuplicatelistMemoryEntries()calls in render.
listMemoryEntries()is called twice on lines 2704 and 2705, causing redundant computation on every render when the overlay is visible.♻️ Proposed fix to compute once
{/* Memory Remove Wizard */} - {ui.activeOverlay === 'memory-remove-wizard' && ( + {ui.activeOverlay === 'memory-remove-wizard' && (() => { + const memoryData = listMemoryEntries(); + return ( <MemoryRemoveWizard ref={memoryRemoveWizardRef} isVisible={true} state={ui.memoryRemoveWizard} - projectEntries={listMemoryEntries().project.entries} - globalEntries={listMemoryEntries().global.entries} + projectEntries={memoryData.project.entries} + globalEntries={memoryData.global.entries} onUpdateState={handleMemoryRemoveWizardUpdate} onComplete={handleMemoryRemoveComplete} onClose={handleClose} /> - )} + ); + })()}Alternatively, consider using
useMemoif the overlay is frequently toggled.packages/cli/src/cli/ink-cli/components/overlays/MemoryRemoveWizard.tsx (1)
71-74: Consider using the index parameter directly fromonSelect.The
entryparameter is unused becauseentryIndexis used instead. This works but could be cleaner ifBaseSelector.onSelectprovided the index, or by renaming the parameter to_entryto signal intentional non-use.💡 Optional: Mark unused parameter
- const handleEntrySelect = (entry: string) => { + const handleEntrySelect = (_entry: string) => { if (!state?.scope) return; onComplete(entryIndex, state.scope); };
packages/cli/src/cli/commands/interactive-commands/memory-command.ts
Outdated
Show resolved
Hide resolved
- Removed unused imports from memory command file for better clarity. - Added 'memory-remove-wizard' to the list of overlays in InputContainer for improved user interaction.
| @@ -0,0 +1,13 @@ | |||
| --- | |||
| '@dexto/agent-management': patch | |||
| 'dexto': minor | |||
There was a problem hiding this comment.
change to patch version
| * Handler for /memory remove with power user shortcuts | ||
| * Syntax: | ||
| * /memory remove → Interactive wizard | ||
| * /memory remove <number> → Remove from project | ||
| * /memory remove <number> --global → Remove from global | ||
| * /memory remove global <number> → Remove from global | ||
| */ | ||
| async function handleRemoveCommand(args: string[]): Promise<CommandHandlerResult> { |
There was a problem hiding this comment.
we don't need power user shortcuts like /memory remove, let interactive command handle all of this
can remove any unnecessary code from these files
| } | ||
|
|
||
| /** | ||
| * Handler for # <content> - DEPRECATED: Now handled via /memory add |
There was a problem hiding this comment.
can remove if unused
| function findProjectInstructionFile(): string | null { | ||
| return discoverAgentInstructionFile(); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the global instruction file path (~/.dexto/AGENTS.md) | ||
| */ | ||
| function getGlobalInstructionFilePath(): string { | ||
| return getDextoGlobalPath('', 'AGENTS.md'); | ||
| } |
There was a problem hiding this comment.
no need for 1 line functions, just call the function directly
| /** | ||
| * Add a memory entry to the instruction file for a given scope | ||
| */ | ||
| export function addMemoryEntry( |
There was a problem hiding this comment.
i believe internal memories are stored in the database and need to be enabled, can you check MemoryManager class
Not sure if we should be adding to AGENTS.md here, it kind of breaks the pattern we have in core
do other CLI tools write to AGENTS.md?
Release Note
pnpm changeset(select packages + bump)Summary by CodeRabbit