From a4556a470779d2eda3f258ea8260a3e505cdb4eb Mon Sep 17 00:00:00 2001 From: Matt Pua Date: Tue, 16 Jun 2026 11:48:54 -0400 Subject: [PATCH] fix(sessions): keep queued messages in queue when cancelling Stop now cancels only the in-flight turn. Queued messages stay in the queue (shown as queued cards) instead of being dumped into the input. A queuePaused flag suppresses the auto-drain triggered by the cancel-induced turn-end event; it's cleared on the next user send, so the kept messages resume then. Generated-By: PostHog Code Task-Id: 82344ce6-2b72-4bb3-b929-41cd17820f5e --- packages/core/src/sessions/sessionService.ts | 10 ++++-- packages/shared/src/sessions.ts | 7 ++++ .../sessions/hooks/useSessionCallbacks.ts | 33 ++----------------- 3 files changed, 18 insertions(+), 32 deletions(-) diff --git a/packages/core/src/sessions/sessionService.ts b/packages/core/src/sessions/sessionService.ts index 0eb24e96dc..56c241bd65 100644 --- a/packages/core/src/sessions/sessionService.ts +++ b/packages/core/src/sessions/sessionService.ts @@ -1533,7 +1533,8 @@ export class SessionService { const hasQueuedMessages = freshSession && freshSession.messageQueue.length > 0 && - freshSession.status === "connected"; + freshSession.status === "connected" && + !freshSession.queuePaused; if (hasQueuedMessages) { setTimeout(() => { @@ -1667,6 +1668,10 @@ export class SessionService { ); } + if (session.queuePaused) { + this.d.store.updateSession(session.taskRunId, { queuePaused: false }); + } + if (session.isCloud) { return this.sendCloudPrompt(session, prompt); } @@ -1909,6 +1914,7 @@ export class SessionService { this.d.store.updateSession(session.taskRunId, { isPromptPending: false, promptStartedAt: null, + queuePaused: true, }); if (session.isCloud) { @@ -2162,7 +2168,7 @@ export class SessionService { isTerminal || (session.cloudStatus === "in_progress" && session.status === "connected"); - if (!canSendNow || session.isPromptPending) return; + if (!canSendNow || session.isPromptPending || session.queuePaused) return; const drained = this.d.store.dequeueMessages(taskId); const combined = this.d.h.combineQueuedCloudPrompts(drained); diff --git a/packages/shared/src/sessions.ts b/packages/shared/src/sessions.ts index 1c7663ba6b..94eda72497 100644 --- a/packages/shared/src/sessions.ts +++ b/packages/shared/src/sessions.ts @@ -68,6 +68,13 @@ export interface AgentSession { pendingPermissions: Map; pausedDurationMs: number; messageQueue: QueuedMessage[]; + /** + * Set when the user cancels an in-flight prompt. Keeps queued messages in + * `messageQueue` but suppresses the auto-drain that a turn-end event would + * otherwise trigger, so a Stop doesn't immediately fire the next queued + * message. Cleared on the next user-initiated send (see `sendPrompt`). + */ + queuePaused?: boolean; isCloud?: boolean; cloudStatus?: TaskRunStatus; cloudStage?: string | null; diff --git a/packages/ui/src/features/sessions/hooks/useSessionCallbacks.ts b/packages/ui/src/features/sessions/hooks/useSessionCallbacks.ts index 5678edca4c..482750782d 100644 --- a/packages/ui/src/features/sessions/hooks/useSessionCallbacks.ts +++ b/packages/ui/src/features/sessions/hooks/useSessionCallbacks.ts @@ -1,7 +1,3 @@ -import { - combineQueuedCloudPrompts, - promptToQueuedEditorContent, -} from "@posthog/core/sessions/cloudPrompt"; import { SESSION_SERVICE, type SessionService, @@ -10,10 +6,7 @@ import { useService } from "@posthog/di/react"; import type { Task } from "@posthog/shared/domain-types"; import { tryExecuteCodeCommand } from "@posthog/ui/features/message-editor/commands"; import { useDraftStore } from "@posthog/ui/features/message-editor/draftStore"; -import { - type AgentSession, - sessionStoreSetters, -} from "@posthog/ui/features/sessions/sessionStore"; +import type { AgentSession } from "@posthog/ui/features/sessions/sessionStore"; import { useTaskViewed } from "@posthog/ui/features/sidebar/useTaskViewed"; import { SHELL_CLIENT, @@ -42,7 +35,7 @@ export function useSessionCallbacks({ const sessionService = useService(SESSION_SERVICE); const shellClient = useService(SHELL_CLIENT); const { markActivity, markAsViewed } = useTaskViewed(); - const { requestFocus, setPendingContent } = useDraftStore((s) => s.actions); + const { requestFocus } = useDraftStore((s) => s.actions); const sessionRef = useRef(session); sessionRef.current = session; @@ -94,30 +87,10 @@ export function useSessionCallbacks({ ); const handleCancelPrompt = useCallback(async () => { - const queuedMessages = sessionStoreSetters.dequeueMessages(taskId); const result = await sessionService.cancelPrompt(taskId); log.info("Prompt cancelled", { success: result }); - - const queuedPrompt = sessionRef.current?.isCloud - ? combineQueuedCloudPrompts(queuedMessages) - : queuedMessages.map((message) => message.content).join("\n\n"); - - if (queuedPrompt) { - const pendingContent = sessionRef.current?.isCloud - ? promptToQueuedEditorContent(queuedPrompt) - : { - segments: [ - { - type: "text" as const, - text: typeof queuedPrompt === "string" ? queuedPrompt : "", - }, - ], - }; - - setPendingContent(taskId, pendingContent); - } requestFocus(taskId); - }, [taskId, setPendingContent, requestFocus, sessionService]); + }, [taskId, requestFocus, sessionService]); const handleRetry = useCallback(async () => { try {