Merge branch 'feature/mobile-app'

This commit is contained in:
Mohamed Boudra
2025-10-21 18:15:41 +02:00
51 changed files with 23579 additions and 769 deletions
+4 -4
View File
@@ -41,15 +41,15 @@
"@playwright/test": "^1.56.1",
"@types/express": "^4.17.20",
"@types/node": "^20.9.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@types/uuid": "^9.0.7",
"@types/ws": "^8.5.8",
"@vitejs/plugin-react": "^4.2.0",
"@vitest/ui": "^3.2.4",
"concurrently": "^8.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tsx": "^4.6.0",
"typescript": "^5.2.2",
"vite": "^7.1.10",
@@ -30,12 +30,23 @@ export const LoadConversationRequestMessageSchema = z.object({
conversationId: z.string(),
});
export const ListConversationsRequestMessageSchema = z.object({
type: z.literal("list_conversations_request"),
});
export const DeleteConversationRequestMessageSchema = z.object({
type: z.literal("delete_conversation_request"),
conversationId: z.string(),
});
export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
UserTextMessageSchema,
AudioChunkMessageSchema,
AbortRequestMessageSchema,
AudioPlayedMessageSchema,
LoadConversationRequestMessageSchema,
ListConversationsRequestMessageSchema,
DeleteConversationRequestMessageSchema,
]);
export type SessionInboundMessage = z.infer<typeof SessionInboundMessageSchema>;
@@ -189,6 +200,28 @@ export const SessionStateMessageSchema = z.object({
}),
});
export const ListConversationsResponseMessageSchema = z.object({
type: z.literal("list_conversations_response"),
payload: z.object({
conversations: z.array(
z.object({
id: z.string(),
lastUpdated: z.string(),
messageCount: z.number(),
})
),
}),
});
export const DeleteConversationResponseMessageSchema = z.object({
type: z.literal("delete_conversation_response"),
payload: z.object({
conversationId: z.string(),
success: z.boolean(),
error: z.string().optional(),
}),
});
export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
ActivityLogMessageSchema,
AssistantChunkMessageSchema,
@@ -201,6 +234,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
AgentUpdateMessageSchema,
AgentStatusMessageSchema,
SessionStateMessageSchema,
ListConversationsResponseMessageSchema,
DeleteConversationResponseMessageSchema,
]);
export type SessionOutboundMessage = z.infer<
@@ -219,6 +254,8 @@ export type AgentCreatedMessage = z.infer<typeof AgentCreatedMessageSchema>;
export type AgentUpdateMessage = z.infer<typeof AgentUpdateMessageSchema>;
export type AgentStatusMessage = z.infer<typeof AgentStatusMessageSchema>;
export type SessionStateMessage = z.infer<typeof SessionStateMessageSchema>;
export type ListConversationsResponseMessage = z.infer<typeof ListConversationsResponseMessageSchema>;
export type DeleteConversationResponseMessage = z.infer<typeof DeleteConversationResponseMessageSchema>;
// Type exports for payload types
export type ActivityLogPayload = z.infer<typeof ActivityLogPayloadSchema>;
+74 -1
View File
@@ -15,7 +15,7 @@ import { getSystemPrompt } from "./agent/system-prompt.js";
import { getAllTools } from "./agent/llm-openai.js";
import { TTSManager } from "./agent/tts-manager.js";
import { STTManager } from "./agent/stt-manager.js";
import { saveConversation } from "./persistence.js";
import { saveConversation, listConversations, deleteConversation } from "./persistence.js";
import { experimental_createMCPClient } from "ai";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createTerminalMcpServer } from "./terminal-mcp/index.js";
@@ -257,6 +257,14 @@ export class Session {
case "load_conversation_request":
await this.handleLoadConversation();
break;
case "list_conversations_request":
await this.handleListConversations();
break;
case "delete_conversation_request":
await this.handleDeleteConversation(msg.conversationId);
break;
}
} catch (error: any) {
console.error(
@@ -292,6 +300,71 @@ export class Session {
await this.sendSessionState();
}
/**
* List all conversations
*/
public async handleListConversations(): Promise<void> {
try {
const conversations = await listConversations();
this.emit({
type: "list_conversations_response",
payload: {
conversations: conversations.map(conv => ({
id: conv.id,
lastUpdated: conv.lastUpdated.toISOString(),
messageCount: conv.messageCount,
})),
},
});
} catch (error: any) {
console.error(
`[Session ${this.clientId}] Failed to list conversations:`,
error
);
this.emit({
type: "activity_log",
payload: {
id: uuidv4(),
timestamp: new Date(),
type: "error",
content: `Failed to list conversations: ${error.message}`,
},
});
}
}
/**
* Delete a conversation
*/
public async handleDeleteConversation(conversationId: string): Promise<void> {
try {
await deleteConversation(conversationId);
this.emit({
type: "delete_conversation_response",
payload: {
conversationId,
success: true,
},
});
console.log(
`[Session ${this.clientId}] Deleted conversation ${conversationId}`
);
} catch (error: any) {
console.error(
`[Session ${this.clientId}] Failed to delete conversation ${conversationId}:`,
error
);
this.emit({
type: "delete_conversation_response",
payload: {
conversationId,
success: false,
error: error.message,
},
});
}
}
/**
* Send current session state (live agents and commands) to client
*/
@@ -19,7 +19,7 @@ export function useWebSocket(url: string, conversationId?: string | null): UseWe
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const handlersRef = useRef<Map<SessionOutboundMessage['type'], Set<(message: SessionOutboundMessage) => void>>>(new Map());
const reconnectTimeoutRef = useRef<number>();
const reconnectTimeoutRef = useRef<number | undefined>(undefined);
const connect = useCallback(() => {
if (wsRef.current?.readyState === WebSocket.OPEN) {