Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ function ModeAndBranchRow({
interface MessageEditorProps {
sessionId: string;
placeholder?: string;
onBeforeSubmit?: (text: string, clearEditor: () => void) => boolean;
onSubmit?: (text: string) => void;
onBashCommand?: (command: string) => void;
onBashModeChange?: (isBashMode: boolean) => void;
Expand All @@ -154,6 +155,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
{
sessionId,
placeholder = "Type a message... @ to mention files, ! for bash mode, / for skills",
onBeforeSubmit,
onSubmit,
onBashCommand,
onBashModeChange,
Expand Down Expand Up @@ -213,6 +215,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
context: { taskId, repoPath },
getPromptHistory,
capabilities: { bashMode: !isCloud },
onBeforeSubmit,
onSubmit,
onBashCommand,
onBashModeChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface UseTiptapEditorOptions {
};
clearOnSubmit?: boolean;
getPromptHistory?: () => string[];
onBeforeSubmit?: (text: string, clearEditor: () => void) => boolean;
onSubmit?: (text: string) => void;
onBashCommand?: (command: string) => void;
onBashModeChange?: (isBashMode: boolean) => void;
Expand Down Expand Up @@ -84,6 +85,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
capabilities = {},
clearOnSubmit = true,
getPromptHistory,
onBeforeSubmit,
onSubmit,
onBashCommand,
onBashModeChange,
Expand All @@ -99,6 +101,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
} = capabilities;

const callbackRefs = useRef({
onBeforeSubmit,
onSubmit,
onBashCommand,
onBashModeChange,
Expand All @@ -107,6 +110,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
onBlur,
});
callbackRefs.current = {
onBeforeSubmit,
onSubmit,
onBashCommand,
onBashModeChange,
Expand Down Expand Up @@ -450,26 +454,39 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {

const text = editor.getText().trim();

const doClear = () => {
if (!clearOnSubmit) return;
editor.commands.clearContent();
prevBashModeRef.current = false;
pasteCountRef.current = 0;
setAttachments([]);
draft.clearDraft();
};

if (enableBashMode && text.startsWith("!")) {
// Bash mode requires immediate execution, can't be queued
// Bash mode requires immediate execution, can't be queued.
// Intentionally bypasses onBeforeSubmit — bash commands run inline and
// cannot be deferred the way normal prompts can.
if (isLoading) {
toast.error("Cannot run shell commands while agent is generating");
return;
}
const command = text.slice(1).trim();
if (command) callbackRefs.current.onBashCommand?.(command);
} else {
const serialized = contentToXml(content);

if (callbackRefs.current.onBeforeSubmit) {
if (!callbackRefs.current.onBeforeSubmit(serialized, doClear)) {
return;
}
}

// Normal prompts can be queued when loading
callbackRefs.current.onSubmit?.(contentToXml(content));
callbackRefs.current.onSubmit?.(serialized);
}

if (clearOnSubmit) {
editor.commands.clearContent();
prevBashModeRef.current = false;
pasteCountRef.current = 0;
setAttachments([]);
draft.clearDraft();
}
doClear();
}, [
editor,
disabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface SessionViewProps {
isRunning: boolean;
isPromptPending?: boolean | null;
promptStartedAt?: number | null;
onBeforeSubmit?: (text: string, clearEditor: () => void) => boolean;
onSendPrompt: (text: string) => void;
onBashCommand?: (command: string) => void;
onCancelPrompt: () => void;
Expand Down Expand Up @@ -73,6 +74,7 @@ export function SessionView({
isRunning,
isPromptPending = false,
promptStartedAt,
onBeforeSubmit,
onSendPrompt,
onBashCommand,
onCancelPrompt,
Expand Down Expand Up @@ -538,6 +540,7 @@ export function SessionView({
ref={editorRef}
sessionId={sessionId}
placeholder="Type a message... @ to mention files, ! for bash mode, / for skills"
onBeforeSubmit={onBeforeSubmit}
onSubmit={handleSubmit}
onBashCommand={onBashCommand}
onCancel={onCancelPrompt}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { GitBranch, Warning } from "@phosphor-icons/react";
import {
AlertDialog,
Button,
Callout,
Code,
Flex,
Text,
} from "@radix-ui/themes";

interface BranchMismatchDialogProps {
open: boolean;
linkedBranch: string;
currentBranch: string;
hasUncommittedChanges: boolean;
switchError: string | null;
onSwitch: () => void;
onContinue: () => void;
onCancel: () => void;
isSwitching?: boolean;
}

function BranchLabel({ name }: { name: string }) {
return (
<Code
size="2"
variant="ghost"
truncate
style={{
maxWidth: "100%",
display: "inline-flex",
alignItems: "center",
gap: "4px",
}}
>
<GitBranch size={12} style={{ flexShrink: 0 }} />
<span
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{name}
</span>
</Code>
);
}

export function BranchMismatchDialog({
open,
linkedBranch,
currentBranch,
hasUncommittedChanges,
switchError,
onSwitch,
onContinue,
onCancel,
isSwitching,
}: BranchMismatchDialogProps) {
return (
<AlertDialog.Root
open={open}
onOpenChange={(isOpen) => {
if (!isOpen) onCancel();
}}
>
<AlertDialog.Content maxWidth="420px" size="2">
<AlertDialog.Title size="3">
<Flex align="center" gap="2">
<Warning size={18} weight="fill" color="var(--orange-9)" />
Wrong branch
</Flex>
</AlertDialog.Title>
<AlertDialog.Description size="2">
This task is linked to a different branch than the one you're
currently on. The agent will make changes on the current branch.
</AlertDialog.Description>
<Flex direction="column" gap="1" mt="3" style={{ minWidth: 0 }}>
<Flex align="center" gap="2" style={{ minWidth: 0 }}>
<Text
size="1"
color="gray"
style={{ flexShrink: 0, width: "64px" }}
>
Linked
</Text>
<BranchLabel name={linkedBranch} />
</Flex>
<Flex align="center" gap="2" style={{ minWidth: 0 }}>
<Text
size="1"
color="gray"
style={{ flexShrink: 0, width: "64px" }}
>
Current
</Text>
<BranchLabel name={currentBranch} />
</Flex>
</Flex>

{hasUncommittedChanges && !switchError && (
<Callout.Root size="1" color="gray" mt="3">
<Callout.Text size="1">
You have uncommitted changes on your current branch. If needed,
commit or stash them first.
</Callout.Text>
</Callout.Root>
)}

{switchError && (
<Callout.Root size="1" color="red" mt="3">
<Callout.Text size="1">{switchError}</Callout.Text>
</Callout.Root>
)}

<Flex justify="end" gap="2" mt="4">
<AlertDialog.Cancel>
<Button variant="soft" color="gray" size="1" disabled={isSwitching}>
Cancel
</Button>
</AlertDialog.Cancel>

<Button
variant="soft"
color="orange"
size="1"
onClick={onContinue}
disabled={isSwitching}
>
Continue anyway
</Button>

<AlertDialog.Action>
<Button
variant="solid"
size="1"
onClick={onSwitch}
loading={isSwitching}
>
Switch branch
</Button>
</AlertDialog.Action>
</Flex>
</AlertDialog.Content>
</AlertDialog.Root>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { useSessionConnection } from "@features/sessions/hooks/useSessionConnect
import { useSessionViewState } from "@features/sessions/hooks/useSessionViewState";
import { useRestoreTask } from "@features/suspension/hooks/useRestoreTask";
import { useSuspendedTaskIds } from "@features/suspension/hooks/useSuspendedTaskIds";
import { BranchMismatchDialog } from "@features/task-detail/components/BranchMismatchDialog";
import { WorkspaceSetupPrompt } from "@features/task-detail/components/WorkspaceSetupPrompt";
import { useBranchMismatchDialog } from "@features/workspace/hooks/useBranchMismatchDialog";
import {
useCreateWorkspace,
useWorkspaceLoaded,
Expand Down Expand Up @@ -81,6 +83,12 @@ export function TaskLogsPanel({ taskId, task, hideInput }: TaskLogsPanelProps) {
handleBashCommand,
} = useSessionCallbacks({ taskId, task, session, repoPath });

const { handleBeforeSubmit, dialogProps } = useBranchMismatchDialog({
taskId,
repoPath,
onSendPrompt: handleSendPrompt,
});

const cloudOutput = session?.cloudOutput ?? null;
const prUrl =
isCloud && cloudOutput?.pr_url ? (cloudOutput.pr_url as string) : null;
Expand Down Expand Up @@ -147,6 +155,7 @@ export function TaskLogsPanel({ taskId, task, hideInput }: TaskLogsPanelProps) {
isRestoring={isRestoring}
isPromptPending={isPromptPending}
promptStartedAt={promptStartedAt}
onBeforeSubmit={handleBeforeSubmit}
onSendPrompt={handleSendPrompt}
onBashCommand={isCloud ? undefined : handleBashCommand}
onCancelPrompt={handleCancelPrompt}
Expand All @@ -165,6 +174,8 @@ export function TaskLogsPanel({ taskId, task, hideInput }: TaskLogsPanelProps) {
</ErrorBoundary>
</Box>
</Flex>

{dialogProps && <BranchMismatchDialog {...dialogProps} />}
</BackgroundWrapper>
);
}
Loading
Loading