fix: restore input on submission error and enforce clean working tree for git operations

This commit is contained in:
Mohamed Boudra
2025-12-27 09:35:40 +07:00
parent 40835bbe9d
commit 95c821a4c6
3 changed files with 29 additions and 13 deletions
+10 -6
View File
@@ -122,6 +122,7 @@ export default function DraftAgentScreen() {
>(null); >(null);
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [promptText, setPromptText] = useState("");
const [baseBranch, setBaseBranch] = useState(""); const [baseBranch, setBaseBranch] = useState("");
// Isolation mode: "none" | "branch" | "worktree" // Isolation mode: "none" | "branch" | "worktree"
const [isolationMode, setIsolationMode] = useState<"none" | "branch" | "worktree">("none"); const [isolationMode, setIsolationMode] = useState<"none" | "branch" | "worktree">("none");
@@ -487,27 +488,27 @@ export default function DraftAgentScreen() {
const trimmedPrompt = text.trim(); const trimmedPrompt = text.trim();
if (!trimmedPath) { if (!trimmedPath) {
setErrorMessage("Working directory is required"); setErrorMessage("Working directory is required");
return; throw new Error("Working directory is required");
} }
if (!trimmedPrompt) { if (!trimmedPrompt) {
setErrorMessage("Initial prompt is required"); setErrorMessage("Initial prompt is required");
return; throw new Error("Initial prompt is required");
} }
if (!selectedServerId) { if (!selectedServerId) {
setErrorMessage("No host selected"); setErrorMessage("No host selected");
return; throw new Error("No host selected");
} }
if (gitBlockingError) { if (gitBlockingError) {
setErrorMessage(gitBlockingError); setErrorMessage(gitBlockingError);
return; throw new Error(gitBlockingError);
} }
if (isLoading) { if (isLoading) {
return; throw new Error("Already loading");
} }
const createAgent = sessionMethods?.createAgent; const createAgent = sessionMethods?.createAgent;
if (!createAgent) { if (!createAgent) {
setErrorMessage("Host is not connected"); setErrorMessage("Host is not connected");
return; throw new Error("Host is not connected");
} }
const modeId = const modeId =
modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined; modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined;
@@ -608,6 +609,7 @@ export default function DraftAgentScreen() {
} }
pendingRequestIdRef.current = null; pendingRequestIdRef.current = null;
setIsLoading(false); setIsLoading(false);
setPromptText("");
router.push({ router.push({
pathname: "/agent/[serverId]/[agentId]", pathname: "/agent/[serverId]/[agentId]",
params: { params: {
@@ -905,6 +907,8 @@ export default function DraftAgentScreen() {
serverId={selectedServerId ?? ""} serverId={selectedServerId ?? ""}
onSubmitMessage={handleCreateFromInput} onSubmitMessage={handleCreateFromInput}
isSubmitLoading={isLoading} isSubmitLoading={isLoading}
value={promptText}
onChangeText={setPromptText}
/> />
</View> </View>
</View> </View>
@@ -52,6 +52,8 @@ interface AgentInputAreaProps {
}) => Promise<void>; }) => Promise<void>;
/** Externally controlled loading state. When true, disables the submit button. */ /** Externally controlled loading state. When true, disables the submit button. */
isSubmitLoading?: boolean; isSubmitLoading?: boolean;
value?: string;
onChangeText?: (text: string) => void;
} }
const MIN_INPUT_HEIGHT = 50; const MIN_INPUT_HEIGHT = 50;
@@ -96,6 +98,8 @@ export function AgentInputArea({
serverId, serverId,
onSubmitMessage, onSubmitMessage,
isSubmitLoading = false, isSubmitLoading = false,
value,
onChangeText,
}: AgentInputAreaProps) { }: AgentInputAreaProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
@@ -146,7 +150,9 @@ export function AgentInputArea({
const wsOrInert = ws ?? inertWebSocket; const wsOrInert = ws ?? inertWebSocket;
const { startRealtime, stopRealtime, isRealtimeMode } = useRealtime(); const { startRealtime, stopRealtime, isRealtimeMode } = useRealtime();
const [userInput, setUserInput] = useState(""); const [internalInput, setInternalInput] = useState("");
const userInput = value ?? internalInput;
const setUserInput = onChangeText ?? setInternalInput;
const [inputHeight, setInputHeight] = useState(MIN_INPUT_HEIGHT); const [inputHeight, setInputHeight] = useState(MIN_INPUT_HEIGHT);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [selectedImages, setSelectedImages] = useState<Array<{ uri: string; mimeType: string }>>([]); const [selectedImages, setSelectedImages] = useState<Array<{ uri: string; mimeType: string }>>([]);
@@ -367,8 +373,11 @@ export function AgentInputArea({
const message = userInput.trim(); const message = userInput.trim();
const imageAttachments = selectedImages.length > 0 ? selectedImages : undefined; const imageAttachments = selectedImages.length > 0 ? selectedImages : undefined;
setUserInput(""); const isControlled = value !== undefined;
if (!isControlled) {
setUserInput("");
}
setSelectedImages([]); setSelectedImages([]);
setInputHeight(MIN_INPUT_HEIGHT); setInputHeight(MIN_INPUT_HEIGHT);
setIsProcessing(true); setIsProcessing(true);
@@ -377,6 +386,11 @@ export function AgentInputArea({
await submitMessage(message, imageAttachments); await submitMessage(message, imageAttachments);
} catch (error) { } catch (error) {
console.error("[AgentInput] Failed to send message:", error); console.error("[AgentInput] Failed to send message:", error);
// Restore input on error so user doesn't lose their message
setUserInput(message);
if (imageAttachments) {
setSelectedImages(imageAttachments);
}
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
+2 -4
View File
@@ -1503,10 +1503,6 @@ export class Session {
}; };
} }
if (!normalized.createWorktree) {
await this.ensureCleanWorkingTree(cwd);
}
if (normalized.createWorktree) { if (normalized.createWorktree) {
const targetBranch = normalized.createNewBranch const targetBranch = normalized.createNewBranch
? normalized.newBranchName ? normalized.newBranchName
@@ -1751,6 +1747,7 @@ export class Session {
return; return;
} }
await this.ensureCleanWorkingTree(cwd);
await execAsync(`git checkout ${branch}`, { cwd }); await execAsync(`git checkout ${branch}`, { cwd });
} }
@@ -1773,6 +1770,7 @@ export class Session {
throw new Error(`Branch already exists: ${newBranchName}`); throw new Error(`Branch already exists: ${newBranchName}`);
} }
await this.ensureCleanWorkingTree(cwd);
await execAsync(`git checkout -b ${newBranchName} ${baseBranch}`, { await execAsync(`git checkout -b ${newBranchName} ${baseBranch}`, {
cwd, cwd,
}); });