| ... | @@ -288,6 +288,7 @@ import { | ... | @@ -288,6 +288,7 @@ import { |
| 288 | type DraftThreadEnvMode, | 288 | type DraftThreadEnvMode, |
| 289 | finalizePromotedDraftThreadByRef, | 289 | finalizePromotedDraftThreadByRef, |
| 290 | markPromotedDraftThreadByRef, | 290 | markPromotedDraftThreadByRef, |
| | 291 | restoreFailedBackgroundDraftThread, |
| 291 | useComposerDraftStore, | 292 | useComposerDraftStore, |
| 292 | DraftId, | 293 | DraftId, |
| 293 | } from "../composerDraftStore"; | 294 | } from "../composerDraftStore"; |
| ... | @@ -7893,6 +7894,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7893,6 +7894,7 @@ export default function ChatView(props: ChatViewProps) { |
| 7893 | } | 7894 | } |
| 7894 | | 7895 | |
| 7895 | let turnStartSucceeded = false; | 7896 | let turnStartSucceeded = false; |
| | 7897 | let backgroundDraftOpened = false; |
| 7896 | if (failure === null && turnAttachmentsResult._tag === "Success") { | 7898 | if (failure === null && turnAttachmentsResult._tag === "Success") { |
| 7897 | const bootstrap = | 7899 | const bootstrap = |
| 7898 | isLocalDraftThread || baseBranchForWorktree | 7900 | isLocalDraftThread || baseBranchForWorktree |
| ... | @@ -7931,7 +7933,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7931,7 +7933,7 @@ export default function ChatView(props: ChatViewProps) { |
| 7931 | if (backgroundThreadRef) { | 7933 | if (backgroundThreadRef) { |
| 7932 | beginBackgroundDraftSubmissionByRef(backgroundThreadRef); | 7934 | beginBackgroundDraftSubmissionByRef(backgroundThreadRef); |
| 7933 | } | 7935 | } |
| 7934 | const startResult = await startThreadTurn({ | 7936 | const startPromise = startThreadTurn({ |
| 7935 | environmentId, | 7937 | environmentId, |
| 7936 | input: { | 7938 | input: { |
| 7937 | threadId: threadIdForSend, | 7939 | threadId: threadIdForSend, |
| ... | @@ -7975,10 +7977,32 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7975,10 +7977,32 @@ export default function ChatView(props: ChatViewProps) { |
| 7975 | createdAt: messageCreatedAt, | 7977 | createdAt: messageCreatedAt, |
| 7976 | }, | 7978 | }, |
| 7977 | }); | 7979 | }); |
| 7978 | if (startResult._tag === "Failure") { | 7980 | if (backgroundThreadRef) { |
| 7979 | if (backgroundThreadRef) { | 7981 | markPromotedDraftThreadByRef(backgroundThreadRef); |
| | 7982 | try { |
| | 7983 | backgroundDraftOpened = Boolean( |
| | 7984 | await handleNewThread( |
| | 7985 | scopeProjectRef(activeProject.environmentId, activeProject.id), |
| | 7986 | resolveBackgroundDraftWorkspaceOptions({ |
| | 7987 | envMode: sendEnvMode, |
| | 7988 | branch: activeThreadBranch, |
| | 7989 | startFromOrigin, |
| | 7990 | }), |
| | 7991 | ), |
| | 7992 | ); |
| | 7993 | } catch (error) { |
| 7980 | clearBackgroundDraftSubmissionByRef(backgroundThreadRef); | 7994 | clearBackgroundDraftSubmissionByRef(backgroundThreadRef); |
| | 7995 | toastManager.add( |
| | 7996 | stackedThreadToast({ |
| | 7997 | type: "warning", |
| | 7998 | title: "Could not open a fresh composer", |
| | 7999 | description: error instanceof Error ? error.message : undefined, |
| | 8000 | }), |
| | 8001 | ); |
| 7981 | } | 8002 | } |
| | 8003 | } |
| | 8004 | const startResult = await startPromise; |
| | 8005 | if (startResult._tag === "Failure") { |
| 7982 | failure = startResult; | 8006 | failure = startResult; |
| 7983 | } else { | 8007 | } else { |
| 7984 | turnStartSucceeded = true; | 8008 | turnStartSucceeded = true; |
| ... | @@ -7991,48 +8015,26 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -7991,48 +8015,26 @@ export default function ChatView(props: ChatViewProps) { |
| 7991 | } | 8015 | } |
| 7992 | acknowledgeActiveThreadWoke(); | 8016 | acknowledgeActiveThreadWoke(); |
| 7993 | if (backgroundThreadRef) { | 8017 | if (backgroundThreadRef) { |
| 7994 | markPromotedDraftThreadByRef(backgroundThreadRef); | 8018 | if (backgroundDraftOpened || currentRouteThreadKeyRef.current !== routeThreadKey) { |
| 7995 | try { | 8019 | finalizePromotedDraftThreadByRef(backgroundThreadRef); |
| 7996 | const nextDraft = await handleNewThread( | 8020 | } else { |
| 7997 | scopeProjectRef(activeProject.environmentId, activeProject.id), | | |
| 7998 | resolveBackgroundDraftWorkspaceOptions({ | | |
| 7999 | envMode: sendEnvMode, | | |
| 8000 | branch: activeThreadBranch, | | |
| 8001 | startFromOrigin, | | |
| 8002 | }), | | |
| 8003 | ); | | |
| 8004 | if (nextDraft) { | | |
| 8005 | finalizePromotedDraftThreadByRef(backgroundThreadRef); | | |
| 8006 | toastManager.add( | | |
| 8007 | stackedThreadToast({ | | |
| 8008 | type: "success", | | |
| 8009 | title: "Started in background", | | |
| 8010 | timeout: 5_000, | | |
| 8011 | actionProps: { | | |
| 8012 | children: "Open", | | |
| 8013 | onClick: () => { | | |
| 8014 | void navigate({ | | |
| 8015 | to: "/$environmentId/$threadId", | | |
| 8016 | params: buildThreadRouteParams(backgroundThreadRef), | | |
| 8017 | }); | | |
| 8018 | }, | | |
| 8019 | }, | | |
| 8020 | }), | | |
| 8021 | ); | | |
| 8022 | } else { | | |
| 8023 | clearBackgroundDraftSubmissionByRef(backgroundThreadRef); | | |
| 8024 | } | | |
| 8025 | } catch (error) { | | |
| 8026 | clearBackgroundDraftSubmissionByRef(backgroundThreadRef); | 8021 | clearBackgroundDraftSubmissionByRef(backgroundThreadRef); |
| 8027 | resetLocalDispatch(); | 8022 | } |
| | 8023 | if (backgroundDraftOpened) { |
| 8028 | toastManager.add( | 8024 | toastManager.add( |
| 8029 | stackedThreadToast({ | 8025 | stackedThreadToast({ |
| 8030 | type: "warning", | 8026 | type: "success", |
| 8031 | title: "Task started in the background", | 8027 | title: "Started in background", |
| 8032 | description: | 8028 | timeout: 5_000, |
| 8033 | error instanceof Error | 8029 | actionProps: { |
| 8034 | ? `Could not open a fresh composer: ${error.message}` | 8030 | children: "Open", |
| 8035 | : "Could not open a fresh composer.", | 8031 | onClick: () => { |
| | 8032 | void navigate({ |
| | 8033 | to: "/$environmentId/$threadId", |
| | 8034 | params: buildThreadRouteParams(backgroundThreadRef), |
| | 8035 | }); |
| | 8036 | }, |
| | 8037 | }, |
| 8036 | }), | 8038 | }), |
| 8037 | ); | 8039 | ); |
| 8038 | } | 8040 | } |
| ... | @@ -8041,6 +8043,16 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -8041,6 +8043,16 @@ export default function ChatView(props: ChatViewProps) { |
| 8041 | } | 8043 | } |
| 8042 | | 8044 | |
| 8043 | if (failure !== null) { | 8045 | if (failure !== null) { |
| | 8046 | if (resolvedSubmissionIntent === "background" && draftId && draftThread) { |
| | 8047 | restoreFailedBackgroundDraftThread( |
| | 8048 | draftId, |
| | 8049 | draftThread, |
| | 8050 | wasBootstrapThreadDeleted(squashAtomCommandFailure(failure)) |
| | 8051 | ? newThreadId() |
| | 8052 | : threadIdForSend, |
| | 8053 | ); |
| | 8054 | clearBackgroundDraftSubmissionByRef(scopeThreadRef(environmentId, threadIdForSend)); |
| | 8055 | } |
| 8044 | if (queuedMessage) { | 8056 | if (queuedMessage) { |
| 8045 | setOptimisticUserMessages((existing) => { | 8057 | setOptimisticUserMessages((existing) => { |
| 8046 | const removed = existing.filter((message) => message.id === messageIdForSend); | 8058 | const removed = existing.filter((message) => message.id === messageIdForSend); |
| ... | @@ -8059,14 +8071,18 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -8059,14 +8071,18 @@ export default function ChatView(props: ChatViewProps) { |
| 8059 | }); | 8071 | }); |
| 8060 | } | 8072 | } |
| 8061 | } else if ( | 8073 | } else if ( |
| 8062 | promptRef.current.length === 0 && | 8074 | backgroundDraftOpened |
| 8063 | composerImagesRef.current.length === 0 && | 8075 | ? !composerDraftHasUserContent( |
| 8064 | composerFilesRef.current.length === 0 && | 8076 | useComposerDraftStore.getState().getComposerDraft(composerDraftTarget), |
| 8065 | composerTerminalContextsRef.current.length === 0 && | 8077 | ) |
| 8066 | (useComposerDraftStore.getState().getComposerDraft(composerDraftTarget)?.previewAnnotations | 8078 | : promptRef.current.length === 0 && |
| 8067 | .length ?? 0) === 0 && | 8079 | composerImagesRef.current.length === 0 && |
| 8068 | (useComposerDraftStore.getState().getComposerDraft(composerDraftTarget)?.reviewComments | 8080 | composerFilesRef.current.length === 0 && |
| 8069 | .length ?? 0) === 0 | 8081 | composerTerminalContextsRef.current.length === 0 && |
| | 8082 | (useComposerDraftStore.getState().getComposerDraft(composerDraftTarget) |
| | 8083 | ?.previewAnnotations.length ?? 0) === 0 && |
| | 8084 | (useComposerDraftStore.getState().getComposerDraft(composerDraftTarget)?.reviewComments |
| | 8085 | .length ?? 0) === 0 |
| 8070 | ) { | 8086 | ) { |
| 8071 | setOptimisticUserMessages((existing) => { | 8087 | setOptimisticUserMessages((existing) => { |
| 8072 | const removed = existing.filter((message) => message.id === messageIdForSend); | 8088 | const removed = existing.filter((message) => message.id === messageIdForSend); |
| ... | @@ -8095,7 +8111,12 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -8095,7 +8111,12 @@ export default function ChatView(props: ChatViewProps) { |
| 8095 | } | 8111 | } |
| 8096 | if (!isAtomCommandInterrupted(failure)) { | 8112 | if (!isAtomCommandInterrupted(failure)) { |
| 8097 | const error = squashAtomCommandFailure(failure); | 8113 | const error = squashAtomCommandFailure(failure); |
| 8098 | if (isLocalDraftThread && draftId && wasBootstrapThreadDeleted(error)) { | 8114 | if ( |
| | 8115 | resolvedSubmissionIntent !== "background" && |
| | 8116 | isLocalDraftThread && |
| | 8117 | draftId && |
| | 8118 | wasBootstrapThreadDeleted(error) |
| | 8119 | ) { |
| 8099 | const failedDraftSession = getDraftSession(draftId); | 8120 | const failedDraftSession = getDraftSession(draftId); |
| 8100 | if (failedDraftSession?.threadId === threadIdForSend) { | 8121 | if (failedDraftSession?.threadId === threadIdForSend) { |
| 8101 | setLogicalProjectDraftThreadId( | 8122 | setLogicalProjectDraftThreadId( |
| ... | @@ -8113,6 +8134,21 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -8113,6 +8134,21 @@ export default function ChatView(props: ChatViewProps) { |
| 8113 | threadIdForSend, | 8134 | threadIdForSend, |
| 8114 | error instanceof Error ? error.message : "Failed to send message.", | 8135 | error instanceof Error ? error.message : "Failed to send message.", |
| 8115 | ); | 8136 | ); |
| | 8137 | if (backgroundDraftOpened && draftId) { |
| | 8138 | toastManager.add( |
| | 8139 | stackedThreadToast({ |
| | 8140 | type: "error", |
| | 8141 | title: "Background task failed", |
| | 8142 | description: error instanceof Error ? error.message : "Failed to send message.", |
| | 8143 | actionProps: { |
| | 8144 | children: "Open draft", |
| | 8145 | onClick: () => { |
| | 8146 | void navigate({ to: "/draft/$draftId", params: { draftId } }); |
| | 8147 | }, |
| | 8148 | }, |
| | 8149 | }), |
| | 8150 | ); |
| | 8151 | } |
| 8116 | } | 8152 | } |
| 8117 | } | 8153 | } |
| 8118 | sendInFlightRef.current = false; | 8154 | sendInFlightRef.current = false; |