Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
0ed8221
πŸ€– refactor: dedupe memory sweep recordUsage callbacks
mux-bot[bot] Jul 8, 2026
e57fbe2
πŸ€– refactor: dedupe memory scope-full cap check into helper
mux-bot[bot] Jul 9, 2026
3fd83d9
refactor: dedupe blockquote line formatting in bash monitor wake prompt
mux-bot[bot] Jul 9, 2026
889e6d2
refactor: dedupe tool_search removal in prepareToolSearch
mux-bot[bot] Jul 9, 2026
70e3467
refactor: dedupe anthropic cache-create token extraction in usageHelpers
mux-bot[bot] Jul 10, 2026
3edcc0e
refactor: dedupe capability-model thinking policy resolution
mux-bot[bot] Jul 10, 2026
f4edad3
refactor: dedupe queue entry clear-callback projection in MessageQueue
mux-bot[bot] Jul 11, 2026
171fa18
refactor: dedupe OpenAI-origin model check in cacheStrategy
mux-bot[bot] Jul 12, 2026
45ff959
refactor: dedupe tool-call-execution-start emit in StreamManager
mux-bot[bot] Jul 13, 2026
266edd2
refactor: dedupe model-parameter extras merge in aiService
mux-bot[bot] Jul 13, 2026
5b49abb
refactor: unify legacy tool_search part rename helper in toolCatalog
mux-bot[bot] Jul 14, 2026
5d8c86c
refactor: drop duplicated context-cap rationale comment in codexOAuth
mux-bot[bot] Jul 14, 2026
8eef696
refactor: dedupe flat-section pinned block resolution in pinnedReorder
mux-bot[bot] Jul 15, 2026
4348f91
refactor: dedupe JSON-wrapped tool-output unwrap in workflowRunMessages
mux-bot[bot] Jul 15, 2026
bf7e13f
refactor: hoist errorType local in finalizeWorkspaceTurnFromStreamError
mux-bot[bot] Jul 15, 2026
bad97c3
refactor: extract buildSkillDescriptor helper for skill discovery
mux-bot[bot] Jul 15, 2026
b60b29c
refactor: hoist duplicated Date.parse(createdAt) in bash monitor deli…
mux-bot[bot] Jul 16, 2026
a2613ba
refactor: extract awaitPendingLoad helper in DevToolsService
mux-bot[bot] Jul 16, 2026
a5a0339
refactor: dedupe MCP OAuth redirect URI resolution in router
mux-bot[bot] Jul 18, 2026
dd6bb7d
refactor: extract getTotalTokens helper for total-token sums
mux-bot[bot] Jul 20, 2026
27be013
refactor: hoist duplicated dedupeKeys snapshot in removeByDedupeKeyPr…
mux-bot[bot] Jul 20, 2026
1583c59
refactor: dedupe settled workspace-turn reconciliation guard
mux-bot[bot] Jul 21, 2026
0615ed8
refactor: dedupe fire-and-forget archive-all catch in TaskGroupListItem
mux-bot[bot] Jul 21, 2026
a06d433
refactor: extract someDescendantAgentTaskWorkspace helper for sticky-…
mux-bot[bot] Jul 21, 2026
e0dfe9f
refactor: drop duplicated Kimi K3 max-effort rationale in providerOpt…
mux-bot[bot] Jul 22, 2026
55556cb
refactor: drop redundant structuredOutput guard at subagent report ca…
mux-bot[bot] Jul 22, 2026
66f18c1
refactor: extract isZipMediaType helper for staged attachment media-t…
mux-bot[bot] Jul 23, 2026
3676cc7
refactor: hoist duplicated goal-bypass attachment check in ChatInput
mux-bot[bot] Jul 24, 2026
c98f3ee
refactor: dedupe anchored Anthropic model-id regex construction
mux-bot[bot] Jul 24, 2026
c705442
refactor: dedupe MCP header telemetry flag derivation in router
mux-bot[bot] Jul 25, 2026
8bb2330
refactor: extract _child_dirs helper for job folder discovery
mux-bot[bot] Jul 25, 2026
c0500b4
refactor: drop redundant "exec" fallback duplication for normalizeAge…
mux-bot[bot] Jul 27, 2026
ff03c1e
refactor: name the digest truncation bounds in the timeline mapper
mux-bot[bot] Jul 28, 2026
e6684e1
refactor: dedupe defensive unknown-field reads in the timeline mapper
mux-bot[bot] Jul 29, 2026
6e8ae63
refactor: share the mobile-touch media query constant
mux-bot[bot] Jul 29, 2026
61b7191
refactor: share the docked toast overlay placement class
mux-bot[bot] Jul 29, 2026
821c89e
refactor: share the primary mouse button guard
mux-bot[bot] Jul 29, 2026
49fc7e7
refactor: name ModelSelector row selection/highlight state
mux-bot[bot] Jul 30, 2026
33d43e9
refactor: share the workspace footer pill class
mux-bot[bot] Jul 30, 2026
f49e823
refactor: share the safe inactive-animation pause install
mux-bot[bot] Aug 1, 2026
ff723ad
refactor: share the bash monitor wake message predicate
mux-bot[bot] Aug 1, 2026
c0e4b70
refactor: dedupe monitor disposition branch in terminate()
mux-bot[bot] Aug 1, 2026
d2b0fbf
refactor: share the queued-message action button classes
mux-bot[bot] Aug 2, 2026
beaf641
refactor: extract parseSubagentReportFromMessage helper
mux-bot[bot] Aug 2, 2026
87ae2fe
refactor: drop stale userOverridable references from experiments UI
mux-bot[bot] Aug 3, 2026
93b69ac
refactor: reuse isTaskAwaitMessage predicate in transcript projection
mux-bot[bot] Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions benchmarks/terminal_bench/prepare_leaderboard_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ def get_model_from_config(config_path: Path) -> str | None:
return None


