Merge branch 'storage-terminal-ui-dev' into dev
# Conflicts: # nix/package.nix # packages/app/src/app/_layout.tsx # packages/app/src/components/sidebar-workspace-list.tsx # packages/app/src/hooks/use-command-center.ts # packages/app/src/screens/agent/draft-agent-screen.tsx # packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx # packages/app/src/screens/workspace/workspace-screen.tsx # packages/server/src/server/session.ts # packages/server/src/server/session.workspaces.test.ts # packages/server/src/terminal/terminal.test.ts # packages/server/src/terminal/terminal.ts
This commit is contained in:
@@ -11,15 +11,14 @@ import Animated from "react-native-reanimated";
|
||||
import { Folder, GitBranch, PanelRight } from "lucide-react-native";
|
||||
import { SidebarMenuToggle } from "@/components/headers/menu-header";
|
||||
import { HeaderToggleButton } from "@/components/headers/header-toggle-button";
|
||||
import { AgentInputArea } from "@/components/agent-input-area";
|
||||
import { Composer } from "@/components/composer";
|
||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||
import { FormSelectTrigger } from "@/components/agent-form/agent-form-dropdowns";
|
||||
import { ExplorerSidebar } from "@/components/explorer-sidebar";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { FileDropZone } from "@/components/file-drop-zone";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAgentFormState, type CreateAgentInitialValues } from "@/hooks/use-agent-form-state";
|
||||
import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query";
|
||||
import type { CreateAgentInitialValues } from "@/hooks/use-agent-form-state";
|
||||
import {
|
||||
CHECKOUT_STATUS_STALE_TIME,
|
||||
checkoutStatusQueryKey,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
import { useAllAgentsList } from "@/hooks/use-all-agents-list";
|
||||
import { useHosts } from "@/runtime/host-runtime";
|
||||
import { buildBranchComboOptions, normalizeBranchOptionName } from "@/utils/branch-suggestions";
|
||||
import { buildHostAgentDetailRoute } from "@/utils/host-routes";
|
||||
import { shortenPath } from "@/utils/shorten-path";
|
||||
import { collectAgentWorkingDirectorySuggestions } from "@/utils/agent-working-directory-suggestions";
|
||||
import { buildWorkingDirectorySuggestions } from "@/utils/working-directory-suggestions";
|
||||
@@ -50,6 +50,7 @@ import type {
|
||||
AgentSessionConfig,
|
||||
} from "@server/server/agent/agent-sdk-types";
|
||||
import { AGENT_PROVIDER_DEFINITIONS } from "@server/server/agent/provider-manifest";
|
||||
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
|
||||
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
|
||||
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||
@@ -65,6 +66,7 @@ const DRAFT_CAPABILITIES: AgentCapabilityFlags = {
|
||||
supportsMcpServers: false,
|
||||
supportsReasoningStream: false,
|
||||
supportsToolInvocations: false,
|
||||
supportsTerminalMode: false,
|
||||
};
|
||||
const PROVIDER_DEFINITION_MAP = new Map(
|
||||
AGENT_PROVIDER_DEFINITIONS.map((definition) => [definition.id, definition]),
|
||||
@@ -202,37 +204,45 @@ function DraftAgentScreenContent({
|
||||
return values;
|
||||
}, [resolvedMode, resolvedModel, resolvedProvider, resolvedThinkingOptionId, resolvedWorkingDir]);
|
||||
|
||||
const draftIdRef = useRef(generateDraftId());
|
||||
const draftAgentIdRef = useRef(generateDraftId());
|
||||
const draftInput = useAgentInputDraft(
|
||||
{
|
||||
draftKey: ({ selectedServerId }) =>
|
||||
buildDraftStoreKey({
|
||||
serverId: selectedServerId ?? "",
|
||||
agentId: draftAgentIdRef.current,
|
||||
draftId: draftIdRef.current,
|
||||
}),
|
||||
composer: {
|
||||
initialServerId: resolvedServerId ?? null,
|
||||
initialValues,
|
||||
isVisible,
|
||||
onlineServerIds,
|
||||
},
|
||||
},
|
||||
);
|
||||
const composerState = draftInput.composerState;
|
||||
if (!composerState) {
|
||||
throw new Error("Draft agent composer state is required");
|
||||
}
|
||||
|
||||
const {
|
||||
selectedServerId,
|
||||
setSelectedServerIdFromUser,
|
||||
selectedProvider,
|
||||
setProviderFromUser,
|
||||
selectedMode,
|
||||
setModeFromUser,
|
||||
selectedModel,
|
||||
setModelFromUser,
|
||||
selectedThinkingOptionId,
|
||||
setThinkingOptionFromUser,
|
||||
providerDefinitions,
|
||||
workingDir,
|
||||
setWorkingDirFromUser,
|
||||
providerDefinitions,
|
||||
modeOptions,
|
||||
availableModels,
|
||||
allProviderModels,
|
||||
isAllModelsLoading,
|
||||
availableThinkingOptions,
|
||||
isModelLoading,
|
||||
modelError,
|
||||
refreshProviderModels,
|
||||
setProviderAndModelFromUser,
|
||||
persistFormPreferences,
|
||||
} = useAgentFormState({
|
||||
initialServerId: resolvedServerId ?? null,
|
||||
initialValues,
|
||||
isVisible,
|
||||
isCreateFlow: true,
|
||||
onlineServerIds,
|
||||
});
|
||||
effectiveModelId,
|
||||
effectiveThinkingOptionId,
|
||||
commandDraftConfig,
|
||||
statusControls,
|
||||
} = composerState;
|
||||
const isMobile = isCompactFormFactor();
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
@@ -244,15 +254,6 @@ function DraftAgentScreenContent({
|
||||
(state) => state.activateExplorerTabForCheckout,
|
||||
);
|
||||
const isExplorerOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
|
||||
const draftIdRef = useRef(generateDraftId());
|
||||
const draftAgentIdRef = useRef(generateDraftId());
|
||||
const draftInput = useAgentInputDraft(
|
||||
buildDraftStoreKey({
|
||||
serverId: selectedServerId ?? "",
|
||||
agentId: draftAgentIdRef.current,
|
||||
draftId: draftIdRef.current,
|
||||
}),
|
||||
);
|
||||
|
||||
const [worktreeMode, setWorktreeMode] = useState<"none" | "create" | "attach">(
|
||||
initialWorktreeMode,
|
||||
@@ -632,6 +633,16 @@ function DraftAgentScreenContent({
|
||||
isGit: isAttachWorktree && selectedWorktreePath ? true : checkout?.isGit === true,
|
||||
};
|
||||
}, [selectedServerId, explorerCwd, isAttachWorktree, selectedWorktreePath, checkout?.isGit]);
|
||||
const draftExplorerWorkspaceId = useSessionStore(
|
||||
useCallback(
|
||||
(state) =>
|
||||
resolveWorkspaceIdByExecutionDirectory({
|
||||
workspaces: selectedServerId ? state.sessions[selectedServerId]?.workspaces?.values() : null,
|
||||
workspaceDirectory: explorerCwd,
|
||||
}),
|
||||
[explorerCwd, selectedServerId],
|
||||
),
|
||||
);
|
||||
const canOpenExplorer = draftExplorerCheckout !== null;
|
||||
const openExplorerForDraftCheckout = useCallback(() => {
|
||||
if (!draftExplorerCheckout) {
|
||||
@@ -743,47 +754,6 @@ function DraftAgentScreenContent({
|
||||
}, [baseBranch, branchSearchQuery, branchSuggestionsQuery.data, checkout, worktreeOptions]);
|
||||
|
||||
const createAgentClient = sessionClient;
|
||||
const effectiveDraftModelId = useMemo(() => {
|
||||
if (selectedModel.trim()) {
|
||||
return selectedModel.trim();
|
||||
}
|
||||
return availableModels.find((model) => model.isDefault)?.id ?? availableModels[0]?.id ?? "";
|
||||
}, [availableModels, selectedModel]);
|
||||
const effectiveDraftThinkingOptionId = useMemo(() => {
|
||||
if (selectedThinkingOptionId.trim()) {
|
||||
return selectedThinkingOptionId.trim();
|
||||
}
|
||||
const selectedModelDefinition =
|
||||
availableModels.find((model) => model.id === effectiveDraftModelId) ?? null;
|
||||
return selectedModelDefinition?.defaultThinkingOptionId ?? "";
|
||||
}, [availableModels, effectiveDraftModelId, selectedThinkingOptionId]);
|
||||
const draftCommandConfig = useMemo<DraftCommandConfig | undefined>(() => {
|
||||
const cwd = (
|
||||
isAttachWorktree && selectedWorktreePath ? selectedWorktreePath : workingDir
|
||||
).trim();
|
||||
if (!cwd) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
provider: selectedProvider,
|
||||
cwd,
|
||||
...(modeOptions.length > 0 && selectedMode !== "" ? { modeId: selectedMode } : {}),
|
||||
...(effectiveDraftModelId ? { model: effectiveDraftModelId } : {}),
|
||||
...(effectiveDraftThinkingOptionId
|
||||
? { thinkingOptionId: effectiveDraftThinkingOptionId }
|
||||
: {}),
|
||||
};
|
||||
}, [
|
||||
effectiveDraftModelId,
|
||||
effectiveDraftThinkingOptionId,
|
||||
isAttachWorktree,
|
||||
modeOptions.length,
|
||||
selectedMode,
|
||||
selectedProvider,
|
||||
selectedWorktreePath,
|
||||
workingDir,
|
||||
]);
|
||||
|
||||
const {
|
||||
formErrorMessage,
|
||||
@@ -791,7 +761,7 @@ function DraftAgentScreenContent({
|
||||
optimisticStreamItems,
|
||||
draftAgent,
|
||||
handleCreateFromInput,
|
||||
} = useDraftAgentCreateFlow<Agent, { id: string; cwd: string }>({
|
||||
} = useDraftAgentCreateFlow<Agent, { id: string; workspaceId: string | null }>({
|
||||
draftId: draftIdRef.current,
|
||||
getPendingServerId: () => selectedServerId,
|
||||
validateBeforeSubmit: ({ text }) => {
|
||||
@@ -817,7 +787,7 @@ function DraftAgentScreenContent({
|
||||
if (isModelLoading) {
|
||||
return "Model defaults are still loading";
|
||||
}
|
||||
if (!effectiveDraftModelId) {
|
||||
if (!effectiveModelId) {
|
||||
return "No model is available for the selected provider";
|
||||
}
|
||||
if (isAttachWorktree && !selectedWorktreePath) {
|
||||
@@ -850,15 +820,19 @@ function DraftAgentScreenContent({
|
||||
const cwd =
|
||||
(isAttachWorktree && selectedWorktreePath ? selectedWorktreePath : workingDir).trim() ||
|
||||
".";
|
||||
const provider = selectedProvider;
|
||||
const model = effectiveDraftModelId || null;
|
||||
const thinkingOptionId = effectiveDraftThinkingOptionId || null;
|
||||
const modeId = modeOptions.length > 0 && selectedMode !== "" ? selectedMode : null;
|
||||
const provider = composerState.selectedProvider;
|
||||
const model = effectiveModelId || null;
|
||||
const thinkingOptionId = effectiveThinkingOptionId || null;
|
||||
const modeId =
|
||||
composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
|
||||
? composerState.selectedMode
|
||||
: null;
|
||||
|
||||
return {
|
||||
serverId,
|
||||
id: draftAgentIdRef.current,
|
||||
provider,
|
||||
terminal: false,
|
||||
status: "running",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -887,14 +861,17 @@ function DraftAgentScreenContent({
|
||||
const resolvedWorkingDir =
|
||||
isAttachWorktree && selectedWorktreePath ? selectedWorktreePath : trimmedPath;
|
||||
|
||||
const modeId = modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined;
|
||||
const modeId =
|
||||
composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
|
||||
? composerState.selectedMode
|
||||
: undefined;
|
||||
const config: AgentSessionConfig = {
|
||||
provider: selectedProvider,
|
||||
provider: composerState.selectedProvider,
|
||||
cwd: resolvedWorkingDir,
|
||||
...(modeId ? { modeId } : {}),
|
||||
...(effectiveDraftModelId ? { model: effectiveDraftModelId } : {}),
|
||||
...(effectiveDraftThinkingOptionId
|
||||
? { thinkingOptionId: effectiveDraftThinkingOptionId }
|
||||
...(effectiveModelId ? { model: effectiveModelId } : {}),
|
||||
...(effectiveThinkingOptionId
|
||||
? { thinkingOptionId: effectiveThinkingOptionId }
|
||||
: {}),
|
||||
};
|
||||
|
||||
@@ -942,20 +919,29 @@ function DraftAgentScreenContent({
|
||||
|
||||
const createdWorkingDir = typeof result.cwd === "string" ? result.cwd.trim() : "";
|
||||
const configuredWorkingDir = config.cwd.trim();
|
||||
const workspaceId = createdWorkingDir.length > 0 ? createdWorkingDir : configuredWorkingDir;
|
||||
const workspaceId = resolveWorkspaceIdByExecutionDirectory({
|
||||
workspaces: useSessionStore.getState().sessions[selectedServerId]?.workspaces?.values(),
|
||||
workspaceDirectory: createdWorkingDir.length > 0 ? createdWorkingDir : configuredWorkingDir,
|
||||
});
|
||||
|
||||
return {
|
||||
agentId: result.id,
|
||||
result: {
|
||||
id: result.id,
|
||||
cwd: workspaceId,
|
||||
workspaceId,
|
||||
},
|
||||
};
|
||||
},
|
||||
onCreateSuccess: ({ result }) => {
|
||||
if (!result.workspaceId) {
|
||||
router.replace(
|
||||
buildHostAgentDetailRoute(selectedServerId as string, result.id) as any,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const route = prepareWorkspaceTab({
|
||||
serverId: selectedServerId as string,
|
||||
workspaceId: result.cwd,
|
||||
workspaceId: result.workspaceId,
|
||||
target: { kind: "agent", agentId: result.id },
|
||||
});
|
||||
router.replace(route as any);
|
||||
@@ -1216,7 +1202,7 @@ function DraftAgentScreenContent({
|
||||
)}
|
||||
</Animated.View>
|
||||
<View style={styles.inputAreaWrapper}>
|
||||
<AgentInputArea
|
||||
<Composer
|
||||
agentId={draftAgentIdRef.current}
|
||||
serverId={selectedServerId ?? ""}
|
||||
isInputActive={isFocused}
|
||||
@@ -1230,24 +1216,9 @@ function DraftAgentScreenContent({
|
||||
clearDraft={draftInput.clear}
|
||||
autoFocus={!isSubmitting}
|
||||
onAddImages={handleAddImagesCallback}
|
||||
commandDraftConfig={draftCommandConfig}
|
||||
commandDraftConfig={commandDraftConfig}
|
||||
statusControls={{
|
||||
providerDefinitions,
|
||||
selectedProvider,
|
||||
onSelectProvider: setProviderFromUser,
|
||||
modeOptions,
|
||||
selectedMode,
|
||||
onSelectMode: setModeFromUser,
|
||||
models: availableModels,
|
||||
selectedModel,
|
||||
onSelectModel: setModelFromUser,
|
||||
isModelLoading,
|
||||
allProviderModels,
|
||||
isAllModelsLoading,
|
||||
onSelectProviderAndModel: setProviderAndModelFromUser,
|
||||
thinkingOptions: availableThinkingOptions,
|
||||
selectedThinkingOptionId,
|
||||
onSelectThinkingOption: setThinkingOptionFromUser,
|
||||
...statusControls,
|
||||
disabled: isSubmitting,
|
||||
}}
|
||||
/>
|
||||
@@ -1257,7 +1228,7 @@ function DraftAgentScreenContent({
|
||||
{!isMobile && isExplorerOpen && explorerServerId && draftExplorerCheckout ? (
|
||||
<ExplorerSidebar
|
||||
serverId={explorerServerId}
|
||||
workspaceId={draftExplorerCheckout.cwd}
|
||||
workspaceId={draftExplorerWorkspaceId}
|
||||
workspaceRoot={draftExplorerCheckout.cwd}
|
||||
isGit={explorerIsGit}
|
||||
/>
|
||||
@@ -1280,7 +1251,7 @@ function DraftAgentScreenContent({
|
||||
{isMobile && explorerServerId && draftExplorerCheckout ? (
|
||||
<ExplorerSidebar
|
||||
serverId={explorerServerId}
|
||||
workspaceId={draftExplorerCheckout.cwd}
|
||||
workspaceId={draftExplorerWorkspaceId}
|
||||
workspaceRoot={draftExplorerCheckout.cwd}
|
||||
isGit={explorerIsGit}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user