feat(Agnet): support multi agents (#385)
This commit is contained in:
@@ -87,6 +87,7 @@ interface ChatState {
|
||||
// Sessions
|
||||
sessions: ChatSession[];
|
||||
currentSessionKey: string;
|
||||
currentAgentId: string;
|
||||
/** First user message text per session key, used as display label */
|
||||
sessionLabels: Record<string, string>;
|
||||
/** Last message timestamp (ms) per session key, used for sorting */
|
||||
@@ -657,6 +658,19 @@ function getCanonicalPrefixFromSessions(sessions: ChatSession[]): string | null
|
||||
return `${parts[0]}:${parts[1]}`;
|
||||
}
|
||||
|
||||
function getAgentIdFromSessionKey(sessionKey: string): string {
|
||||
if (!sessionKey.startsWith('agent:')) return 'main';
|
||||
const parts = sessionKey.split(':');
|
||||
return parts[1] || 'main';
|
||||
}
|
||||
|
||||
function getCanonicalPrefixFromSessionKey(sessionKey: string): string | null {
|
||||
if (!sessionKey.startsWith('agent:')) return null;
|
||||
const parts = sessionKey.split(':');
|
||||
if (parts.length < 2) return null;
|
||||
return `${parts[0]}:${parts[1]}`;
|
||||
}
|
||||
|
||||
function isToolOnlyMessage(message: RawMessage | undefined): boolean {
|
||||
if (!message) return false;
|
||||
if (isToolResultRole(message.role)) return true;
|
||||
@@ -923,6 +937,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
|
||||
sessions: [],
|
||||
currentSessionKey: DEFAULT_SESSION_KEY,
|
||||
currentAgentId: 'main',
|
||||
sessionLabels: {},
|
||||
sessionLastActivity: {},
|
||||
|
||||
@@ -964,7 +979,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
return true;
|
||||
});
|
||||
|
||||
const { currentSessionKey } = get();
|
||||
const { currentSessionKey, sessions: localSessions } = get();
|
||||
let nextSessionKey = currentSessionKey || DEFAULT_SESSION_KEY;
|
||||
if (!nextSessionKey.startsWith('agent:')) {
|
||||
const canonicalMatch = canonicalBySuffix.get(nextSessionKey);
|
||||
@@ -973,9 +988,10 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
}
|
||||
}
|
||||
if (!dedupedSessions.find((s) => s.key === nextSessionKey) && dedupedSessions.length > 0) {
|
||||
// Current session not found in the backend list
|
||||
const isNewEmptySession = get().messages.length === 0;
|
||||
if (!isNewEmptySession) {
|
||||
// Preserve only locally-created pending sessions. On initial boot the
|
||||
// default ghost key (`agent:main:main`) should yield to real history.
|
||||
const hasLocalPendingSession = localSessions.some((session) => session.key === nextSessionKey);
|
||||
if (!hasLocalPendingSession) {
|
||||
nextSessionKey = dedupedSessions[0].key;
|
||||
}
|
||||
}
|
||||
@@ -987,7 +1003,11 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
]
|
||||
: dedupedSessions;
|
||||
|
||||
set({ sessions: sessionsWithCurrent, currentSessionKey: nextSessionKey });
|
||||
set({
|
||||
sessions: sessionsWithCurrent,
|
||||
currentSessionKey: nextSessionKey,
|
||||
currentAgentId: getAgentIdFromSessionKey(nextSessionKey),
|
||||
});
|
||||
|
||||
if (currentSessionKey !== nextSessionKey) {
|
||||
get().loadHistory();
|
||||
@@ -1038,6 +1058,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
const leavingEmpty = !currentSessionKey.endsWith(':main') && messages.length === 0;
|
||||
set((s) => ({
|
||||
currentSessionKey: key,
|
||||
currentAgentId: getAgentIdFromSessionKey(key),
|
||||
messages: [],
|
||||
streamingText: '',
|
||||
streamingMessage: null,
|
||||
@@ -1108,6 +1129,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
lastUserMessageAt: null,
|
||||
pendingToolImages: [],
|
||||
currentSessionKey: next?.key ?? DEFAULT_SESSION_KEY,
|
||||
currentAgentId: getAgentIdFromSessionKey(next?.key ?? DEFAULT_SESSION_KEY),
|
||||
}));
|
||||
if (next) {
|
||||
get().loadHistory();
|
||||
@@ -1128,13 +1150,16 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
// NOTE: We intentionally do NOT call sessions.reset on the old session.
|
||||
// sessions.reset archives (renames) the session JSONL file, making old
|
||||
// conversation history inaccessible when the user switches back to it.
|
||||
const { currentSessionKey, messages } = get();
|
||||
const { currentSessionKey, messages, sessions } = get();
|
||||
const leavingEmpty = !currentSessionKey.endsWith(':main') && messages.length === 0;
|
||||
const prefix = getCanonicalPrefixFromSessions(get().sessions) ?? DEFAULT_CANONICAL_PREFIX;
|
||||
const prefix = getCanonicalPrefixFromSessionKey(currentSessionKey)
|
||||
?? getCanonicalPrefixFromSessions(sessions)
|
||||
?? DEFAULT_CANONICAL_PREFIX;
|
||||
const newKey = `${prefix}:session-${Date.now()}`;
|
||||
const newSessionEntry: ChatSession = { key: newKey, displayName: newKey };
|
||||
set((s) => ({
|
||||
currentSessionKey: newKey,
|
||||
currentAgentId: getAgentIdFromSessionKey(newKey),
|
||||
sessions: [
|
||||
...(leavingEmpty ? s.sessions.filter((sess) => sess.key !== currentSessionKey) : s.sessions),
|
||||
newSessionEntry,
|
||||
|
||||
Reference in New Issue
Block a user