def _child_dirs(path: Path) -> list[Path]:
"""List the immediate subdirectories of a directory, skipping plain files."""
return [child for child in path.iterdir() if child.is_dir()]


def _is_job_folder(path: Path) -> bool:
"""Check if a directory looks like a job folder (contains trial dirs with config.json)."""
if not path.is_dir():
Expand Down Expand Up @@ -249,20 +254,14 @@ def find_job_folders(artifacts_dir: Path) -> list[Path]:
# Check for direct jobs/ folder
direct_jobs = artifacts_dir / "jobs"
if direct_jobs.exists():
for item in direct_jobs.iterdir():
if item.is_dir():
job_folders.append(item)
job_folders.extend(_child_dirs(direct_jobs))
return job_folders

# Check for per-artifact structure
for artifact_dir in artifacts_dir.iterdir():
if not artifact_dir.is_dir():
continue
for artifact_dir in _child_dirs(artifacts_dir):
jobs_dir = artifact_dir / "jobs"
if jobs_dir.exists():
for item in jobs_dir.iterdir():
if item.is_dir():
job_folders.append(item)
job_folders.extend(_child_dirs(jobs_dir))

return job_folders

Expand Down
4 changes: 2 additions & 2 deletions src/browser/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
LEFT_SIDEBAR_DEFAULT_WIDTH_PX,
LEFT_SIDEBAR_MAX_WIDTH_PX,
LEFT_SIDEBAR_MIN_WIDTH_PX,
MOBILE_TOUCH_MEDIA_QUERY,
} from "@/constants/layout";
import { buildCoreSources, type BuildSourcesParams } from "./utils/commands/sources";

