fix: restore input on submission error and enforce clean working tree for git operations
This commit is contained in:
@@ -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 }>>([]);
|
||||||
@@ -368,7 +374,10 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user