feat(agents): support chat to agent (#403)
This commit is contained in:
@@ -18,6 +18,7 @@ export const initialChatState: Pick<
|
||||
| 'pendingToolImages'
|
||||
| 'sessions'
|
||||
| 'currentSessionKey'
|
||||
| 'currentAgentId'
|
||||
| 'sessionLabels'
|
||||
| 'sessionLastActivity'
|
||||
| 'showThinking'
|
||||
@@ -38,6 +39,7 @@ export const initialChatState: Pick<
|
||||
|
||||
sessions: [],
|
||||
currentSessionKey: DEFAULT_SESSION_KEY,
|
||||
currentAgentId: 'main',
|
||||
sessionLabels: {},
|
||||
sessionLastActivity: {},
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { invokeIpc } from '@/lib/api-client';
|
||||
import { useAgentsStore } from '@/stores/agents';
|
||||
import {
|
||||
clearErrorRecoveryTimer,
|
||||
clearHistoryPoll,
|
||||
@@ -7,16 +8,78 @@ import {
|
||||
setLastChatEventAt,
|
||||
upsertImageCacheEntry,
|
||||
} from './helpers';
|
||||
import type { RawMessage } from './types';
|
||||
import type { ChatSession, RawMessage } from './types';
|
||||
import type { ChatGet, ChatSet, RuntimeActions } from './store-api';
|
||||
|
||||
function normalizeAgentId(value: string | undefined | null): string {
|
||||
return (value ?? '').trim().toLowerCase() || 'main';
|
||||
}
|
||||
|
||||
function getAgentIdFromSessionKey(sessionKey: string): string {
|
||||
if (!sessionKey.startsWith('agent:')) return 'main';
|
||||
const [, agentId] = sessionKey.split(':');
|
||||
return agentId || 'main';
|
||||
}
|
||||
|
||||
function buildFallbackMainSessionKey(agentId: string): string {
|
||||
return `agent:${normalizeAgentId(agentId)}:main`;
|
||||
}
|
||||
|
||||
function resolveMainSessionKeyForAgent(agentId: string | undefined | null): string | null {
|
||||
if (!agentId) return null;
|
||||
const normalizedAgentId = normalizeAgentId(agentId);
|
||||
const summary = useAgentsStore.getState().agents.find((agent) => agent.id === normalizedAgentId);
|
||||
return summary?.mainSessionKey || buildFallbackMainSessionKey(normalizedAgentId);
|
||||
}
|
||||
|
||||
function ensureSessionEntry(sessions: ChatSession[], sessionKey: string): ChatSession[] {
|
||||
if (sessions.some((session) => session.key === sessionKey)) {
|
||||
return sessions;
|
||||
}
|
||||
return [...sessions, { key: sessionKey, displayName: sessionKey }];
|
||||
}
|
||||
|
||||
export function createRuntimeSendActions(set: ChatSet, get: ChatGet): Pick<RuntimeActions, 'sendMessage' | 'abortRun'> {
|
||||
return {
|
||||
sendMessage: async (text: string, attachments?: Array<{ fileName: string; mimeType: string; fileSize: number; stagedPath: string; preview: string | null }>) => {
|
||||
sendMessage: async (
|
||||
text: string,
|
||||
attachments?: Array<{ fileName: string; mimeType: string; fileSize: number; stagedPath: string; preview: string | null }>,
|
||||
targetAgentId?: string | null,
|
||||
) => {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed && (!attachments || attachments.length === 0)) return;
|
||||
|
||||
const { currentSessionKey } = get();
|
||||
const targetSessionKey = resolveMainSessionKeyForAgent(targetAgentId) ?? get().currentSessionKey;
|
||||
if (targetSessionKey !== get().currentSessionKey) {
|
||||
const current = get();
|
||||
const leavingEmpty = !current.currentSessionKey.endsWith(':main') && current.messages.length === 0;
|
||||
set((s) => ({
|
||||
currentSessionKey: targetSessionKey,
|
||||
currentAgentId: getAgentIdFromSessionKey(targetSessionKey),
|
||||
sessions: ensureSessionEntry(
|
||||
leavingEmpty ? s.sessions.filter((session) => session.key !== current.currentSessionKey) : s.sessions,
|
||||
targetSessionKey,
|
||||
),
|
||||
sessionLabels: leavingEmpty
|
||||
? Object.fromEntries(Object.entries(s.sessionLabels).filter(([key]) => key !== current.currentSessionKey))
|
||||
: s.sessionLabels,
|
||||
sessionLastActivity: leavingEmpty
|
||||
? Object.fromEntries(Object.entries(s.sessionLastActivity).filter(([key]) => key !== current.currentSessionKey))
|
||||
: s.sessionLastActivity,
|
||||
messages: [],
|
||||
streamingText: '',
|
||||
streamingMessage: null,
|
||||
streamingTools: [],
|
||||
activeRunId: null,
|
||||
error: null,
|
||||
pendingFinal: false,
|
||||
lastUserMessageAt: null,
|
||||
pendingToolImages: [],
|
||||
}));
|
||||
await get().loadHistory(true);
|
||||
}
|
||||
|
||||
const currentSessionKey = targetSessionKey;
|
||||
|
||||
// Add user message optimistically (with local file metadata for UI display)
|
||||
const nowMs = Date.now();
|
||||
|
||||
@@ -3,6 +3,12 @@ import { getCanonicalPrefixFromSessions, getMessageText, toMs } from './helpers'
|
||||
import { DEFAULT_CANONICAL_PREFIX, DEFAULT_SESSION_KEY, type ChatSession, type RawMessage } from './types';
|
||||
import type { ChatGet, ChatSet, SessionHistoryActions } from './store-api';
|
||||
|
||||
function getAgentIdFromSessionKey(sessionKey: string): string {
|
||||
if (!sessionKey.startsWith('agent:')) return 'main';
|
||||
const [, agentId] = sessionKey.split(':');
|
||||
return agentId || 'main';
|
||||
}
|
||||
|
||||
export function createSessionActions(
|
||||
set: ChatSet,
|
||||
get: ChatGet,
|
||||
@@ -70,7 +76,11 @@ export function createSessionActions(
|
||||
]
|
||||
: dedupedSessions;
|
||||
|
||||
set({ sessions: sessionsWithCurrent, currentSessionKey: nextSessionKey });
|
||||
set({
|
||||
sessions: sessionsWithCurrent,
|
||||
currentSessionKey: nextSessionKey,
|
||||
currentAgentId: getAgentIdFromSessionKey(nextSessionKey),
|
||||
});
|
||||
|
||||
if (currentSessionKey !== nextSessionKey) {
|
||||
get().loadHistory();
|
||||
@@ -123,6 +133,7 @@ export function createSessionActions(
|
||||
const leavingEmpty = !currentSessionKey.endsWith(':main') && messages.length === 0;
|
||||
set((s) => ({
|
||||
currentSessionKey: key,
|
||||
currentAgentId: getAgentIdFromSessionKey(key),
|
||||
messages: [],
|
||||
streamingText: '',
|
||||
streamingMessage: null,
|
||||
@@ -190,6 +201,7 @@ export function createSessionActions(
|
||||
lastUserMessageAt: null,
|
||||
pendingToolImages: [],
|
||||
currentSessionKey: next?.key ?? DEFAULT_SESSION_KEY,
|
||||
currentAgentId: getAgentIdFromSessionKey(next?.key ?? DEFAULT_SESSION_KEY),
|
||||
}));
|
||||
if (next) {
|
||||
get().loadHistory();
|
||||
@@ -217,6 +229,7 @@ export function createSessionActions(
|
||||
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,
|
||||
|
||||
@@ -76,6 +76,7 @@ export 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 */
|
||||
@@ -100,7 +101,8 @@ export interface ChatState {
|
||||
fileSize: number;
|
||||
stagedPath: string;
|
||||
preview: string | null;
|
||||
}>
|
||||
}>,
|
||||
targetAgentId?: string | null,
|
||||
) => Promise<void>;
|
||||
abortRun: () => Promise<void>;
|
||||
handleChatEvent: (event: Record<string, unknown>) => void;
|
||||
|
||||
Reference in New Issue
Block a user