feat: add relay reconnect with grace period and branch suggestions

This commit is contained in:
Mohamed Boudra
2026-02-13 09:46:40 +07:00
parent 837d0fb387
commit bf432b3d3a
23 changed files with 1137 additions and 103 deletions
@@ -276,13 +276,11 @@ export function AgentInputArea({
imageAttachments?: ImageAttachment[],
forceSend?: boolean
) {
const socketConnected = isConnected;
const trimmedMessage = message.trim();
if (!trimmedMessage) return;
// When the parent controls submission (e.g. draft agent creation), let it
// decide what to do even if the socket is currently disconnected (so we
// don't no-op and lose deterministic error handling in the UI/tests).
if (!onSubmitMessageRef.current && !socketConnected) return;
if (!sendAgentMessageRef.current && !onSubmitMessageRef.current) return;
if (agent?.status === "running" && !forceSend) {
@@ -485,7 +483,7 @@ export function AgentInputArea({
async function handleSendQueuedNow(id: string) {
const item = queuedMessages.find((q) => q.id === id);
if (!item || !isConnected) return;
if (!item) return;
if (!sendAgentMessageRef.current && !onSubmitMessageRef.current) return;
updateQueue((current) => current.filter((q) => q.id !== id));
+10 -12
View File
@@ -731,7 +731,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
scrollEnabled={IS_WEB ? inputHeight >= MAX_INPUT_HEIGHT : true}
onContentSizeChange={handleContentSizeChange}
editable={
!isDictating && !isRealtimeVoiceForCurrentAgent && isConnected && !disabled
!isDictating && !isRealtimeVoiceForCurrentAgent && !disabled
}
onKeyPress={
shouldHandleDesktopSubmit ? handleDesktopKeyPress : undefined
@@ -836,24 +836,22 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
)}
{shouldShowSendButton && (
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger
onPress={handleSendMessage}
disabled={
!isConnected ||
<TooltipTrigger
onPress={handleSendMessage}
disabled={
isSubmitDisabled ||
isSubmitLoading ||
disabled
}
accessibilityLabel={isAgentRunning ? "Send and interrupt" : "Send message"}
accessibilityRole="button"
style={[
styles.sendButton,
(!isConnected ||
isSubmitDisabled ||
accessibilityLabel={isAgentRunning ? "Send and interrupt" : "Send message"}
accessibilityRole="button"
style={[
styles.sendButton,
(isSubmitDisabled ||
isSubmitLoading ||
disabled) &&
styles.buttonDisabled,
]}
]}
>
{isSubmitLoading ? (
<ActivityIndicator size="small" color="white" />
@@ -183,7 +183,7 @@ function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
return (
<SessionProvider
key={`${daemon.serverId}:${activeUrl}`}
key={daemon.serverId}
serverUrl={activeUrl}
serverId={daemon.serverId}
activeConnection={active?.activeConnection ?? null}
+15 -5
View File
@@ -35,6 +35,7 @@ export interface ComboboxProps {
options: ComboboxOption[];
value: string;
onSelect: (id: string) => void;
onSearchQueryChange?: (query: string) => void;
placeholder?: string;
searchPlaceholder?: string;
emptyText?: string;
@@ -156,6 +157,7 @@ export function Combobox({
options,
value,
onSelect,
onSearchQueryChange,
placeholder = "Search...",
searchPlaceholder,
emptyText = "No options match your search.",
@@ -191,16 +193,24 @@ export function Combobox({
[isControlled, onOpenChange]
);
const setSearchQueryWithCallback = useCallback(
(nextQuery: string) => {
setSearchQuery(nextQuery);
onSearchQueryChange?.(nextQuery);
},
[onSearchQueryChange]
);
const handleClose = useCallback(() => {
setOpen(false);
setSearchQuery("");
}, [setOpen]);
setSearchQueryWithCallback("");
}, [setOpen, setSearchQueryWithCallback]);
useEffect(() => {
if (isOpen) {
setSearchQuery("");
setSearchQueryWithCallback("");
}
}, [isOpen]);
}, [isOpen, setSearchQueryWithCallback]);
const collisionPadding = useMemo(() => {
const basePadding = 16;
@@ -426,7 +436,7 @@ export function Combobox({
<SearchInput
placeholder={searchPlaceholder ?? placeholder}
value={searchQuery}
onChangeText={setSearchQuery}
onChangeText={setSearchQueryWithCallback}
onSubmitEditing={handleSubmitSearch}
autoFocus={!isMobile}
/>