Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.9 kB · 177 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178import { join, normalize, sep } from 'path'import { getProjectRoot } from '../../bootstrap/state.js'import { buildMemoryPrompt, ensureMemoryDirExists,} from '../../memdir/memdir.js'import { getMemoryBaseDir } from '../../memdir/paths.js'import { getCwd } from '../../utils/cwd.js'import { findCanonicalGitRoot } from '../../utils/git.js'import { sanitizePath } from '../../utils/path.js'
// Persistent agent memory scope: 'user' (~/.claude/agent-memory/), 'project' (.claude/agent-memory/), or 'local' (.claude/agent-memory-local/)export type AgentMemoryScope = 'user' | 'project' | 'local'
/** * Sanitize an agent type name for use as a directory name. * Replaces colons (invalid on Windows, used in plugin-namespaced agent * types like "my-plugin:my-agent") with dashes. */function sanitizeAgentTypeForPath(agentType: string): string { return agentType.replace(/:/g, '-')}
/** * Returns the local agent memory directory, which is project-specific and not checked into VCS. * When CLAUDE_CODE_REMOTE_MEMORY_DIR is set, persists to the mount with project namespacing. * Otherwise, uses <cwd>/.claude/agent-memory-local/<agentType>/. */function getLocalAgentMemoryDir(dirName: string): string { if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) { return ( join( process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR, 'projects', sanitizePath( findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot(), ), 'agent-memory-local', dirName, ) + sep ) } return join(getCwd(), '.claude', 'agent-memory-local', dirName) + sep}
/** * Returns the agent memory directory for a given agent type and scope. * - 'user' scope: <memoryBase>/agent-memory/<agentType>/ * - 'project' scope: <cwd>/.claude/agent-memory/<agentType>/ * - 'local' scope: see getLocalAgentMemoryDir() */export function getAgentMemoryDir( agentType: string, scope: AgentMemoryScope,): string { const dirName = sanitizeAgentTypeForPath(agentType) switch (scope) { case 'project': return join(getCwd(), '.claude', 'agent-memory', dirName) + sep case 'local': return getLocalAgentMemoryDir(dirName) case 'user': return join(getMemoryBaseDir(), 'agent-memory', dirName) + sep }}
// Check if file is within an agent memory directory (any scope).export function isAgentMemoryPath(absolutePath: string): boolean { // SECURITY: Normalize to prevent path traversal bypasses via .. segments const normalizedPath = normalize(absolutePath) const memoryBase = getMemoryBaseDir()
// User scope: check memory base (may be custom dir or config home) if (normalizedPath.startsWith(join(memoryBase, 'agent-memory') + sep)) { return true }
// Project scope: always cwd-based (not redirected) if ( normalizedPath.startsWith(join(getCwd(), '.claude', 'agent-memory') + sep) ) { return true }
// Local scope: persisted to mount when CLAUDE_CODE_REMOTE_MEMORY_DIR is set, otherwise cwd-based if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) { if ( normalizedPath.includes(sep + 'agent-memory-local' + sep) && normalizedPath.startsWith( join(process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR, 'projects') + sep, ) ) { return true } } else if ( normalizedPath.startsWith( join(getCwd(), '.claude', 'agent-memory-local') + sep, ) ) { return true }
return false}
/** * Returns the agent memory file path for a given agent type and scope. */export function getAgentMemoryEntrypoint( agentType: string, scope: AgentMemoryScope,): string { return join(getAgentMemoryDir(agentType, scope), 'MEMORY.md')}
export function getMemoryScopeDisplay( memory: AgentMemoryScope | undefined,): string { switch (memory) { case 'user': return `User (${join(getMemoryBaseDir(), 'agent-memory')}/)` case 'project': return 'Project (.claude/agent-memory/)' case 'local': return `Local (${getLocalAgentMemoryDir('...')})` default: return 'None' }}
/** * Load persistent memory for an agent with memory enabled. * Creates the memory directory if needed and returns a prompt with memory contents. * * @param agentType The agent's type name (used as directory name) * @param scope 'user' for ~/.claude/agent-memory/ or 'project' for .claude/agent-memory/ */export function loadAgentMemoryPrompt( agentType: string, scope: AgentMemoryScope,): string { let scopeNote: string switch (scope) { case 'user': scopeNote = '- Since this memory is user-scope, keep learnings general since they apply across all projects' break case 'project': scopeNote = '- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project' break case 'local': scopeNote = '- Since this memory is local-scope (not checked into version control), tailor your memories to this project and machine' break }
const memoryDir = getAgentMemoryDir(agentType, scope)
// Fire-and-forget: this runs at agent-spawn time inside a sync // getSystemPrompt() callback (called from React render in AgentDetail.tsx, // so it cannot be async). The spawned agent won't try to Write until after // a full API round-trip, by which time mkdir will have completed. Even if // it hasn't, FileWriteTool does its own mkdir of the parent directory. void ensureMemoryDirExists(memoryDir)
const coworkExtraGuidelines = process.env.CLAUDE_COWORK_MEMORY_EXTRA_GUIDELINES return buildMemoryPrompt({ displayName: 'Persistent Agent Memory', memoryDir, extraGuidelines: coworkExtraGuidelines && coworkExtraGuidelines.trim().length > 0 ? [scopeNote, coworkExtraGuidelines] : [scopeNote], })}