fix(ui): scope agent memory to project context owner
This commit is contained in:
@@ -6,46 +6,26 @@ import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { formatDirectoryName } from '@/lib/utils';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { CHAT_DRAFT_PROJECT_ID, getChatsRootForHome, getChatsRootFromDirectory, isChatDirectoryPath } from '@/lib/chatDirectories';
|
||||
import { CHAT_DRAFT_PROJECT_ID } from '@/lib/chatDirectories';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { useProjectContextOwner } from '@/hooks/useProjectContextOwner';
|
||||
|
||||
export const ProjectContextPanel: React.FC<{
|
||||
onActionComplete?: () => void;
|
||||
onOpenPlan?: (plan: { id: string; title: string }) => void;
|
||||
}> = ({ onActionComplete, onOpenPlan }) => {
|
||||
const activeProjectId = useProjectsStore((state) => state.activeProjectId);
|
||||
const projects = useProjectsStore((state) => state.projects);
|
||||
const homeDirectory = useDirectoryStore((state) => state.homeDirectory);
|
||||
const { t } = useI18n();
|
||||
const gitDirectories = useGitStore((state) => state.directories);
|
||||
const isChatContext = useSessionUIStore((state) => (
|
||||
state.newSessionDraft.open
|
||||
? state.newSessionDraft.target === 'chat'
|
||||
: isChatDirectoryPath(state.currentSessionDirectory)
|
||||
));
|
||||
const chatSessionDirectory = useSessionUIStore((state) => state.currentSessionDirectory);
|
||||
const chatsRoot = getChatsRootFromDirectory(chatSessionDirectory) ?? getChatsRootForHome(homeDirectory);
|
||||
const projectRef = useProjectContextOwner(chatSessionDirectory);
|
||||
const isChatContext = projectRef?.id === CHAT_DRAFT_PROJECT_ID;
|
||||
|
||||
const activeProject = React.useMemo(() => {
|
||||
if (isChatContext) return null;
|
||||
if (activeProjectId) {
|
||||
return projects.find((project) => project.id === activeProjectId) ?? projects[0] ?? null;
|
||||
}
|
||||
return projects[0] ?? null;
|
||||
}, [activeProjectId, isChatContext, projects]);
|
||||
|
||||
const projectRef = React.useMemo(() => {
|
||||
if (isChatContext && chatsRoot) {
|
||||
return { id: CHAT_DRAFT_PROJECT_ID, path: chatsRoot };
|
||||
}
|
||||
if (!activeProject) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: activeProject.id,
|
||||
path: activeProject.path,
|
||||
};
|
||||
}, [activeProject, chatsRoot, isChatContext]);
|
||||
return projects.find((project) => project.id === projectRef?.id) ?? null;
|
||||
}, [isChatContext, projectRef?.id, projects]);
|
||||
|
||||
const projectLabel = React.useMemo(() => {
|
||||
if (isChatContext) return t('sessions.sidebar.activity.chatsTitle');
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user