Skip to content

Commit 008124a

Browse files
authored
feat(tarko-agent-server, agent-ui): return initialization events from session create (#1662)
1 parent 8e01185 commit 008124a

File tree

7 files changed

+126
-82
lines changed

7 files changed

+126
-82
lines changed

multimodal/omni-tars/gui-agent/src/GuiAgentPlugin.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ export class GuiAgentPlugin extends AgentPlugin {
168168
private async emitPresetUserQuey(): Promise<void> {
169169
const eventStream = this.agent.getEventStream();
170170
const events = eventStream.getEvents();
171-
if (events.length === 0) {
171+
// Only emit if no user messages exist yet
172+
const hasUserMessage = events.some(event => event.type === 'user_message');
173+
if (!hasUserMessage) {
172174
const event = eventStream.createEvent('user_message', {
173175
content: `Goto: ${this.agentMode!.link}`,
174176
});
@@ -179,10 +181,13 @@ export class GuiAgentPlugin extends AgentPlugin {
179181
private async emitPresetAssistantMessage(): Promise<void> {
180182
const eventStream = this.agent.getEventStream();
181183
const events = eventStream.getEvents();
182-
guiLogger.debug('emitPresetAssistantMessage events length:', events.length);
183-
const event = eventStream.createEvent('assistant_message', {
184-
content: `Successfully navigated to ${this.agentMode!.link}, page loaded completely`,
185-
});
186-
eventStream.sendEvent(event);
184+
// Only emit if no assistant messages exist yet
185+
const hasAssistantMessage = events.some(event => event.type === 'assistant_message');
186+
if (!hasAssistantMessage) {
187+
const event = eventStream.createEvent('assistant_message', {
188+
content: `Successfully navigated to ${this.agentMode!.link}, page loaded successfully`,
189+
});
190+
eventStream.sendEvent(event);
191+
}
187192
}
188193
}

multimodal/tarko/agent-server/src/api/controllers/sessions.ts

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,8 @@ export async function createSession(req: Request, res: Response) {
6161
}
6262
}
6363

64-
// Pass custom AGIO provider, session metadata, and agent options if available
65-
const session = new AgentSession(
66-
server,
67-
sessionId,
68-
server.getCustomAgioProvider(),
69-
sessionInfo || undefined,
70-
agentOptions, // Pass agentOptions for one-time Agent initialization
71-
);
72-
73-
server.sessions[sessionId] = session;
74-
75-
const { storageUnsubscribe } = await session.initialize();
76-
77-
// Save unsubscribe function for cleanup
78-
if (storageUnsubscribe) {
79-
server.storageUnsubscribes[sessionId] = storageUnsubscribe;
80-
}
81-
8264
let savedSessionInfo: SessionInfo | undefined;
83-
// Store session metadata if we have storage
65+
// Store session metadata FIRST if we have storage
8466
if (server.storageProvider) {
8567
const now = Date.now();
8668

@@ -110,20 +92,59 @@ export async function createSession(req: Request, res: Response) {
11092
};
11193

11294
savedSessionInfo = await server.storageProvider.createSession(sessionInfo);
95+
}
11396

114-
// If runtime settings were provided and session is active, update the agent configuration
115-
if (runtimeSettings && savedSessionInfo) {
116-
try {
117-
await session.updateSessionConfig(savedSessionInfo);
118-
console.log('Session created with runtime settings', { sessionId, runtimeSettings });
119-
} catch (error) {
120-
console.error('Failed to apply runtime settings to new session', { sessionId, error });
121-
// Continue execution - the runtime settings are saved, will apply on next session restart
122-
}
97+
// Pass custom AGIO provider, session metadata, and agent options if available
98+
const session = new AgentSession(
99+
server,
100+
sessionId,
101+
server.getCustomAgioProvider(),
102+
savedSessionInfo || undefined,
103+
agentOptions, // Pass agentOptions for one-time Agent initialization
104+
);
105+
106+
server.sessions[sessionId] = session;
107+
108+
const { storageUnsubscribe } = await session.initialize();
109+
110+
// Save unsubscribe function for cleanup
111+
if (storageUnsubscribe) {
112+
server.storageUnsubscribes[sessionId] = storageUnsubscribe;
113+
}
114+
115+
// If runtime settings were provided and session is active, update the agent configuration
116+
if (runtimeSettings && savedSessionInfo) {
117+
try {
118+
await session.updateSessionConfig(savedSessionInfo);
119+
console.log('Session created with runtime settings', { sessionId, runtimeSettings });
120+
} catch (error) {
121+
console.error('Failed to apply runtime settings to new session', { sessionId, error });
122+
// Continue execution - the runtime settings are saved, will apply on next session restart
123+
}
124+
}
125+
126+
// Wait a short time to ensure all initialization events are persisted
127+
// This handles the async nature of event storage during agent initialization
128+
await session.waitForEventSavesToComplete();
129+
130+
// Get events that were created during agent initialization
131+
let initializationEvents: any[] = [];
132+
if (server.storageProvider) {
133+
try {
134+
initializationEvents = await server.storageProvider.getSessionEvents(sessionId);
135+
} catch (error) {
136+
console.warn('Failed to retrieve initialization events:', error);
137+
// Continue without events - not critical for session creation
123138
}
124139
}
125140

126-
res.status(201).json({ sessionId, session: savedSessionInfo });
141+
console.log('Return initializationEvents', initializationEvents);
142+
143+
res.status(201).json({
144+
sessionId,
145+
session: savedSessionInfo,
146+
events: initializationEvents,
147+
});
127148
} catch (error) {
128149
console.error('Failed to create session:', error);
129150
res.status(500).json({ error: 'Failed to create session' });

multimodal/tarko/agent-server/src/core/AgentSession.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,26 @@ export class AgentSession {
7171
private agioProviderConstructor?: AgioProviderConstructor;
7272
private sessionInfo?: SessionInfo;
7373
private storageUnsubscribeMap = new WeakMap<IAgent, () => void>();
74+
private pendingEventSaves = new Set<Promise<void>>();
7475

7576
/**
7677
* Create event handler for storage and AGIO processing
7778
*/
7879
private createEventHandler() {
7980
return async (event: AgentEventStream.Event) => {
81+
console.log('Receive event', event);
82+
8083
// Save to storage if available and event should be stored
8184
if (this.server.storageProvider && shouldStoreEvent(event)) {
82-
try {
83-
await this.server.storageProvider.saveEvent(this.id, event);
84-
} catch (error) {
85-
console.error(`Failed to save event to storage: ${error}`);
86-
}
85+
const savePromise = this.server.storageProvider.saveEvent(this.id, event)
86+
.catch(error => {
87+
console.error(`Failed to save event to storage: ${error}`);
88+
})
89+
.finally(() => {
90+
this.pendingEventSaves.delete(savePromise);
91+
});
92+
93+
this.pendingEventSaves.add(savePromise);
8794
}
8895

8996
// Process AGIO events if collector is configured
@@ -97,6 +104,16 @@ export class AgentSession {
97104
};
98105
}
99106

107+
/**
108+
* Wait for all pending event saves to complete
109+
* This ensures that all events emitted during initialization are persisted before querying storage
110+
*/
111+
async waitForEventSavesToComplete(): Promise<void> {
112+
if (this.pendingEventSaves.size > 0) {
113+
await Promise.all(Array.from(this.pendingEventSaves));
114+
}
115+
}
116+
100117
/**
101118
* Create and initialize a complete agent instance with all wrappers and configuration
102119
*/

multimodal/tarko/agent-ui/src/common/services/apiService.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class ApiService {
5757
async createSession(
5858
runtimeSettings?: Record<string, any>,
5959
agentOptions?: Record<string, any>,
60-
): Promise<SessionInfo> {
60+
): Promise<{ session: SessionInfo; events: AgentEventStream.Event[] }> {
6161
try {
6262
const response = await fetch(`${API_BASE_URL}${API_ENDPOINTS.CREATE_SESSION}`, {
6363
method: 'POST',
@@ -69,8 +69,8 @@ class ApiService {
6969
throw new Error(`Failed to create session: ${response.statusText}`);
7070
}
7171

72-
const { sessionId, session } = await response.json();
73-
return session as SessionInfo;
72+
const { sessionId, session, events } = await response.json();
73+
return { session: session as SessionInfo, events: events || [] };
7474
} catch (error) {
7575
console.error('Error creating session:', error);
7676
throw error;
@@ -588,30 +588,6 @@ class ApiService {
588588
return { success: false };
589589
}
590590
}
591-
592-
/**
593-
* Delete a session
594-
*/
595-
async deleteSession(sessionId: string): Promise<{ success: boolean }> {
596-
try {
597-
const response = await fetch(`${API_BASE_URL}${API_ENDPOINTS.DELETE_SESSION}`, {
598-
method: 'POST',
599-
headers: {
600-
'Content-Type': 'application/json',
601-
},
602-
body: JSON.stringify({ sessionId }),
603-
});
604-
605-
if (!response.ok) {
606-
throw new Error(`Failed to delete session: ${response.statusText}`);
607-
}
608-
609-
return { success: true };
610-
} catch (error) {
611-
console.error('Error deleting session:', error);
612-
return { success: false };
613-
}
614-
}
615591
}
616592

617593
// Export singleton instance

multimodal/tarko/agent-ui/src/common/state/actions/eventProcessors/handlers/SystemHandler.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { v4 as uuidv4 } from 'uuid';
22
import { EventHandler, EventHandlerContext } from '../types';
33
import { AgentEventStream, Message } from '@/common/types';
44
import { messagesAtom } from '@/common/state/atoms/message';
5-
import { activePanelContentAtom } from '@/common/state/atoms/ui';
5+
import { sessionPanelContentAtom } from '@/common/state/atoms/ui';
66
import { shouldUpdatePanelContent } from '../utils/panelContentUpdater';
77
import { ChatCompletionContentPartImage } from '@tarko/agent-interface';
88

@@ -82,18 +82,25 @@ export class EnvironmentInputHandler
8282
);
8383

8484
if (imageContent && imageContent.image_url) {
85-
const currentPanel = get(activePanelContentAtom);
85+
const currentPanelContent = get(sessionPanelContentAtom);
86+
const sessionMessages = get(messagesAtom)[sessionId] || [];
87+
88+
// Check if this is the first environment_input event in the session
89+
const isFirstEnvironmentInput = sessionMessages.filter(msg => msg.role === 'environment').length === 0;
90+
const currentSessionPanel = currentPanelContent[sessionId];
8691

87-
// Only update if current panel is browser_vision_control to maintain context
88-
if (currentPanel && currentPanel.type === 'browser_vision_control') {
89-
set(activePanelContentAtom, {
90-
...currentPanel,
91-
type: 'browser_vision_control',
92-
title: currentPanel.title,
93-
timestamp: event.timestamp,
94-
originalContent: event.content,
95-
environmentId: event.id,
96-
});
92+
// Always show first environment_input (initialization screenshot) or update existing browser_vision_control panel
93+
if (isFirstEnvironmentInput || (currentSessionPanel && currentSessionPanel.type === 'browser_vision_control')) {
94+
set(sessionPanelContentAtom, (prev) => ({
95+
...prev,
96+
[sessionId]: {
97+
type: 'browser_vision_control',
98+
title: event.description || 'Browser Screenshot',
99+
timestamp: event.timestamp,
100+
originalContent: event.content,
101+
environmentId: event.id,
102+
},
103+
}));
97104
}
98105
// Skip update for other panel types to avoid duplicate Browser Screenshot rendering
99106
}

multimodal/tarko/agent-ui/src/common/state/actions/sessionActions.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const loadSessionsAction = atom(null, async (get, set) => {
8383

8484
export const createSessionAction = atom(null, async (get, set, runtimeSettings?: Record<string, any>, agentOptions?: Record<string, any>) => {
8585
try {
86-
const newSession = await apiService.createSession(runtimeSettings, agentOptions);
86+
const { session: newSession, events: initializationEvents } = await apiService.createSession(runtimeSettings, agentOptions);
8787

8888
set(sessionsAtom, (prev) => [newSession, ...prev]);
8989

@@ -107,6 +107,17 @@ export const createSessionAction = atom(null, async (get, set, runtimeSettings?:
107107
}));
108108
set(activeSessionIdAtom, newSession.id);
109109

110+
// Process initialization events if any were returned
111+
if (initializationEvents && initializationEvents.length > 0) {
112+
console.log(`Processing ${initializationEvents.length} initialization events for session ${newSession.id}`);
113+
114+
const processedEvents = preprocessStreamingEvents(initializationEvents);
115+
116+
for (const event of processedEvents) {
117+
await set(processEventAction, { sessionId: newSession.id, event });
118+
}
119+
}
120+
110121
return newSession.id;
111122
} catch (error) {
112123
console.error('Failed to create session:', error);

multimodal/tarko/agent-ui/src/standalone/home/CreatingPage.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useState, useRef } from 'react';
22
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
33
import { useAtomValue, useSetAtom } from 'jotai';
44
import { useSession } from '@/common/hooks/useSession';
@@ -26,8 +26,15 @@ const CreatingPage: React.FC = () => {
2626
const resetGlobalSettings = useSetAtom(resetGlobalRuntimeSettingsAction);
2727
const createSession = useSetAtom(createSessionAction);
2828
const [isCreating, setIsCreating] = useState(true);
29+
const hasExecuted = useRef(false);
2930

3031
useEffect(() => {
32+
// Prevent double execution
33+
if (hasExecuted.current) {
34+
return;
35+
}
36+
hasExecuted.current = true;
37+
3138
const createSessionWithOptions = async () => {
3239
try {
3340
// Get parameters from multiple sources (priority order):
@@ -105,7 +112,7 @@ const CreatingPage: React.FC = () => {
105112
};
106113

107114
createSessionWithOptions();
108-
}, [location.state, searchParams, globalSettings, resetGlobalSettings, navigate, sendMessage, createSession]);
115+
}, []); // Empty dependency array since we use useRef to prevent double execution
109116

110117
return (
111118
<div className="h-full flex items-center justify-center">

0 commit comments

Comments
 (0)