fix(ui): scope agent memory to project context owner

This commit is contained in:
Bohdan Triapitsyn
2026-08-27 20:18:12 +03:00
parent 7aae5a6634
commit 03f4b5e3e0
9 changed files with 214 additions and 54 deletions
@@ -106,10 +106,16 @@ its own tool. It feeds this panel only — what a session is told about memory i
decided server-side by `packages/web/server/lib/session-knowledge`, so it
reaches sessions that have no UI at all and survives compaction.
Both sides resolve a worktree to its project before touching the store — the
client through `resolveProjectForSessionDirectory`, the server through
`agent-memory/project-resolution`. Keying by the session directory instead filed
a worktree's memories under a project nothing reads.
`useProjectContextOwner` is the client authority shared by this panel and the
memory sync. It resolves managed chat directories to the Chats root and a
worktree to its project before either consumer touches a store. The server uses
`agent-memory/project-resolution` for the same worktree rule. Keying by a
worktree session directory would file memories under a project nothing reads.
Project memory is rendered only when the store's `projectPath` matches the
panel owner. An owner switch hides the previous project's entries before the
new request starts. A failed request marks the new owner unavailable instead of
presenting that hidden list as authoritative empty memory.
Turning the switch back on re-reads the store only after the setting has
finished being written. The switch flips the client immediately, which makes the
@@ -11,7 +11,7 @@ import { useI18n } from '@/lib/i18n';
import { AGENT_MEMORY_BODY_MAX_LENGTH, AGENT_MEMORY_TITLE_MAX_LENGTH, type AgentMemoryEntry, type AgentMemoryScope } from '@/lib/agentMemoryApi';
import { classifyMemory, memoryViewKey, type MemoryBadge } from '@/lib/agentMemoryBadges';
import { cn } from '@/lib/utils';
import { useAgentMemoryStore } from '@/stores/useAgentMemoryStore';
import { selectProjectMemoryForPath, useAgentMemoryStore } from '@/stores/useAgentMemoryStore';
import { useUIStore } from '@/stores/useUIStore';
/**
@@ -160,7 +160,7 @@ export const MemorySection: React.FC<{
const [expandedId, setExpandedId] = React.useState<string | null>(null);
const globalEntries = useAgentMemoryStore((state) => state.global);
const projectEntries = useAgentMemoryStore((state) => state.project);
const projectEntries = useAgentMemoryStore((state) => selectProjectMemoryForPath(state, projectPath));
const globalFailed = useAgentMemoryStore((state) => state.globalFailed);
const projectFailed = useAgentMemoryStore((state) => state.projectFailed);
const deleteEntry = useAgentMemoryStore((state) => state.deleteEntry);
@@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
import { useI18n } from '@/lib/i18n';
import { resolveProjectContextId, type ProjectRef, type ProjectTodoItem } from '@/lib/projectContextApi';
import { cn } from '@/lib/utils';
import { useAgentMemoryStore } from '@/stores/useAgentMemoryStore';
import { selectProjectMemoryForPath, useAgentMemoryStore } from '@/stores/useAgentMemoryStore';
import { countHighlightedMemories, memoryViewKey } from '@/lib/agentMemoryBadges';
import { EMPTY_PROJECT_CONTEXT_ENTRY, useProjectContextStore } from '@/stores/useProjectContextStore';
import { useUIStore } from '@/stores/useUIStore';
@@ -133,7 +133,9 @@ export const ProjectNotesTodoPanel: React.FC<ProjectNotesTodoPanelProps> = ({
const memoryDisabledByServer = useAgentMemoryStore((state) => state.disabled);
const memoryVisible = memoryEnabled && !memoryDisabledByServer;
const globalMemory = useAgentMemoryStore((state) => state.global);
const projectMemory = useAgentMemoryStore((state) => state.project);
const projectMemory = useAgentMemoryStore(
(state) => selectProjectMemoryForPath(state, projectRef?.path ?? null),
);
const isMobile = useUIStore((state) => state.isMobile);
const storedTab = useUIStore((state) => state.projectContextTab);