From 690f5298cbbd40c5f80db2160ec171bff02020f8 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 04:08:31 +0200 Subject: [PATCH] fix(app): keep draft composers off the agent command list before a project and model are chosen (#5594) A draft composer passed no command config when it was missing a project or a provider, which is what a running agent's composer passes. The `/` menu then listed commands by the draft key, got "Agent not found", and showed /clear and /exit. Drafts now always report a command target (ready, needs-project, needs-provider), and an incomplete draft asks for the missing choice instead of querying. Closes #5516 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Ivan Malison --- .../e2e/browser/composer-autocomplete.spec.ts | 30 ++++++++ .../src/components/workspace-setup-dialog.tsx | 2 +- .../composer/draft/input-draft.live.test.tsx | 15 ++-- .../src/composer/draft/input-draft.test.ts | 63 +++++++++-------- .../app/src/composer/draft/input-draft.ts | 32 ++++----- .../app/src/composer/draft/workspace-tab.tsx | 2 +- packages/app/src/composer/index.tsx | 12 ++-- .../app/src/hooks/use-agent-autocomplete.ts | 68 ++++++++++++------- .../app/src/hooks/use-agent-commands-query.ts | 6 ++ packages/app/src/i18n/resources/ar.ts | 2 + packages/app/src/i18n/resources/en.ts | 2 + packages/app/src/i18n/resources/es.ts | 2 + packages/app/src/i18n/resources/fr.ts | 2 + packages/app/src/i18n/resources/ja.ts | 2 + packages/app/src/i18n/resources/ko.ts | 2 + packages/app/src/i18n/resources/pt-BR.ts | 2 + packages/app/src/i18n/resources/ru.ts | 2 + packages/app/src/i18n/resources/zh-CN.ts | 2 + .../provider-selection/provider-selection.ts | 16 +++-- .../app/src/screens/new-workspace-screen.tsx | 2 +- 20 files changed, 175 insertions(+), 91 deletions(-) diff --git a/packages/app/e2e/browser/composer-autocomplete.spec.ts b/packages/app/e2e/browser/composer-autocomplete.spec.ts index 01bdfd45d2..d30e69b18c 100644 --- a/packages/app/e2e/browser/composer-autocomplete.spec.ts +++ b/packages/app/e2e/browser/composer-autocomplete.spec.ts @@ -10,6 +10,7 @@ import { daemonWsRoutePattern } from "../support/helpers/daemon-port"; import { getServerId } from "../support/helpers/server-id"; import { switchWorkspaceViaSidebar } from "../support/helpers/workspace-ui"; import { expectMobileAgentSidebarVisible } from "../support/helpers/sidebar"; +import { expectNewWorkspaceProjectSelected } from "../support/helpers/new-workspace"; const TEST_COMMANDS = [ { @@ -338,6 +339,16 @@ function expectPopoverFramesStable(frames: PopoverFrame[]): void { ).toBeUndefined(); } +function recordCommandRequestsByAgentId(page: Page): string[] { + const requests: string[] = []; + const record = ({ payload }: { payload: string | Buffer }) => { + if (typeof payload !== "string" || !payload.includes("list_commands_request")) return; + if (!payload.includes("draftConfig")) requests.push(payload); + }; + page.on("websocket", (socket) => socket.on("framesent", record)); + return requests; +} + function expectPopoverDoesNotDisappearAfterFirstVisible(frames: PopoverFrame[]): void { const firstVisibleIndex = frames.findIndex( (frame) => @@ -359,6 +370,25 @@ function expectPopoverDoesNotDisappearAfterFirstVisible(frames: PopoverFrame[]): } test.describe("Composer autocomplete", () => { + test("keeps New workspace off the running-agent command list before a project is chosen", async ({ + page, + }) => { + const commandRequestsByAgentId = recordCommandRequestsByAgentId(page); + + await page.goto("/new"); + await expectNewWorkspaceProjectSelected(page, "Choose project"); + await composerLocator(page).fill("/"); + + const popover = page + .getByTestId("composer-autocomplete-popover") + .filter({ visible: true }) + .first(); + await expect(popover).toContainText("Choose a project to see commands", { timeout: 30_000 }); + await expect(popover).not.toContainText("/clear"); + await expect(popover).not.toContainText("/exit"); + expect(commandRequestsByAgentId).toEqual([]); + }); + test("stays visible after returning from app-wide routes", async ({ page }) => { await installListCommandsStub(page); const serverId = getServerId(); diff --git a/packages/app/src/components/workspace-setup-dialog.tsx b/packages/app/src/components/workspace-setup-dialog.tsx index cf815d0a18..1954394176 100644 --- a/packages/app/src/components/workspace-setup-dialog.tsx +++ b/packages/app/src/components/workspace-setup-dialog.tsx @@ -462,7 +462,7 @@ export function WorkspaceSetupDialog() { cwd={sourceDirectory} clearDraft={chatDraft.clear} autoFocus - commandDraftConfig={composerState?.commandDraftConfig} + commandDraft={composerState?.commandDraft} agentControls={agentControlsWithDisabled} inputWrapperStyle={styles.composerInputWrapper} /> diff --git a/packages/app/src/composer/draft/input-draft.live.test.tsx b/packages/app/src/composer/draft/input-draft.live.test.tsx index b6dd735207..33041cceb6 100644 --- a/packages/app/src/composer/draft/input-draft.live.test.tsx +++ b/packages/app/src/composer/draft/input-draft.live.test.tsx @@ -198,12 +198,15 @@ describe("useAgentInputDraft live contract", () => { }); expect(getLatest().composerState?.agentControls.selectedProvider).toBe("codex"); - expect(getLatest().composerState?.commandDraftConfig).toEqual({ - provider: "codex", - cwd: "/repo", - modeId: "auto", - model: "gpt-5.4", - thinkingOptionId: "high", + expect(getLatest().composerState?.commandDraft).toEqual({ + status: "ready", + config: { + provider: "codex", + cwd: "/repo", + modeId: "auto", + model: "gpt-5.4", + thinkingOptionId: "high", + }, }); const hydratedTextReplacement = getLatest().textReplacement; diff --git a/packages/app/src/composer/draft/input-draft.test.ts b/packages/app/src/composer/draft/input-draft.test.ts index d9bbb6838f..882f342199 100644 --- a/packages/app/src/composer/draft/input-draft.test.ts +++ b/packages/app/src/composer/draft/input-draft.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { hasDraftContent, resolveDraftKey } from "./input-draft-core"; import { - buildDraftCommandConfig, + buildDraftCommandTarget, resolveEffectiveComposerModelId, resolveEffectiveComposerThinkingOptionId, } from "@/provider-selection/provider-selection"; @@ -124,46 +124,55 @@ describe("resolveEffectiveComposerThinkingOptionId", () => { }); }); -describe("buildDraftComposerCommandConfig", () => { - it("returns undefined when cwd is empty", () => { +describe("buildDraftCommandTarget", () => { + const codexSelection = { + provider: "codex", + modelId: "gpt-5.4", + modeId: "auto", + thinkingOptionId: "high", + availableModels: [], + modeOptions: [{ id: "auto", label: "Auto" }], + }; + + it("needs a project when cwd is empty", () => { expect( - buildDraftCommandConfig({ - selection: { - provider: "codex", - modelId: "gpt-5.4", - modeId: "", - thinkingOptionId: "", - availableModels: [], - modeOptions: [], - }, + buildDraftCommandTarget({ + selection: codexSelection, cwd: " ", effectiveModelId: "gpt-5.4", effectiveThinkingOptionId: "high", }), - ).toBeUndefined(); + ).toEqual({ status: "needs-project" }); + }); + + it("needs a provider when none is selected", () => { + expect( + buildDraftCommandTarget({ + selection: { ...codexSelection, provider: null }, + cwd: "/repo", + effectiveModelId: "", + effectiveThinkingOptionId: "", + }), + ).toEqual({ status: "needs-provider" }); }); it("builds the draft command config from derived composer state", () => { expect( - buildDraftCommandConfig({ - selection: { - provider: "codex", - modelId: "gpt-5.4", - modeId: "auto", - thinkingOptionId: "high", - availableModels: [], - modeOptions: [{ id: "auto", label: "Auto" }], - }, + buildDraftCommandTarget({ + selection: codexSelection, cwd: "/repo", effectiveModelId: "gpt-5.4", effectiveThinkingOptionId: "high", }), ).toEqual({ - provider: "codex", - cwd: "/repo", - modeId: "auto", - model: "gpt-5.4", - thinkingOptionId: "high", + status: "ready", + config: { + provider: "codex", + cwd: "/repo", + modeId: "auto", + model: "gpt-5.4", + thinkingOptionId: "high", + }, }); }); }); diff --git a/packages/app/src/composer/draft/input-draft.ts b/packages/app/src/composer/draft/input-draft.ts index 64b50fce0c..0812fb280d 100644 --- a/packages/app/src/composer/draft/input-draft.ts +++ b/packages/app/src/composer/draft/input-draft.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { UserComposerAttachment } from "@/attachments/types"; import type { TextReplacement } from "@/composer/types"; import type { DraftAgentControlsProps } from "@/composer/agent-controls"; -import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query"; +import type { DraftCommandTarget } from "@/hooks/use-agent-commands-query"; import { useAgentFormState, type CreateAgentInitialValues, @@ -16,7 +16,7 @@ import { type DraftKeyInput, } from "@/composer/draft/input-draft-core"; import { - buildDraftCommandConfig, + buildDraftCommandTarget, resolveEffectiveComposerModelId, resolveEffectiveComposerThinkingOptionId, type ProviderSelectionState, @@ -50,7 +50,7 @@ type DraftComposerState = UseAgentFormStateResult & { effectiveThinkingOptionId: string; featureValues: Record | undefined; agentControls: DraftAgentControlsProps; - commandDraftConfig: DraftCommandConfig | undefined; + commandDraft: DraftCommandTarget; }; export interface AgentInputDraft { @@ -287,19 +287,16 @@ export function useAgentInputDraft(input: UseAgentInputDraftInput): AgentInputDr [applyProfileFeatureValues, formState], ); - const commandDraftConfig = useMemo( + const commandDraft = useMemo( () => - composerOptions - ? buildDraftCommandConfig({ - selection: providerSelection, - cwd: workingDir, - effectiveModelId, - effectiveThinkingOptionId, - featureValues: draftFeatureValues, - }) - : undefined, + buildDraftCommandTarget({ + selection: providerSelection, + cwd: workingDir, + effectiveModelId, + effectiveThinkingOptionId, + featureValues: draftFeatureValues, + }), [ - composerOptions, effectiveModelId, effectiveThinkingOptionId, draftFeatureValues, @@ -325,10 +322,10 @@ export function useAgentInputDraft(input: UseAgentInputDraftInput): AgentInputDr onSetFeature: setDraftFeatureValue, onApplyAgentProfile: applyDraftAgentProfile, }), - commandDraftConfig, + commandDraft, }; }, [ - commandDraftConfig, + commandDraft, composerOptions, effectiveModelId, effectiveThinkingOptionId, @@ -358,7 +355,6 @@ export const __private__ = { resolveDraftKey, resolveEffectiveComposerModelId, resolveEffectiveComposerThinkingOptionId, - buildDraftCommandConfig, - buildDraftComposerCommandConfig: buildDraftCommandConfig, + buildDraftCommandTarget, buildDraftAgentControls, }; diff --git a/packages/app/src/composer/draft/workspace-tab.tsx b/packages/app/src/composer/draft/workspace-tab.tsx index dbfd3c4f01..519e22e940 100644 --- a/packages/app/src/composer/draft/workspace-tab.tsx +++ b/packages/app/src/composer/draft/workspace-tab.tsx @@ -682,7 +682,7 @@ export function WorkspaceDraftAgentTab({ autoFocus={shouldAutoFocusWorkspaceDraftComposer({ isPaneFocused, isSubmitting })} autoFocusKey={String(draftInput.attachmentFocusRequestId)} onFocusInput={handleFocusInputCallback} - commandDraftConfig={composerState.commandDraftConfig} + commandDraft={composerState.commandDraft} agentControls={composerAgentControls} isCompactLayout={isCompactComposerLayout} /> diff --git a/packages/app/src/composer/index.tsx b/packages/app/src/composer/index.tsx index 6380edc856..ef6e27d3ac 100644 --- a/packages/app/src/composer/index.tsx +++ b/packages/app/src/composer/index.tsx @@ -62,7 +62,7 @@ import { } from "./input/input"; import type { ImageAttachment, MessagePayload, TextReplacement } from "./types"; import { ICON_SIZE, type Theme } from "@/styles/theme"; -import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query"; +import type { DraftCommandTarget } from "@/hooks/use-agent-commands-query"; import { encodeImages } from "@/utils/encode-images"; import { focusWithRetries } from "@/utils/web-focus"; import { @@ -973,8 +973,8 @@ interface ComposerProps { autoFocusKey?: string; /** Callback to expose a focus function to parent components (desktop only). */ onFocusInput?: (focus: () => void) => void; - /** Optional draft context for listing commands before an agent exists. */ - commandDraftConfig?: DraftCommandConfig; + /** Draft context for listing commands before an agent exists. Omitted for running agents. */ + commandDraft?: DraftCommandTarget; /** Called when a message is about to be sent (any path: keyboard, dictation, queued). */ onMessageSent?: () => void; onComposerHeightChange?: (height: number) => void; @@ -1264,7 +1264,7 @@ function ComposerContentImpl({ autoFocus = false, autoFocusKey, onFocusInput, - commandDraftConfig, + commandDraft, onMessageSent, onComposerHeightChange, onAttentionInputFocus, @@ -2346,7 +2346,7 @@ function ComposerContentImpl({ setUserInput: replaceUserInput, serverId, agentId, - draftConfig: commandDraftConfig, + draft: commandDraft, canExecuteClientSlashCommand: buildOutgoingAttachments(attachments).length === 0, onClientSlashCommand: runClientSlashCommand, pluginClientSlashCommands, @@ -2355,7 +2355,7 @@ function ComposerContentImpl({ replaceUserInput, serverId, agentId, - commandDraftConfig, + commandDraft, buildOutgoingAttachments, attachments, runClientSlashCommand, diff --git a/packages/app/src/hooks/use-agent-autocomplete.ts b/packages/app/src/hooks/use-agent-autocomplete.ts index 5ab515d8b1..fe6f98c3f1 100644 --- a/packages/app/src/hooks/use-agent-autocomplete.ts +++ b/packages/app/src/hooks/use-agent-autocomplete.ts @@ -7,6 +7,7 @@ import { useAgentCommandsQuery, type AgentSlashCommand, type DraftCommandConfig, + type DraftCommandTarget, } from "./use-agent-commands-query"; import { orderAutocompleteOptions } from "@/components/ui/autocomplete-utils"; import { useAutocomplete } from "./use-autocomplete"; @@ -34,7 +35,7 @@ interface UseAgentAutocompleteInput { setUserInput: (nextValue: string) => void; serverId: string; agentId: string; - draftConfig?: DraftCommandConfig; + draft?: DraftCommandTarget; onAutocompleteApplied?: () => void; onClientSlashCommand?: (command: ClientSlashCommand) => void; canExecuteClientSlashCommand?: boolean; @@ -117,18 +118,13 @@ type AvailableCommand = | { source: "plugin"; command: PluginClientSlashCommand } | { source: "provider"; command: AgentSlashCommand }; -function normalizeDraftCommandConfig( - draftConfig?: DraftCommandConfig, -): DraftCommandConfig | undefined { - if (!draftConfig) { +function resolveDraftQueryConfig(draft?: DraftCommandTarget): DraftCommandConfig | undefined { + if (draft?.status !== "ready") { return undefined; } + const draftConfig = draft.config; const cwd = draftConfig.cwd.trim(); - if (!cwd) { - return undefined; - } - const modeId = draftConfig.modeId?.trim() ?? ""; const model = draftConfig.model?.trim() ?? ""; const thinkingOptionId = draftConfig.thinkingOptionId?.trim() ?? ""; @@ -337,6 +333,35 @@ function resolveAutocompleteErrorMessage(args: { return undefined; } +// A draft's agentId is a draft key the daemon does not know, so a draft lists +// commands by its config only, and not at all until that config is complete. +function resolveDraftCommandContext(draft: DraftCommandTarget | undefined) { + const queryDraftConfig = resolveDraftQueryConfig(draft); + const isDraftContext = draft !== undefined; + return { + isDraftContext, + queryDraftConfig, + canListCommands: !isDraftContext || queryDraftConfig !== undefined, + }; +} + +function resolveAutocompleteEmptyText(args: { + mode: AutocompleteMode; + draft: DraftCommandTarget | undefined; + t: TFunction; +}): string { + if (args.mode === "file") { + return args.t("agentAutocomplete.noFiles"); + } + if (args.draft?.status === "needs-project") { + return args.t("agentAutocomplete.chooseProjectForCommands"); + } + if (args.draft?.status === "needs-provider") { + return args.t("agentAutocomplete.chooseModelForCommands"); + } + return args.t("agentAutocomplete.noCommands"); +} + export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAutocompleteResult { const { t } = useTranslation(); const { @@ -345,7 +370,7 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut setUserInput, serverId, agentId, - draftConfig, + draft, onAutocompleteApplied, onClientSlashCommand, canExecuteClientSlashCommand, @@ -380,13 +405,10 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut return () => clearTimeout(timer); }, [fileFilterQuery]); - const normalizedDraftConfig = useMemo( - () => normalizeDraftCommandConfig(draftConfig), - [draftConfig], + const { isDraftContext, queryDraftConfig, canListCommands } = useMemo( + () => resolveDraftCommandContext(draft), + [draft], ); - - const isDraftContext = normalizedDraftConfig !== undefined; - const queryDraftConfig = normalizedDraftConfig; const canLoadCommands = resolveCanLoadCommands({ serverId, agentId, isDraftContext }); const agentCwd = useSessionStore( @@ -410,17 +432,14 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut autocompleteCwd, }); - const { - commands, - isLoading: isCommandsLoading, - isError, - error, - } = useAgentCommandsQuery({ + const commandsQuery = useAgentCommandsQuery({ serverId, agentId, - enabled: mode === "command" && canLoadCommands, + enabled: mode === "command" && canLoadCommands && canListCommands, draftConfig: queryDraftConfig, }); + const { commands, isError, error } = commandsQuery; + const isCommandsLoading = canListCommands && commandsQuery.isLoading; const isVisible = canShowAutocomplete && !(mode === "command" && isCommandsLoading); @@ -587,8 +606,7 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut mode === "file" ? t("agentAutocomplete.searchingWorkspace") : t("agentAutocomplete.loadingCommands"); - const emptyText = - mode === "file" ? t("agentAutocomplete.noFiles") : t("agentAutocomplete.noCommands"); + const emptyText = resolveAutocompleteEmptyText({ mode, draft, t }); return { isVisible, diff --git a/packages/app/src/hooks/use-agent-commands-query.ts b/packages/app/src/hooks/use-agent-commands-query.ts index e57184f536..eddc0ab02b 100644 --- a/packages/app/src/hooks/use-agent-commands-query.ts +++ b/packages/app/src/hooks/use-agent-commands-query.ts @@ -17,6 +17,12 @@ export interface AgentSlashCommand { export type DraftCommandConfig = AgentCommandsDraftConfig; +/** What a draft composer can list commands for, before an agent exists. */ +export type DraftCommandTarget = + | { status: "ready"; config: DraftCommandConfig } + | { status: "needs-project" } + | { status: "needs-provider" }; + interface ListAgentCommandsOptions { agentId: string; draftConfig?: DraftCommandConfig; diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 05ef39bda6..b32fa13c70 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -1522,6 +1522,8 @@ export const ar: TranslationResources = { noFiles: "لم يتم العثور على ملفات أو أدلة", noCommands: "لم يتم العثور على أي أوامر", failedToLoad: "فشل التحميل", + chooseProjectForCommands: "اختر مشروعًا لعرض الأوامر", + chooseModelForCommands: "اختر نموذجًا لعرض الأوامر", }, loadOlderHistory: { failed: "تعذر تحميل السجل الأقدم", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index 7ec3c1018d..ef1e65f2ef 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -1547,6 +1547,8 @@ export const en = { noFiles: "No files or directories found", noCommands: "No commands found", failedToLoad: "Failed to load", + chooseProjectForCommands: "Choose a project to see commands", + chooseModelForCommands: "Select a model to see commands", }, loadOlderHistory: { failed: "Couldn't load older history", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index a285459213..0d4dc5a43e 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -1568,6 +1568,8 @@ export const es: TranslationResources = { noFiles: "No se encontraron archivos ni directorios", noCommands: "No se encontraron comandos", failedToLoad: "No se pudo cargar", + chooseProjectForCommands: "Elige un proyecto para ver los comandos", + chooseModelForCommands: "Selecciona un modelo para ver los comandos", }, loadOlderHistory: { failed: "No se pudo cargar el historial anterior", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index 738ac68589..e3721eb523 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -1572,6 +1572,8 @@ export const fr: TranslationResources = { noFiles: "Aucun fichier ou répertoire trouvé", noCommands: "Aucune commande trouvée", failedToLoad: "Échec du chargement", + chooseProjectForCommands: "Choisissez un projet pour voir les commandes", + chooseModelForCommands: "Sélectionnez un modèle pour voir les commandes", }, loadOlderHistory: { failed: "Impossible de charger l'ancien historique", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index 09ecc45711..beee86a4f4 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -1539,6 +1539,8 @@ export const ja: TranslationResources = { noFiles: "ファイルまたはディレクトリが見つかりません", noCommands: "コマンドが見つかりません", failedToLoad: "読み込みに失敗しました", + chooseProjectForCommands: "コマンドを表示するにはプロジェクトを選択してください", + chooseModelForCommands: "コマンドを表示するにはモデルを選択してください", }, loadOlderHistory: { failed: "古い履歴を読み込めませんでした", diff --git a/packages/app/src/i18n/resources/ko.ts b/packages/app/src/i18n/resources/ko.ts index 145a864761..d70c345137 100644 --- a/packages/app/src/i18n/resources/ko.ts +++ b/packages/app/src/i18n/resources/ko.ts @@ -1532,6 +1532,8 @@ export const ko: TranslationResources = { noFiles: "파일 또는 디렉터리를 찾을 수 없습니다", noCommands: "명령을 찾을 수 없습니다", failedToLoad: "불러오지 못했습니다", + chooseProjectForCommands: "명령을 보려면 프로젝트를 선택하세요", + chooseModelForCommands: "명령을 보려면 모델을 선택하세요", }, loadOlderHistory: { failed: "이전 기록을 불러올 수 없습니다", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index be65df9f93..0ab0df227d 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -1553,6 +1553,8 @@ export const ptBR: TranslationResources = { noFiles: "Nenhum arquivo ou diretório encontrado", noCommands: "Nenhum comando encontrado", failedToLoad: "Falha ao carregar", + chooseProjectForCommands: "Escolha um projeto para ver os comandos", + chooseModelForCommands: "Selecione um modelo para ver os comandos", }, loadOlderHistory: { failed: "Não foi possível carregar o histórico mais antigo", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 24df5f6478..29090f3ad4 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -1551,6 +1551,8 @@ export const ru: TranslationResources = { noFiles: "Файлы и каталоги не найдены", noCommands: "Команды не найдены", failedToLoad: "Не удалось загрузить", + chooseProjectForCommands: "Выберите проект, чтобы увидеть команды", + chooseModelForCommands: "Выберите модель, чтобы увидеть команды", }, loadOlderHistory: { failed: "Не удалось загрузить старую историю.", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index 850a25724f..da4e6fdfb3 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -1505,6 +1505,8 @@ export const zhCN: TranslationResources = { noFiles: "没有找到文件或目录", noCommands: "没有找到 commands", failedToLoad: "加载失败", + chooseProjectForCommands: "选择项目以查看命令", + chooseModelForCommands: "选择模型以查看命令", }, loadOlderHistory: { failed: "无法加载更早历史", diff --git a/packages/app/src/provider-selection/provider-selection.ts b/packages/app/src/provider-selection/provider-selection.ts index ca8be17b85..ae00dd9528 100644 --- a/packages/app/src/provider-selection/provider-selection.ts +++ b/packages/app/src/provider-selection/provider-selection.ts @@ -5,7 +5,7 @@ import type { ProviderSnapshotEntry, } from "@getpaseo/protocol/agent-types"; import type { AgentProviderDefinition } from "@getpaseo/protocol/provider-manifest"; -import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query"; +import type { DraftCommandTarget } from "@/hooks/use-agent-commands-query"; import { i18n } from "@/i18n/i18next"; import { compareMatchScores, scoreTextFields } from "@getpaseo/protocol/search/text-match"; import { filterSelectableModels } from "./model-catalog"; @@ -286,19 +286,22 @@ export function resolveEffectiveComposerThinkingOptionId( return selectedModelDefinition?.defaultThinkingOptionId ?? ""; } -export function buildDraftCommandConfig(input: { +export function buildDraftCommandTarget(input: { selection: ProviderSelectionState; cwd: string; effectiveModelId: string; effectiveThinkingOptionId: string; featureValues?: Record; -}): DraftCommandConfig | undefined { +}): DraftCommandTarget { const cwd = input.cwd.trim(); - if (!input.selection.provider || !cwd) { - return undefined; + if (!cwd) { + return { status: "needs-project" }; + } + if (!input.selection.provider) { + return { status: "needs-provider" }; } - return { + const config = { provider: input.selection.provider, cwd, ...(input.selection.modeOptions.length > 0 && input.selection.modeId !== "" @@ -310,6 +313,7 @@ export function buildDraftCommandConfig(input: { : {}), ...(input.featureValues ? { featureValues: input.featureValues } : {}), }; + return { status: "ready", config }; } export function resolveSubmissionReadiness(input: { diff --git a/packages/app/src/screens/new-workspace-screen.tsx b/packages/app/src/screens/new-workspace-screen.tsx index 13f3098739..9e0d25144c 100644 --- a/packages/app/src/screens/new-workspace-screen.tsx +++ b/packages/app/src/screens/new-workspace-screen.tsx @@ -2422,7 +2422,7 @@ export function NewWorkspaceScreen({ clearDraft={handleClearDraft} autoFocus autoFocusKey={launchFocusKey} - commandDraftConfig={composerState?.commandDraftConfig} + commandDraft={composerState?.commandDraft} agentControls={agentControlsWithDisabled} /> );