fix(web): only block submit during real env prepare, not every STARTING
The "agent still being set up" submit gate was triggering on every session that briefly transitions through STARTING — including local quick-chat, which has no prepare phase and has its agent live within ~100ms. The eager-init quick-chat e2e tests (chat/quick-chat.spec.ts and chat/message-queue.spec.ts) wait for the editor to become editable, then press Cmd+Enter. The race made wrappedSubmit a no-op exactly when the test was firing — message dropped, agent never saw it, downstream assertions failed. Split the merged isStarting flag in useSessionState: keep the broad flag (still used for the workspace-prep loading UI), and expose isPreparingEnvironment as a separate signal that's only true while prepareProgress.status === "preparing". Thread it through chat-input- area → chat-input-container → useChatInputContainer and gate the submit-disabled tooltip on it instead of the broad isStarting. Net: Docker / Sprites bootstrap still surfaces the "agent still being set up" message and disables submit; quick-chat / local sessions stop silently dropping in-flight messages.
This commit is contained in:
@@ -413,6 +413,7 @@ export function ChatInputArea({
|
||||
onPlanModeChange={handlePlanModeChange}
|
||||
isAgentBusy={isAgentBusy}
|
||||
isStarting={panelState.isStarting}
|
||||
isPreparingEnvironment={panelState.isPreparingEnvironment}
|
||||
isMoving={isMoving}
|
||||
isSending={isSending}
|
||||
onCancel={handleCancelTurn}
|
||||
|
||||
@@ -57,6 +57,11 @@ type ChatInputContainerProps = {
|
||||
onPlanModeChange: (enabled: boolean) => void;
|
||||
isAgentBusy: boolean;
|
||||
isStarting: boolean;
|
||||
/** True only while a containerized executor is bootstrapping (Docker
|
||||
* prepare, Sprites sandbox spin-up). Distinct from the brief STARTING
|
||||
* state every session — including local quick-chat — passes through;
|
||||
* see useSessionState. */
|
||||
isPreparingEnvironment?: boolean;
|
||||
isMoving?: boolean;
|
||||
isSending: boolean;
|
||||
onCancel: () => void;
|
||||
@@ -292,6 +297,7 @@ export const ChatInputContainer = forwardRef<ChatInputContainerHandle, ChatInput
|
||||
sessionId,
|
||||
isSending,
|
||||
isStarting,
|
||||
isPreparingEnvironment: props.isPreparingEnvironment ?? false,
|
||||
isMoving,
|
||||
isFailed: p.isFailed,
|
||||
needsRecovery: props.needsRecovery ?? false,
|
||||
|
||||
@@ -11,6 +11,7 @@ function renderInputState(overrides: Partial<Parameters<typeof useChatInputConta
|
||||
sessionId: "session-1",
|
||||
isSending: false,
|
||||
isStarting: false,
|
||||
isPreparingEnvironment: false,
|
||||
isMoving: false,
|
||||
isFailed: false,
|
||||
needsRecovery: false,
|
||||
|
||||
@@ -16,6 +16,11 @@ type UseChatInputContainerParams = {
|
||||
sessionId: string | null;
|
||||
isSending: boolean;
|
||||
isStarting: boolean;
|
||||
/** True only during a real Docker/Sprites prepare phase. Different from
|
||||
* `isStarting`, which fires for every session that's transitioning
|
||||
* through STARTING (including local quick-chat). Submit gating uses
|
||||
* this narrower flag so quick-chat sessions aren't blocked. */
|
||||
isPreparingEnvironment: boolean;
|
||||
isMoving: boolean;
|
||||
isFailed: boolean;
|
||||
needsRecovery: boolean;
|
||||
@@ -82,6 +87,7 @@ function getInputPlaceholder(
|
||||
|
||||
function computeDerivedState(params: {
|
||||
isStarting: boolean;
|
||||
isPreparingEnvironment: boolean;
|
||||
isMoving: boolean;
|
||||
isSending: boolean;
|
||||
isFailed: boolean;
|
||||
@@ -102,8 +108,15 @@ function computeDerivedState(params: {
|
||||
params.isFailed ||
|
||||
params.needsRecovery ||
|
||||
params.executorUnavailable;
|
||||
const submitDisabled = params.isStarting || isDisabled;
|
||||
const submitDisabledReason = params.isStarting ? "The agent is still being set up." : undefined;
|
||||
// Only an active container/sandbox prepare blocks the submit and surfaces
|
||||
// the "agent still being set up" tooltip. The brief STARTING transition
|
||||
// every session passes through (including local quick-chat) intentionally
|
||||
// does NOT block — the e2e suite presses Cmd+Enter as soon as the editor
|
||||
// becomes editable, and a STARTING-only block silently dropped messages.
|
||||
const submitDisabled = params.isPreparingEnvironment || isDisabled;
|
||||
const submitDisabledReason = params.isPreparingEnvironment
|
||||
? "The agent is still being set up."
|
||||
: undefined;
|
||||
const hasClarification = !!(params.pendingClarification && params.onClarificationResolved);
|
||||
const hasPendingComments = !!(
|
||||
params.pendingCommentsByFile && Object.keys(params.pendingCommentsByFile).length > 0
|
||||
@@ -129,26 +142,11 @@ function computeDerivedState(params: {
|
||||
}
|
||||
|
||||
export function useChatInputContainer(params: UseChatInputContainerParams) {
|
||||
const {
|
||||
ref,
|
||||
sessionId,
|
||||
isSending,
|
||||
isStarting,
|
||||
isMoving,
|
||||
isFailed,
|
||||
needsRecovery,
|
||||
executorUnavailable,
|
||||
isAgentBusy,
|
||||
hasAgentCommands,
|
||||
placeholder,
|
||||
contextItems,
|
||||
pendingClarification,
|
||||
onClarificationResolved,
|
||||
pendingCommentsByFile,
|
||||
showRequestChangesTooltip,
|
||||
onRequestChangesTooltipDismiss,
|
||||
onSubmit,
|
||||
} = params;
|
||||
const { ref, sessionId, isSending, isStarting, isPreparingEnvironment, isMoving } = params;
|
||||
const { isFailed, needsRecovery, executorUnavailable, isAgentBusy, hasAgentCommands } = params;
|
||||
const { placeholder, contextItems, pendingClarification, onClarificationResolved } = params;
|
||||
const { pendingCommentsByFile, showRequestChangesTooltip } = params;
|
||||
const { onRequestChangesTooltipDismiss, onSubmit } = params;
|
||||
|
||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||
const [showNewSessionDialog, setShowNewSessionDialog] = useState(false);
|
||||
@@ -202,6 +200,7 @@ export function useChatInputContainer(params: UseChatInputContainerParams) {
|
||||
|
||||
const derived = computeDerivedState({
|
||||
isStarting,
|
||||
isPreparingEnvironment,
|
||||
isMoving,
|
||||
isSending,
|
||||
isFailed,
|
||||
|
||||
@@ -48,5 +48,11 @@ export function useSessionState(sessionId: string | null) {
|
||||
...flags,
|
||||
isStarting: flags.isStarting || isPreparingEnvironment,
|
||||
isWorking: flags.isWorking || isPreparingEnvironment,
|
||||
// Exposed separately so consumers that gate on "is the executor still
|
||||
// bootstrapping" (e.g. the chat input "agent is still being set up"
|
||||
// tooltip) can distinguish a brief STARTING transition — which every
|
||||
// session passes through, including local quick-chat — from an active
|
||||
// Docker / Sprites prepare-environment phase.
|
||||
isPreparingEnvironment,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user