Expand Down Expand Up @@ -225,8 +226,7 @@ function AppInner() {
// because the sidebar width is controlled by CSS and shouldn't rewrite the user's desktop
// width preference.
const isMobileTouch =
typeof window !== "undefined" &&
window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches;
typeof window !== "undefined" && window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches;
if (isMobileTouch) {
return Number.POSITIVE_INFINITY;
}
Expand Down
6 changes: 4 additions & 2 deletions src/browser/components/ChatPane/ChatPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
mergeConsecutiveStreamErrors,
computeBashOutputGroupInfos,
shouldBypassDeferredMessages,
isBashMonitorWakeMessage,
} from "@/browser/utils/messages/messageUtils";
import { computeTaskReportLinking } from "@/browser/utils/messages/taskReportLinking";
import { BashCollapsedSummaryModeProvider } from "@/browser/features/Tools/BashCollapsedSummaryModeContext";
Expand Down Expand Up @@ -103,6 +104,7 @@ import {
useBackgroundBashError,
} from "@/browser/contexts/BackgroundBashContext";
import { hasWorkspaceRepository } from "@/browser/utils/workspaceCapabilities";
import { isPrimaryMouseButton } from "@/browser/utils/events";
import {
buildEditingStateFromDisplayed,
canEditDisplayedUserMessage,
Expand Down Expand Up @@ -837,7 +839,7 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
);

const handleComposerDockMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
if (event.defaultPrevented || event.button !== 0) {
if (event.defaultPrevented || !isPrimaryMouseButton(event)) {
return;
}
const control = resolveComposerControlFocusTarget(event.target, composerDockRef.current);
Expand Down Expand Up @@ -895,7 +897,7 @@ const ChatPaneContent: React.FC<ChatPaneContentProps> = (props) => {
const userHistoryIds: string[] = [];
for (const message of deferredMessages) {
// Monitor wake events should not interrupt navigation between human prompts.
if (message.type === "user" && message.bashMonitorWake == null) {
if (message.type === "user" && !isBashMonitorWakeMessage(message)) {
userHistoryIds.push(message.historyId);
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/browser/components/ChatPane/WorkspaceFooterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ function WorkspaceBranchControls(props: {
);
}

// Shared by the footer's interactive pills (repository link, "Last prompt") so they keep reading as
// one affordance family: restyling one silently drifting from the other is the failure mode here.
const FOOTER_PILL_CLASS =
"text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 transition-colors focus-visible:ring-1";

function FooterRepositoryLabel(props: { workspaceId: string; projectLabel: string }) {
const workspacePR = useWorkspacePR(props.workspaceId);

Expand All @@ -161,7 +166,7 @@ function FooterRepositoryLabel(props: { workspaceId: string; projectLabel: strin
target="_blank"
rel="noopener noreferrer"
data-testid="workspace-footer-repository"
className="text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 transition-colors focus-visible:ring-1"
className={FOOTER_PILL_CLASS}
>
<Github className="h-3 w-3 shrink-0" aria-hidden="true" />
<span className="font-mono">{slug}</span>
Expand Down Expand Up @@ -238,7 +243,8 @@ function FooterLastPrompt(props: { workspaceId: string }) {
<PopoverTrigger asChild>
<button
type="button"
className="text-muted hover:bg-hover hover:text-foreground focus-visible:ring-accent flex h-5 shrink-0 cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent px-1.5 transition-colors focus-visible:ring-1"
// The extra classes are <button> resets; the pill styling itself is shared.
className={cn(FOOTER_PILL_CLASS, "cursor-pointer border-0 bg-transparent")}
data-testid="workspace-footer-last-prompt"
onKeyDown={stopKeyboardPropagation}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from "react";
import { useAPI } from "@/browser/contexts/API";

const wrapperClassName =
"pointer-events-none absolute right-[15px] bottom-full left-[15px] z-[1000] mb-2 [&>*]:pointer-events-auto";
import { CHAT_DOCK_TOAST_OVERLAY_CLASS } from "@/constants/layout";

/**
* Connection status banner that uses the same *overlay placement* as ChatInputToast.
Expand Down Expand Up @@ -55,7 +53,7 @@ export const ConnectionStatusToast: React.FC<ConnectionStatusToastProps> = ({ wr

if (!wrap) return content;

return <div className={wrapperClassName}>{content}</div>;
return <div className={CHAT_DOCK_TOAST_OVERLAY_CLASS}>{content}</div>;
}

if (apiState.status === "error") {
Expand All @@ -75,7 +73,7 @@ export const ConnectionStatusToast: React.FC<ConnectionStatusToastProps> = ({ wr

if (!wrap) return content;

return <div className={wrapperClassName}>{content}</div>;
return <div className={CHAT_DOCK_TOAST_OVERLAY_CLASS}>{content}</div>;
}

return null;
Expand Down
17 changes: 9 additions & 8 deletions src/browser/components/ModelSelector/ModelSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,33 +406,34 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
const modelProvider = getModelProvider(model);
const showProviderLabel =
modelProvider.length > 0 && duplicateModelNames.has(modelName);
// Name the row's selection/highlight state once so the option class, ARIA state,
// and accent styling below can't drift apart (mirrors AgentModePicker).
const isHighlighted = index === highlightedIndex;
const isSelected = value === model;

return (
<div
key={model}
data-highlighted={index === highlightedIndex}
data-highlighted={isHighlighted}
onMouseEnter={() => setHighlightedIndex(index)}
className={composerPickerOptionClass(
{
isHighlighted: index === highlightedIndex,
isSelected: value === model,
},
{ isHighlighted, isSelected },
"py-1",
hiddenSet.has(model) && "opacity-50"
)}
onClick={() => handleSelectModel(model)}
role="option"
aria-selected={value === model}
aria-selected={isSelected}
>
<ProviderIcon
provider={modelProvider}
className={cn(
"h-3 w-3 shrink-0",
value === model ? "text-accent" : "text-muted"
isSelected ? "text-accent" : "text-muted"
)}
/>
<span className="flex min-w-0 flex-1 items-baseline gap-1">
<span className={cn("min-w-0 truncate", value === model && "text-accent")}>
<span className={cn("min-w-0 truncate", isSelected && "text-accent")}>
{formatModelDisplayName(modelName)}
</span>
{showProviderLabel && (
Expand Down
14 changes: 8 additions & 6 deletions src/browser/components/ProjectSidebar/TaskGroupListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ function getAggregateVisualState(props: TaskGroupListItemProps): VisualState {

export function TaskGroupListItem(props: TaskGroupListItemProps) {
const contextMenu = useContextMenuPosition();
// Variant-group archive is fire-and-forget from the row.
const archiveAll = (buttonElement: HTMLElement) => {
props.onArchiveAll?.(buttonElement).catch(() => {
// The sidebar owner surfaces archive failures through its shared error UI.
});
};
const hasRunningWork = props.runningCount > 0;
const aggregateState = getAggregateVisualState(props);
const statusDescriptionId = `task-group-status-${props.groupId}`;
Expand Down Expand Up @@ -104,9 +110,7 @@ export function TaskGroupListItem(props: TaskGroupListItemProps) {
if (props.onArchiveAll && matchesKeybind(event, KEYBINDS.ARCHIVE_WORKSPACE)) {
event.preventDefault();
stopKeyboardPropagation(event);
props.onArchiveAll(event.currentTarget).catch(() => {
// The sidebar owner surfaces archive failures through its shared error UI.
});
archiveAll(event.currentTarget);
return;
}
if (event.target !== event.currentTarget) {
Expand Down Expand Up @@ -191,9 +195,7 @@ export function TaskGroupListItem(props: TaskGroupListItemProps) {
variant="destructive"
onClick={(event) => {
contextMenu.close();
props.onArchiveAll?.(event.currentTarget).catch(() => {
// The sidebar owner surfaces archive failures through its shared error UI.
});
archiveAll(event.currentTarget);
}}
/>
</PositionedMenu>
Expand Down
10 changes: 6 additions & 4 deletions src/browser/components/WorkspaceMenuBar/WorkspaceMenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ import { forkWorkspace } from "@/browser/utils/chatCommands";
import { SCRATCH_PROJECT_CONFIG_KEY, SCRATCH_PROJECT_NAME } from "@/common/constants/scratch";
import { hasWorkspaceRepository } from "@/browser/utils/workspaceCapabilities";
import { stopKeyboardPropagation } from "@/browser/utils/events";
import { WORKSPACE_MENU_BAR_LEFT_SIDEBAR_COLLAPSED_PADDING_PX } from "@/constants/layout";
import {
MOBILE_TOUCH_MEDIA_QUERY,
WORKSPACE_MENU_BAR_LEFT_SIDEBAR_COLLAPSED_PADDING_PX,
} from "@/constants/layout";
import type { AgentSkillDescriptor, AgentSkillIssue } from "@/common/types/agentSkill";

interface WorkspaceMenuBarProps {
Expand Down Expand Up @@ -163,7 +166,7 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({

const handleOpenTerminal = useCallback(() => {
// On mobile touch devices, always use popout since the right sidebar is hidden
const isMobileTouch = window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches;
const isMobileTouch = window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches;
if (onOpenTerminal && !isMobileTouch) {
onOpenTerminal();
} else {
Expand All @@ -173,8 +176,7 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({
}, [workspaceId, openTerminalPopout, runtimeConfig, onOpenTerminal]);

const isTouchMobileScreen =
typeof window !== "undefined" &&
window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches;
typeof window !== "undefined" && window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches;

const isDevcontainerWorkspace = isDevcontainerRuntime(runtimeConfig);
const isRuntimeRunning = isDevcontainerWorkspace && runtimeStatus === "running";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function WorkspaceModeAISync(props: { workspaceId: string }): null {
const modelKey = getModelKey(workspaceId);
const thinkingKey = getThinkingLevelKey(workspaceId);

const normalizedAgentId = normalizeAgentId(agentId, "exec");
const normalizedAgentId = normalizeAgentId(agentId);

const isExplicitAgentSwitch =
prevAgentIdRef.current !== null &&
Expand Down
3 changes: 2 additions & 1 deletion src/browser/components/WorkspaceShell/WorkspaceShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
LEFT_SIDEBAR_DEFAULT_WIDTH_PX,
LEFT_SIDEBAR_MAX_WIDTH_PX,
LEFT_SIDEBAR_MIN_WIDTH_PX,
MOBILE_TOUCH_MEDIA_QUERY,
} from "@/constants/layout";
import { ChatPane } from "../ChatPane/ChatPane";

Expand Down Expand Up @@ -157,7 +158,7 @@ export const WorkspaceShell: React.FC<WorkspaceShellProps> = (props) => {
const handleOpenTerminal = useCallback(
(options?: TerminalSessionCreateOptions) => {
// On mobile touch devices, always use popout since the right sidebar is hidden
const isMobileTouch = window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches;
const isMobileTouch = window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches;
if (isMobileTouch) {
void openTerminalPopout(props.workspaceId, props.runtimeConfig, options);
} else {
Expand Down
6 changes: 2 additions & 4 deletions src/browser/features/ChatInput/ChatInputToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ReactNode } from "react";
import { AlertTriangle, Check } from "lucide-react";
import React, { useEffect, useCallback } from "react";
import { cn } from "@/common/lib/utils";
import { CHAT_DOCK_TOAST_OVERLAY_CLASS } from "@/constants/layout";

const toastTypeStyles: Record<"success" | "error", string> = {
success: "bg-toast-success-bg border border-accent-dark text-toast-success-text",
Expand Down Expand Up @@ -31,9 +32,6 @@ export const SolutionLabel: React.FC<{ children: ReactNode }> = ({ children }) =
<div className="text-muted-light mb-1 text-[10px] uppercase">{children}</div>
);

const wrapperClassName =
"pointer-events-none absolute right-[15px] bottom-full left-[15px] z-[1000] mb-2 [&>*]:pointer-events-auto";

export const ChatInputToast: React.FC<ChatInputToastProps> = ({
toast,
onDismiss,
Expand Down Expand Up @@ -149,5 +147,5 @@ export const ChatInputToast: React.FC<ChatInputToastProps> = ({

if (!wrap) return content;

return <div className={wrapperClassName}>{content}</div>;
return <div className={CHAT_DOCK_TOAST_OVERLAY_CLASS}>{content}</div>;
};
33 changes: 15 additions & 18 deletions src/browser/features/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ import {
COMPOSER_PRO_HIDE_CLASS,
COMPOSER_WORKSPACE_ICON_ONLY_HIDE_CLASS,
CHAT_DOCK_GUTTER_CLASS,
CHAT_DOCK_TOAST_OVERLAY_CLASS,
CREATION_COLUMN_MAX_WIDTH_CLASS,
MOBILE_TOUCH_MEDIA_QUERY,
} from "@/constants/layout";
import { useChatDockColumnWidthClass } from "@/browser/components/ChatPane/chatDockColumn";

Expand Down Expand Up @@ -353,14 +355,12 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
const isStreamStarting = variant === "workspace" ? (props.isStreamStarting ?? false) : false;
const isCompacting = variant === "workspace" ? (props.isCompacting ?? false) : false;
const [isMobileTouch, setIsMobileTouch] = useState(
() =>
typeof window !== "undefined" &&
window.matchMedia("(max-width: 768px) and (pointer: coarse)").matches
() => typeof window !== "undefined" && window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY).matches
);
useEffect(() => {
if (typeof window === "undefined") return;

const mobileTouchMediaQuery = window.matchMedia("(max-width: 768px) and (pointer: coarse)");
const mobileTouchMediaQuery = window.matchMedia(MOBILE_TOUCH_MEDIA_QUERY);
const handleMobileTouchChange = () => {
setIsMobileTouch(mobileTouchMediaQuery.matches);
};
Expand Down Expand Up @@ -895,7 +895,7 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
return;
}

const normalizedAgentId = normalizeAgentId(agentId, "exec");
const normalizedAgentId = normalizeAgentId(agentId);

updatePersistedState<WorkspaceAISettingsByAgentCache>(
getWorkspaceAISettingsByAgentKey(workspaceId),
Expand Down Expand Up @@ -1240,7 +1240,7 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {

const fallbackModel = defaultModel;

const normalizedAgentId = normalizeAgentId(agentId, "exec");
const normalizedAgentId = normalizeAgentId(agentId);

const isExplicitAgentSwitch =
prevCreationAgentIdRef.current !== null &&
Expand Down Expand Up @@ -2548,13 +2548,16 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
discovery: skillDiscovery,
});

// The initial /goal path sets a goal without sending a user message, so
// attachments would be silently dropped. With attachments present, skip
// command processing and send the raw text as a normal message instead.
// Shared by the creation route below and the workspace path (transferred
// creation drafts retried in the composer), which resolve `parsed` and
// `attachments` identically.
const goalCommandBypassedForAttachments = parsed?.type === "goal-set" && attachments.length > 0;

// Route to creation handler for creation variant
if (variant === "creation") {
// The initial /goal path sets a goal without sending a user message, so
// attachments would be silently dropped. With attachments present, skip
// command processing and send the raw text as a normal message instead.
const goalCommandBypassedForAttachments =
parsed?.type === "goal-set" && attachments.length > 0;
const initialSlashCommand =
parsed?.type === "goal-set" && !goalCommandBypassedForAttachments ? parsed : undefined;
if (
Expand Down Expand Up @@ -2659,12 +2662,6 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {

try {
const modelOneShot = parsed?.type === "model-oneshot" ? parsed : null;
// Mirror the creation-composer /goal bypass: with attachments present,
// send the raw text as a normal message instead of processing the
// command, which would drop the files. Transferred staging-failure
// drafts (raw /goal text + staged/pending chips) retry through here.
const goalCommandBypassedForAttachments =
parsed?.type === "goal-set" && attachments.length > 0;
const commandHandled =
modelOneShot || goalCommandBypassedForAttachments
? false
Expand Down Expand Up @@ -3253,7 +3250,7 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
>
<div className={cn(variant === "creation" ? "w-full" : chatDockColumnWidthClass)}>
{/* Toasts (overlay) */}
<div className="pointer-events-none absolute right-[15px] bottom-full left-[15px] z-[1000] mb-2 flex flex-col gap-2 [&>*]:pointer-events-auto">
<div className={cn(CHAT_DOCK_TOAST_OVERLAY_CLASS, "flex flex-col gap-2")}>
<ConnectionStatusToast wrap={false} />
<ChatInputToast
toast={activeToast}
Expand Down
Loading
Loading