refactor: implement agent cancellation and deletion with UI controls

Add cancel button during agent runs, real-time mode toggle in all states, and agent deletion via long-press action sheet. Server now handles cancel_agent_request and delete_agent_request messages, cleaning up agent state and notifying clients.
This commit is contained in:
Mohamed Boudra
2025-11-15 18:13:04 +01:00
parent 66b465a132
commit 2a02f41182
6 changed files with 461 additions and 95 deletions
@@ -222,8 +222,10 @@ interface SessionContextValue {
// Helpers
initializeAgent: (params: { agentId: string; requestId?: string }) => void;
refreshAgent: (params: { agentId: string; requestId?: string }) => void;
cancelAgentRun: (agentId: string) => void;
sendAgentMessage: (agentId: string, message: string, imageUris?: string[]) => Promise<void>;
sendAgentAudio: (agentId: string, audioBlob: Blob, requestId?: string) => Promise<void>;
deleteAgent: (agentId: string) => void;
createAgent: (options: { config: AgentSessionConfig; worktreeName?: string; requestId?: string }) => void;
resumeAgent: (options: { handle: AgentPersistenceHandle; overrides?: Partial<AgentSessionConfig>; requestId?: string }) => void;
setAgentMode: (agentId: string, modeId: string) => void;
@@ -712,6 +714,71 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
});
});
const unsubAgentDeleted = ws.on("agent_deleted", (message) => {
if (message.type !== "agent_deleted") {
return;
}
const { agentId } = message.payload;
console.log("[Session] Agent deleted:", agentId);
setAgents((prev) => {
if (!prev.has(agentId)) {
return prev;
}
const next = new Map(prev);
next.delete(agentId);
return next;
});
setAgentStreamState((prev) => {
if (!prev.has(agentId)) {
return prev;
}
const next = new Map(prev);
next.delete(agentId);
return next;
});
setPendingPermissions((prev) => {
let changed = false;
const next = new Map(prev);
for (const [key, pending] of prev.entries()) {
if (pending.agentId === agentId) {
next.delete(key);
changed = true;
}
}
return changed ? next : prev;
});
setInitializingAgents((prev) => {
if (!prev.has(agentId)) {
return prev;
}
const next = new Map(prev);
next.delete(agentId);
return next;
});
setGitDiffs((prev) => {
if (!prev.has(agentId)) {
return prev;
}
const next = new Map(prev);
next.delete(agentId);
return next;
});
setFileExplorer((prev) => {
if (!prev.has(agentId)) {
return prev;
}
const next = new Map(prev);
next.delete(agentId);
return next;
});
});
return () => {
unsubSessionState();
unsubAgentState();
@@ -726,6 +793,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
unsubTranscription();
unsubGitDiff();
unsubFileExplorer();
unsubAgentDeleted();
};
}, [ws, audioPlayer, setIsPlayingAudio, updateExplorerState]);
@@ -777,6 +845,28 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
ws.send(msg);
}, [ws]);
const cancelAgentRun = useCallback((agentId: string) => {
const msg: WSInboundMessage = {
type: "session",
message: {
type: "cancel_agent_request",
agentId,
},
};
ws.send(msg);
}, [ws]);
const deleteAgent = useCallback((agentId: string) => {
const msg: WSInboundMessage = {
type: "session",
message: {
type: "delete_agent_request",
agentId,
},
};
ws.send(msg);
}, [ws]);
const sendAgentMessage = useCallback(async (agentId: string, message: string, imageUris?: string[]) => {
// Generate unique message ID for deduplication
const messageId = generateMessageId();
@@ -1003,6 +1093,8 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
requestFilePreview,
initializeAgent,
refreshAgent,
cancelAgentRun,
deleteAgent,
sendAgentMessage,
sendAgentAudio,
createAgent,