Skip to content

Commit 30e2302

Browse files
authored
Merge pull request #19 from baryhuang/claude/issue-6-20250802-2142
feat: implement agent message history isolation
2 parents 5201713 + d34185c commit 30e2302

File tree

8 files changed

+77
-64
lines changed

8 files changed

+77
-64
lines changed

backend/handlers/agentHistories.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export async function handleAgentHistoriesRequest(c: Context) {
1414
try {
1515
const { debugMode, runtime } = c.var.config;
1616
const encodedProjectName = c.req.param("encodedProjectName");
17+
const agentId = c.req.query("agentId"); // Get agent ID from query parameter
1718

1819
if (!encodedProjectName) {
1920
return c.json({ error: "Encoded project name is required" }, 400);
@@ -65,16 +66,45 @@ export async function handleAgentHistoriesRequest(c: Context) {
6566
}
6667

6768
// Group conversations and remove duplicates
68-
const conversations = groupConversations(conversationFiles);
69+
const groupedConversations = groupConversations(conversationFiles);
70+
71+
// Filter conversations by agent ID if provided
72+
let filteredConversations = groupedConversations;
73+
if (agentId) {
74+
filteredConversations = groupedConversations.filter(conversation => {
75+
// Find the corresponding conversation file to check agent ID
76+
const conversationFile = conversationFiles.find(file =>
77+
file.sessionId === conversation.sessionId
78+
);
79+
return conversationFile?.agentId === agentId;
80+
});
81+
82+
if (debugMode) {
83+
console.debug(
84+
`[DEBUG] Filtered to ${filteredConversations.length} conversations for agent: ${agentId}`,
85+
);
86+
}
87+
}
88+
89+
// Add agent ID to the conversation summaries
90+
const conversationsWithAgentId = filteredConversations.map(conversation => {
91+
const conversationFile = conversationFiles.find(file =>
92+
file.sessionId === conversation.sessionId
93+
);
94+
return {
95+
...conversation,
96+
agentId: conversationFile?.agentId
97+
};
98+
});
6999

70100
if (debugMode) {
71101
console.debug(
72-
`[DEBUG] After grouping: ${conversations.length} unique agent conversations`,
102+
`[DEBUG] After grouping and filtering: ${conversationsWithAgentId.length} unique agent conversations`,
73103
);
74104
}
75105

76106
const response: HistoryListResponse = {
77-
conversations,
107+
conversations: conversationsWithAgentId,
78108
};
79109

80110
return c.json(response);

backend/history/grouping.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function createConversationSummary(
6464
lastTime: conversationFile.lastTime,
6565
messageCount: conversationFile.messageCount,
6666
lastMessagePreview: conversationFile.lastMessagePreview,
67+
agentId: conversationFile.agentId,
6768
};
6869
}
6970

backend/history/parser.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface RawHistoryLine {
2222
cwd?: string;
2323
version?: string;
2424
requestId?: string;
25+
agentId?: string; // Agent that created this conversation line
2526
}
2627

2728
// Legacy interface maintained for transition period
@@ -35,6 +36,7 @@ export interface ConversationFile {
3536
lastTime: string;
3637
messageCount: number;
3738
lastMessagePreview: string;
39+
agentId?: string; // Agent that created this conversation
3840
}
3941

4042
/**
@@ -61,12 +63,18 @@ async function parseHistoryFile(
6163
let startTime = "";
6264
let lastTime = "";
6365
let lastMessagePreview = "";
66+
let agentId: string | undefined;
6467

6568
for (const line of lines) {
6669
try {
6770
const parsed = JSON.parse(line) as RawHistoryLine;
6871
messages.push(parsed);
6972

73+
// Extract agent ID from the first message that has one
74+
if (!agentId && parsed.agentId) {
75+
agentId = parsed.agentId;
76+
}
77+
7078
// Track message IDs from assistant messages
7179
if (parsed.message?.role === "assistant" && "id" in parsed.message && parsed.message.id) {
7280
messageIds.add(parsed.message.id);
@@ -114,6 +122,7 @@ async function parseHistoryFile(
114122
lastTime,
115123
messageCount: messages.length,
116124
lastMessagePreview: lastMessagePreview || "No preview available",
125+
agentId,
117126
};
118127
} catch (error) {
119128
console.error(`Failed to read history file ${filePath}:`, error);

backend/history/timestampRestore.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export function calculateConversationMetadata(messages: RawHistoryLine[]): {
6666
startTime: string;
6767
endTime: string;
6868
messageCount: number;
69+
agentId?: string;
6970
} {
7071
if (messages.length === 0) {
7172
const now = new Date().toISOString();
@@ -81,10 +82,14 @@ export function calculateConversationMetadata(messages: RawHistoryLine[]): {
8182
const startTime = sortedMessages[0].timestamp;
8283
const endTime = sortedMessages[sortedMessages.length - 1].timestamp;
8384

85+
// Extract agent ID from the first message that has one
86+
const agentId = messages.find(msg => msg.agentId)?.agentId;
87+
8488
return {
8589
startTime,
8690
endTime,
8791
messageCount: messages.length,
92+
agentId,
8893
};
8994
}
9095

@@ -101,6 +106,7 @@ export function processConversationMessages(
101106
startTime: string;
102107
endTime: string;
103108
messageCount: number;
109+
agentId?: string;
104110
};
105111
} {
106112
// Restore timestamps

frontend/src/components/native/AgentDetailView.tsx

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -129,43 +129,17 @@ export function AgentDetailView({
129129
const agentProjects = await remoteHistory.fetchAgentProjects(agent.apiEndpoint);
130130
console.log(`📁 Found ${agentProjects.length} projects for agent ${agent.name}`);
131131

132-
// Filter projects to only those relevant to this agent
133-
const relevantProjects = agentProjects.filter(project => {
134-
// Filter by agent's working directory or related keywords
135-
const agentWorkingDir = agent.workingDirectory.toLowerCase();
136-
const projectPath = project.path.toLowerCase();
137-
138-
// Extract keywords from agent working directory and description
139-
const agentKeywords = [
140-
...agentWorkingDir.split(/[/\\-_\s]+/).filter(Boolean),
141-
...agent.description.toLowerCase().split(/[\s,.-]+/).filter(Boolean),
142-
agent.id.toLowerCase()
143-
];
144-
145-
// Check if project path contains any agent-specific keywords
146-
const isRelevant = agentKeywords.some(keyword =>
147-
keyword.length > 2 && projectPath.includes(keyword)
148-
);
149-
150-
console.log(`📂 Project: ${project.path}`);
151-
console.log(`🔍 Agent keywords: ${agentKeywords.join(", ")}`);
152-
console.log(`✅ Is relevant: ${isRelevant}`);
153-
154-
return isRelevant;
155-
});
156-
157-
console.log(`🎯 Filtered to ${relevantProjects.length} relevant projects for agent ${agent.name}`);
158-
console.log(`📋 Relevant projects: ${relevantProjects.map(p => p.path).join(", ")}`);
159-
160-
// Collect all conversations from relevant projects only
132+
// Collect all conversations from all projects for this agent
133+
// Backend will filter by agent ID, so no need for fragile keyword matching
161134
const allAgentConversations: ConversationSummary[] = [];
162135

163-
for (const project of relevantProjects) {
136+
for (const project of agentProjects) {
164137
try {
165138
console.log(`📖 Loading conversations from project: ${project.path}`);
166139
const projectHistories = await remoteHistory.fetchAgentHistories(
167140
agent.apiEndpoint,
168-
project.encodedName
141+
project.encodedName,
142+
agent.id // Pass agent ID for filtering
169143
);
170144
console.log(`💬 Found ${projectHistories.length} conversations in project ${project.path}`);
171145
allAgentConversations.push(...projectHistories);
@@ -197,32 +171,16 @@ export function AgentDetailView({
197171
setHistoryLoading(true);
198172
setHistoryError(null);
199173

200-
// Try to find the conversation in relevant projects only
174+
// Try to find the conversation in all agent projects
201175
let foundProject = null;
202176

203177
try {
204178
const agentProjects = await remoteHistory.fetchAgentProjects(agent.apiEndpoint);
205179

206-
// Filter to only relevant projects (same logic as loadAgentHistory)
207-
const relevantProjects = agentProjects.filter(project => {
208-
const agentWorkingDir = agent.workingDirectory.toLowerCase();
209-
const projectPath = project.path.toLowerCase();
210-
211-
const agentKeywords = [
212-
...agentWorkingDir.split(/[/\\-_\s]+/).filter(Boolean),
213-
...agent.description.toLowerCase().split(/[\s,.-]+/).filter(Boolean),
214-
agent.id.toLowerCase()
215-
];
216-
217-
return agentKeywords.some(keyword =>
218-
keyword.length > 2 && projectPath.includes(keyword)
219-
);
220-
});
221-
222-
console.log(`🔍 Searching for session ${sessionId} in ${relevantProjects.length} relevant projects`);
180+
console.log(`🔍 Searching for session ${sessionId} in ${agentProjects.length} projects`);
223181

224-
// Try each relevant project until we find one with this session
225-
for (const project of relevantProjects) {
182+
// Try each project until we find one with this session
183+
for (const project of agentProjects) {
226184
try {
227185
const conversation = await remoteHistory.fetchAgentConversation(
228186
agent.apiEndpoint,

frontend/src/config/api.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ export const getAgentProjectsUrl = (agentEndpoint: string) => {
6060
return `${agentEndpoint}${API_CONFIG.ENDPOINTS.AGENT_PROJECTS}`;
6161
};
6262

63-
export const getAgentHistoriesUrl = (agentEndpoint: string, projectPath: string) => {
63+
export const getAgentHistoriesUrl = (agentEndpoint: string, projectPath: string, agentId?: string) => {
6464
const encodedPath = encodeURIComponent(projectPath);
65-
return `${agentEndpoint}${API_CONFIG.ENDPOINTS.AGENT_HISTORIES}/${encodedPath}`;
65+
const baseUrl = `${agentEndpoint}${API_CONFIG.ENDPOINTS.AGENT_HISTORIES}/${encodedPath}`;
66+
if (agentId) {
67+
return `${baseUrl}?agentId=${encodeURIComponent(agentId)}`;
68+
}
69+
return baseUrl;
6670
};
6771

6872
export const getAgentConversationUrl = (

frontend/src/hooks/useRemoteAgentHistory.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getAgentProjectsUrl, getAgentHistoriesUrl, getAgentConversationUrl } fr
55
interface RemoteAgentHistoryCache {
66
[agentEndpoint: string]: {
77
projects?: ProjectInfo[];
8-
histories?: { [projectId: string]: ConversationSummary[] };
8+
histories?: { [key: string]: ConversationSummary[] }; // key: `${projectId}:${agentId || 'all'}`
99
conversations?: { [key: string]: ConversationHistory }; // key: `${projectId}:${sessionId}`
1010
lastFetch?: number;
1111
};
@@ -63,17 +63,19 @@ export function useRemoteAgentHistory() {
6363

6464
const fetchAgentHistories = useCallback(async (
6565
agentEndpoint: string,
66-
projectId: string
66+
projectId: string,
67+
agentId?: string
6768
): Promise<ConversationSummary[]> => {
68-
if (isCacheValid(agentEndpoint, 'histories') && cache[agentEndpoint].histories?.[projectId]) {
69-
return cache[agentEndpoint].histories![projectId];
69+
const cacheKey = `${projectId}:${agentId || 'all'}`;
70+
if (isCacheValid(agentEndpoint, 'histories') && cache[agentEndpoint].histories?.[cacheKey]) {
71+
return cache[agentEndpoint].histories![cacheKey];
7072
}
7173

7274
try {
7375
setLoading(true);
7476
setError(null);
7577

76-
const response = await fetch(getAgentHistoriesUrl(agentEndpoint, projectId));
78+
const response = await fetch(getAgentHistoriesUrl(agentEndpoint, projectId, agentId));
7779
if (!response.ok) {
7880
throw new Error(`Failed to fetch agent histories: ${response.statusText}`);
7981
}
@@ -86,7 +88,7 @@ export function useRemoteAgentHistory() {
8688
...prev[agentEndpoint],
8789
histories: {
8890
...prev[agentEndpoint]?.histories,
89-
[projectId]: data.conversations,
91+
[cacheKey]: data.conversations,
9092
},
9193
lastFetch: Date.now(),
9294
}
@@ -161,8 +163,9 @@ export function useRemoteAgentHistory() {
161163
return cache[agentEndpoint]?.projects;
162164
}, [cache]);
163165

164-
const getCachedHistories = useCallback((agentEndpoint: string, projectId: string): ConversationSummary[] | undefined => {
165-
return cache[agentEndpoint]?.histories?.[projectId];
166+
const getCachedHistories = useCallback((agentEndpoint: string, projectId: string, agentId?: string): ConversationSummary[] | undefined => {
167+
const cacheKey = `${projectId}:${agentId || 'all'}`;
168+
return cache[agentEndpoint]?.histories?.[cacheKey];
166169
}, [cache]);
167170

168171
const getCachedConversation = useCallback((

shared/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface ConversationSummary {
4040
lastTime: string;
4141
messageCount: number;
4242
lastMessagePreview: string;
43+
agentId?: string; // Agent that created this conversation
4344
}
4445

4546
export interface HistoryListResponse {
@@ -67,6 +68,7 @@ export interface ConversationHistory {
6768
startTime: string;
6869
endTime: string;
6970
messageCount: number;
71+
agentId?: string; // Agent that created this conversation
7072
};
7173
}
7274

0 commit comments

Comments
 (0)