diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts index 0dab1c8d83cc6..1ab9b3159e1f9 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.test.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts @@ -34,7 +34,6 @@ const clientSettings: ClientSettings = { confirmThreadArchive: true, confirmThreadDelete: false, confirmThreadUnpin: false, - contextWindowMeterEnabled: false, diffFilesCollapsed: true, diffIgnoreWhitespace: true, diffLayout: "stacked", @@ -52,7 +51,6 @@ const clientSettings: ClientSettings = { glassOpacity: 80, onboardingCompletedAt: null, panelAnimationDurationMs: 0, - planModeEnabled: false, proactivePanelsEnabled: true, showSkillsInSlashMenu: false, persistComposerContextStrip: true, @@ -67,7 +65,6 @@ const clientSettings: ClientSettings = { boringSidebarMultilineRows: false, threadDetailsUsageEnabled: false, threadDetailsItems: {}, - legacySidebarEnabled: false, sidebarWorkingShelfEnabled: false, loadBalancingEnabled: false, loadBalancingWeights: { "environment-1": 75, "environment-2": 0 }, diff --git a/apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx index 8045fb3dd3d4e..717e71362a593 100644 --- a/apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsAppearanceRouteScreen.tsx @@ -8,6 +8,7 @@ import { CodeAppearanceSection } from "./appearance/sections/CodeAppearanceSecti import { TerminalAppearanceSection } from "./appearance/sections/TerminalAppearanceSection"; import { TextAppearanceSection } from "./appearance/sections/TextAppearanceSection"; import { ThemeAppearanceSection } from "./appearance/sections/ThemeAppearanceSection"; +import { ToolCallsAppearanceSection } from "./appearance/sections/ToolCallsAppearanceSection"; export function SettingsAppearanceRouteScreen() { const insets = useSafeAreaInsets(); @@ -28,6 +29,7 @@ export function SettingsAppearanceRouteScreen() { + ); diff --git a/apps/mobile/src/features/settings/ThreadSettingsSections.tsx b/apps/mobile/src/features/settings/ThreadSettingsSections.tsx index 6ba284dc513db..41600619b3a47 100644 --- a/apps/mobile/src/features/settings/ThreadSettingsSections.tsx +++ b/apps/mobile/src/features/settings/ThreadSettingsSections.tsx @@ -22,8 +22,6 @@ import { serverEnvironment } from "../../state/server"; import { useAtomCommand } from "../../state/use-atom-command"; import { SettingsSection } from "./components/SettingsSection"; import { SettingsSwitchRow } from "./components/SettingsSwitchRow"; -import { ListChoiceRow } from "../../components/ui/ListChoiceRow"; -import type { ToolCallVisibility } from "@t3tools/contracts/settings"; import { SettingsScreen } from "./components/SettingsScreen"; import { planAutoSettleSettingsSync, type AutoSettleSettings } from "./autoSettleSettingsSync"; import { useSettingsEnvironmentFilter } from "./settings-environment-filter"; @@ -45,7 +43,6 @@ export function SettingsLabsRouteScreen() { contentContainerClassName="gap-6 px-5 pt-4" contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 18) + 18 }} > - - ); @@ -303,66 +299,3 @@ function MachineBetaSection({ ); } - -const TOOL_CALL_VISIBILITY_OPTIONS: ReadonlyArray<{ - readonly value: ToolCallVisibility; - readonly label: string; -}> = [ - { value: "showAll", label: "Show all" }, - { value: "hideSuccessful", label: "Hide successful" }, - { value: "hideAll", label: "Hide all" }, - { value: "zen", label: "Zen" }, -]; - -function ToolCallVisibilitySection() { - const savePreferences = useAtomSet(updateMobilePreferencesAtom); - const preferences = useAtomValue(mobilePreferencesAtom); - const selected = AsyncResult.isSuccess(preferences) - ? (preferences.value.toolCallVisibility ?? "showAll") - : null; - - return ( - - - {TOOL_CALL_VISIBILITY_OPTIONS.map((option, index) => ( - 0} - disabled={selected === null} - onPress={() => savePreferences({ toolCallVisibility: option.value })} - /> - ))} - - - Hide the commands, edits, and searches agents run. Hide successful keeps failures. Zen shows - only final answers. Approvals, questions, and errors always show. - - - ); -} - -function LegacySettingsSection() { - const savePreferences = useAtomSet(updateMobilePreferencesAtom); - const preferences = useAtomValue(mobilePreferencesAtom); - const planModeEnabled = - AsyncResult.isSuccess(preferences) && preferences.value.planModeEnabled === true; - - return ( - - - savePreferences({ planModeEnabled: value })} - /> - - - Opt into retired interfaces kept for compatibility. Plan Mode restores the Build/Plan - control; otherwise every task runs in Build mode. - - - ); -} diff --git a/apps/mobile/src/features/settings/appearance/sections/ToolCallsAppearanceSection.tsx b/apps/mobile/src/features/settings/appearance/sections/ToolCallsAppearanceSection.tsx new file mode 100644 index 0000000000000..aa1c46595d697 --- /dev/null +++ b/apps/mobile/src/features/settings/appearance/sections/ToolCallsAppearanceSection.tsx @@ -0,0 +1,48 @@ +import { useAtomSet, useAtomValue } from "@effect/atom-react"; +import type { ToolCallVisibility } from "@t3tools/contracts/settings"; +import { AsyncResult } from "effect/reactivity"; +import { View } from "react-native"; + +import { AppText as Text } from "../../../../components/ui/AppText"; +import { ListChoiceRow } from "../../../../components/ui/ListChoiceRow"; +import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../../../state/preferences"; +import { SettingsSection } from "../../components/SettingsSection"; + +const TOOL_CALL_VISIBILITY_OPTIONS: ReadonlyArray<{ + readonly value: ToolCallVisibility; + readonly label: string; +}> = [ + { value: "showAll", label: "Show all" }, + { value: "hideSuccessful", label: "Hide successful" }, + { value: "hideAll", label: "Hide all" }, + { value: "zen", label: "Zen" }, +]; + +export function ToolCallsAppearanceSection() { + const savePreferences = useAtomSet(updateMobilePreferencesAtom); + const preferences = useAtomValue(mobilePreferencesAtom); + const selected = AsyncResult.isSuccess(preferences) + ? (preferences.value.toolCallVisibility ?? "showAll") + : null; + + return ( + + + {TOOL_CALL_VISIBILITY_OPTIONS.map((option, index) => ( + 0} + disabled={selected === null} + onPress={() => savePreferences({ toolCallVisibility: option.value })} + /> + ))} + + + Hide the commands, edits, and searches agents run. Hide successful keeps failures. Zen shows + only final answers. Approvals, questions, and errors always show. + + + ); +} diff --git a/apps/mobile/src/features/settings/settings-search.ts b/apps/mobile/src/features/settings/settings-search.ts index b3a2e0b68ab97..4d02bd9789b1d 100644 --- a/apps/mobile/src/features/settings/settings-search.ts +++ b/apps/mobile/src/features/settings/settings-search.ts @@ -26,6 +26,12 @@ const SETTINGS_SEARCH_TERMS = { "terminal", "word break", "math", + "tool calls in chat", + "hide tool calls", + "commands edits searches", + "work log", + "zen", + "final answers only", ], SettingsNavigation: ["tabs", "tab order", "reorder", "hide tabs"], SettingsKeyboard: ["return key", "send message", "new line", "hardware keyboard"], @@ -99,19 +105,11 @@ const SETTINGS_SEARCH_TERMS = { "start from remote", ], SettingsLabs: [ - "legacy", - "plan mode", "beta", "linked environments", "link machine", "fast worktree checkout", "clone cache", - "tool calls in chat", - "hide tool calls", - "commands edits searches", - "work log", - "zen", - "final answers only", ], SettingsEnvironmentMaintenance: ["updates", "provider updates", "restart", "manage environments"], SettingsAbout: [ diff --git a/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx index 8f02fe677cdd3..a3b02b8985d4d 100644 --- a/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx +++ b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx @@ -33,6 +33,7 @@ import { KeyboardController } from "react-native-keyboard-controller"; import { AuthOrchestrationOperateScope, + DEFAULT_PROVIDER_INTERACTION_MODE, PROVIDER_SEND_TURN_MAX_ATTACHMENTS, resolveEnvironmentMachineKind, } from "@t3tools/contracts"; @@ -873,7 +874,7 @@ export function NewTaskDraftScreen(props: { threadId: ThreadId.make(creationMetadata.threadId), modelSelection: flow.selectedModel, runtimeMode: flow.runtimeMode, - interactionMode: flow.interactionMode, + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, branch: flow.selectedBranchName, worktreePath: flow.selectedWorktreePath, createdAt: creationMetadata.createdAt, @@ -971,7 +972,6 @@ export function NewTaskDraftScreen(props: { offersSideChat={false} onReconnectEnvironment={() => onReconnectEnvironment(selectedProject.environmentId)} canSwitchThreadProvider - onUpdateThreadInteractionMode={flow.planModeEnabled ? flow.setInteractionMode : undefined} onRespondToApproval={async () => {}} onSelectUserInputOption={() => {}} onChangeUserInputCustomAnswer={() => {}} diff --git a/apps/mobile/src/features/threads/ThreadComposer.tsx b/apps/mobile/src/features/threads/ThreadComposer.tsx index 57bda9aa98c9b..8987d9fadc9f0 100644 --- a/apps/mobile/src/features/threads/ThreadComposer.tsx +++ b/apps/mobile/src/features/threads/ThreadComposer.tsx @@ -21,7 +21,6 @@ import { type EnvironmentId, type MessageId, type ModelSelection, - type ProviderInteractionMode, type ServerConfig as T3ServerConfig, type UsageLimitsReport, } from "@t3tools/contracts"; @@ -253,7 +252,6 @@ export interface ThreadComposerProps { ) => Promise; readonly onShowUsageLimits: (report: UsageLimitsReport | null) => void; readonly canSwitchProvider: boolean; - readonly onUpdateInteractionMode?: (interactionMode: ProviderInteractionMode) => void; readonly onHeightChange?: (height: number) => void; readonly onEditorFocusChange?: (focused: boolean) => void; } @@ -558,10 +556,6 @@ export const ThreadComposer = function ThreadComposer(props: ThreadComposerProps hasThread: !props.isDraft, hasCompactableConversation: props.hasCompactableConversation, onChangeDraftMessage: props.onChangeDraftMessage, - onUpdateInteractionMode: - selectedProviderStatus?.showInteractionModeToggle === false - ? undefined - : props.onUpdateInteractionMode, offersUsageLimits: usageLimitsOffered, onUsageLimits: usageLimitsOffered && draftAttachments.length === 0 ? openUsageLimits : undefined, diff --git a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx index 27cce9c5795ba..f3c81cc9b1363 100644 --- a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx +++ b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx @@ -41,7 +41,6 @@ import { deriveBottomControlInsets, type NativeLayoutMetrics } from "../../lib/r import type { EnvironmentId, MessageId, - ProviderInteractionMode, RuntimeRequestId, ScopedThreadRef, ServerConfig as T3ServerConfig, @@ -301,7 +300,6 @@ export interface ThreadDetailScreenProps { ) => Promise; readonly onReconnectEnvironment: () => void; readonly canSwitchThreadProvider: boolean; - readonly onUpdateThreadInteractionMode?: (interactionMode: ProviderInteractionMode) => void; readonly onRespondToApproval: ApprovalResponder; readonly onSelectUserInputOption: ( requestId: RuntimeRequestId, @@ -1933,7 +1931,6 @@ function ThreadDetailBody( onSendMessage={handleSendMessage} onShowUsageLimits={showUsageLimits} canSwitchProvider={props.canSwitchThreadProvider} - onUpdateInteractionMode={props.onUpdateThreadInteractionMode} onHeightChange={handleComposerHeightChange} onEditorFocusChange={handleOwnedInputFocusChange} /> diff --git a/apps/mobile/src/features/threads/ThreadRouteScreen.tsx b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx index 80a3e8643a567..365d6d584c292 100644 --- a/apps/mobile/src/features/threads/ThreadRouteScreen.tsx +++ b/apps/mobile/src/features/threads/ThreadRouteScreen.tsx @@ -1063,7 +1063,6 @@ function ThreadRouteContent( offersSideChat={composer.sideChatUnavailableReason === null} onReconnectEnvironment={handleReconnectEnvironment} canSwitchThreadProvider={composer.canSwitchThreadProvider} - onUpdateThreadInteractionMode={composer.onUpdateInteractionMode} onRespondToApproval={requests.onRespondToApproval} onSelectUserInputOption={requests.onSelectUserInputOption} onChangeUserInputCustomAnswer={requests.onChangeUserInputCustomAnswer} diff --git a/apps/mobile/src/features/threads/ThreadSettingsSheet.tsx b/apps/mobile/src/features/threads/ThreadSettingsSheet.tsx index a505e5b3f39f7..8403be8baaac8 100644 --- a/apps/mobile/src/features/threads/ThreadSettingsSheet.tsx +++ b/apps/mobile/src/features/threads/ThreadSettingsSheet.tsx @@ -1338,7 +1338,7 @@ export function ExistingThreadSettingsRouteScreen({ option.selection.model === modelSelection.model, ) ?? null; const updateModelSelection = (selection: ModelSelection) => - updateThreadDraftModelSelection(threadKey, selection, serverConfig?.providers ?? []); + updateThreadDraftModelSelection(threadKey, selection); return ( ({ useOlderSettledCount: () => 0 vi.mock("../../state/project-grouping", () => ({ useMobileProjectGroupingSettings: () => ({ sidebarProjectGroupingMode: "none" }), })); -vi.mock("./use-legacy-plan-mode-enabled", () => ({ - useLegacyPlanModeState: () => ({ enabled: false, loaded: true }), -})); vi.mock("../../components/ui/Dialog", () => ({ alert: vi.fn() })); vi.mock("../../state/use-atom-command", () => ({ useAtomCommand: () => vi.fn() })); vi.mock("../../state/use-remote-environment-registry", () => ({ diff --git a/apps/mobile/src/features/threads/new-task-flow-provider.tsx b/apps/mobile/src/features/threads/new-task-flow-provider.tsx index 3876fd2f83dbb..5b42c28adc518 100644 --- a/apps/mobile/src/features/threads/new-task-flow-provider.tsx +++ b/apps/mobile/src/features/threads/new-task-flow-provider.tsx @@ -7,7 +7,6 @@ import React, { useEffect, useRef, useState } from "react"; import type { EnvironmentId, ModelSelection, - ProviderInteractionMode, ProviderOptionSelection, RuntimeMode, ServerProvider, @@ -83,11 +82,6 @@ import { type HomeProjectScope, } from "../home/homeThreadList"; import { useMobileProjectGroupingSettings } from "../../state/project-grouping"; -import { - resolvePendingTaskInteractionMode, - resolveProviderInteractionMode, -} from "../../state/legacy-plan-mode"; -import { useLegacyPlanModeState } from "./use-legacy-plan-mode-enabled"; import { resolveNewTaskDraftWorkspace, resolveNewTaskBranchWorktreePath, @@ -156,8 +150,6 @@ type NewTaskFlowContextValue = { readonly availableBranches: ReadonlyArray; readonly currentCheckoutBranchName: string | null; readonly runtimeMode: RuntimeMode; - readonly interactionMode: ProviderInteractionMode; - readonly planModeEnabled: boolean; readonly expandedProvider: string | null; readonly environments: ReadonlyArray<{ readonly environmentId: EnvironmentId; @@ -209,7 +201,6 @@ type NewTaskFlowContextValue = { readonly setSubmitting: (value: boolean) => void; readonly loadBranches: () => void; readonly setRuntimeMode: (value: RuntimeMode) => void; - readonly setInteractionMode: (value: ProviderInteractionMode) => void; readonly setSelectedModelOptions: ( value: ReadonlyArray | undefined, ) => void; @@ -230,7 +221,6 @@ export function NewTaskFlowProvider( const threads = useThreadShells(); const { savedConnectionsById } = useSavedRemoteConnections(); const groupingSettings = useMobileProjectGroupingSettings(); - const { enabled: legacyPlanModeEnabled } = useLegacyPlanModeState(); const projectScopes = sortHomeProjectScopes({ scopes: buildHomeProjectScopes({ @@ -482,11 +472,6 @@ export function NewTaskFlowProvider( selectedEnvironmentServerConfig?.providers.find( (provider) => provider.instanceId === selectedModel?.instanceId, ) ?? null; - const planModeEnabled = - legacyPlanModeEnabled && selectedProviderStatus?.showInteractionModeToggle !== false; - const interactionMode = planModeEnabled - ? (selectedProjectDraft.interactionMode ?? DEFAULT_PROVIDER_INTERACTION_MODE) - : DEFAULT_PROVIDER_INTERACTION_MODE; const setSelectedModelKey = ( key: string | null, options?: ReadonlyArray, @@ -501,15 +486,7 @@ export function NewTaskFlowProvider( const selection = withRememberedModelOptions( options ? { ...option.selection, options } : option.selection, ); - const provider = selectedEnvironmentServerConfig?.providers.find( - (candidate) => candidate.instanceId === selection.instanceId, - ); - updateComposerDraftSettings(selectedProjectDraftKey, { - modelSelection: selection, - ...(provider?.showInteractionModeToggle === false - ? { interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE } - : {}), - }); + updateComposerDraftSettings(selectedProjectDraftKey, { modelSelection: selection }); setStickyComposerModelSelection(selection); }; const setSelectedModelOptions = (options: ReadonlyArray | undefined) => { @@ -709,13 +686,6 @@ export function NewTaskFlowProvider( updateComposerDraftSettings(selectedProjectDraftKey, { runtimeMode: value }); } }; - const setInteractionMode = (value: ProviderInteractionMode) => { - if (selectedProjectDraftKey) { - updateComposerDraftSettings(selectedProjectDraftKey, { - interactionMode: resolveProviderInteractionMode(selectedProviderStatus, value), - }); - } - }; const buildPendingTaskMessage = ( metadata: TurnCommandMetadata, @@ -761,13 +731,7 @@ export function NewTaskFlowProvider( defaultRuntimeMode, managedPolicy, ), - interactionMode: resolvePendingTaskInteractionMode({ - planModeEnabled: legacyPlanModeEnabled, - draftInteractionMode: draft.interactionMode, - provider: selectedEnvironmentServerConfig?.providers.find( - (candidate) => candidate.instanceId === draftModelSelection.instanceId, - ), - }), + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, creation: { projectId: selectedProject.id, projectTitle: selectedProject.title, @@ -875,8 +839,6 @@ export function NewTaskFlowProvider( availableBranches, currentCheckoutBranchName, runtimeMode, - interactionMode, - planModeEnabled, expandedProvider, environments, selectedProject, @@ -907,7 +869,6 @@ export function NewTaskFlowProvider( setSubmitting: (value: boolean) => updateFlow({ submitting: value }), loadBranches, setRuntimeMode, - setInteractionMode, setSelectedModelOptions, setExpandedProvider: (value: string | null) => updateFlow({ expandedProvider: value }), } satisfies NewTaskFlowContextValue; diff --git a/apps/mobile/src/features/threads/use-composer-command-menu.test.ts b/apps/mobile/src/features/threads/use-composer-command-menu.test.ts index 253c8d9e806ee..74bf278b0a438 100644 --- a/apps/mobile/src/features/threads/use-composer-command-menu.test.ts +++ b/apps/mobile/src/features/threads/use-composer-command-menu.test.ts @@ -62,37 +62,29 @@ describe("resolveComposerMenuSelection", () => { }); describe("mobile slash commands", () => { - const antigravity = { - driver: ProviderDriverKind.make("antigravity"), - showInteractionModeToggle: false, - slashCommands: [{ name: "plan", description: "Plan with Antigravity" }], - }; - - it.each([false, true])( - "keeps native /plan with legacy mode enabled=%s", - (allowInteractionMode) => { - const items = buildComposerSlashCommandItems({ - query: "pl", - atMessageStart: true, - hasThread: true, - allowInteractionMode, - selectedProviderStatus: antigravity, - }); + it("keeps a provider's native /plan", () => { + const items = buildComposerSlashCommandItems({ + query: "pl", + atMessageStart: true, + hasThread: true, + selectedProviderStatus: { + driver: ProviderDriverKind.make("antigravity"), + slashCommands: [{ name: "plan", description: "Plan with Antigravity" }], + }, + }); - expect(items).toHaveLength(1); - expect(items[0]?.type).toBe("provider-slash-command"); - const item = items[0]; - if (!item) throw new Error("Expected the native plan command"); - expect( - resolveComposerCommandSelection({ - draftMessage: "/pl", - trigger: { rangeStart: 0, rangeEnd: 3 }, - item, - allowInteractionMode, - }), - ).toEqual({ text: "/plan ", cursor: 6, interactionMode: null }); - }, - ); + expect(items).toHaveLength(1); + expect(items[0]?.type).toBe("provider-slash-command"); + const item = items[0]; + if (!item) throw new Error("Expected the native plan command"); + expect( + resolveComposerCommandSelection({ + draftMessage: "/pl", + trigger: { rangeStart: 0, rangeEnd: 3 }, + item, + }), + ).toEqual({ text: "/plan ", cursor: 6 }); + }); it("offers T3's /clear in place of the provider's, only at the message start", () => { const claude = { @@ -104,7 +96,6 @@ describe("mobile slash commands", () => { query: "cle", atMessageStart, hasThread: true, - allowInteractionMode: true, offersFreshSession, selectedProviderStatus: claude, }).map((item) => item.id); @@ -112,36 +103,4 @@ describe("mobile slash commands", () => { expect(build(false)).toEqual(["pcmd:clear"]); expect(build(true, false)).toEqual([]); }); - - it("still applies the T3 plan command for supported providers", () => { - const items = buildComposerSlashCommandItems({ - query: "plan", - atMessageStart: true, - hasThread: true, - allowInteractionMode: true, - selectedProviderStatus: { - driver: ProviderDriverKind.make("codex"), - slashCommands: [], - }, - }); - const item = items[0]; - if (!item) throw new Error("Expected the T3 plan command"); - expect( - resolveComposerCommandSelection({ - draftMessage: "/plan", - trigger: { rangeStart: 0, rangeEnd: 5 }, - item, - allowInteractionMode: true, - }), - ).toEqual({ text: "", cursor: 0, interactionMode: "plan" }); - - expect( - resolveComposerCommandSelection({ - draftMessage: "/plan", - trigger: { rangeStart: 0, rangeEnd: 5 }, - item, - allowInteractionMode: false, - }), - ).toEqual({ text: "/plan ", cursor: 6, interactionMode: null }); - }); }); diff --git a/apps/mobile/src/features/threads/use-composer-command-menu.ts b/apps/mobile/src/features/threads/use-composer-command-menu.ts index 1d2fe31a2d420..7ef0885cb6ffd 100644 --- a/apps/mobile/src/features/threads/use-composer-command-menu.ts +++ b/apps/mobile/src/features/threads/use-composer-command-menu.ts @@ -2,7 +2,6 @@ import * as Dialog from "../../components/ui/Dialog"; import type { EnvironmentId, ProjectId, - ProviderInteractionMode, ServerProvider, ThreadId, ThreadPullRequestLink, @@ -107,41 +106,20 @@ export function buildComposerSlashCommandItems(input: { /** T3's `/clear` replaces the provider's, which would clear a session T3 still treats as live. */ readonly offersFreshSession?: boolean; readonly offersSideChat?: boolean; - readonly allowInteractionMode: boolean; - readonly selectedProviderStatus: Pick< - ServerProvider, - "driver" | "slashCommands" | "showInteractionModeToggle" - > | null; + readonly selectedProviderStatus: Pick | null; }): ComposerCommandItem[] { const query = input.query.toLowerCase(); - const allowInteractionMode = - input.allowInteractionMode && input.selectedProviderStatus?.showInteractionModeToggle !== false; - const builtIn = [ - { - id: "cmd:model", - type: "slash-command", - command: "model", - label: "/model", - description: "Switch model", - }, - { - id: "cmd:plan", - type: "slash-command", - command: "plan", - label: "/plan", - description: "Switch to plan mode", - }, - { - id: "cmd:default", - type: "slash-command", - command: "default", - label: "/default", - description: "Switch to default mode", - }, - ] satisfies ComposerCommandItem[]; - const items: ComposerCommandItem[] = builtIn.filter( - (item) => item.command.includes(query) && (item.command === "model" || allowInteractionMode), - ); + const items: ComposerCommandItem[] = "model".includes(query) + ? [ + { + id: "cmd:model", + type: "slash-command", + command: "model", + label: "/model", + description: "Switch model", + }, + ] + : []; // Providers expand commands only at the start of a message. T3 commands // change local state and do not have this restriction, except `/clear`. @@ -199,24 +177,8 @@ export function resolveComposerCommandSelection(input: { readonly draftMessage: string; readonly trigger: Pick; readonly item: ComposerCommandItem; - readonly allowInteractionMode: boolean; -}): { - readonly text: string; - readonly cursor: number; - readonly interactionMode: ProviderInteractionMode | null; -} { +}): { readonly text: string; readonly cursor: number } { const { draftMessage, trigger, item } = input; - if ( - input.allowInteractionMode && - item.type === "slash-command" && - (item.command === "plan" || item.command === "default") - ) { - return { - ...replaceTextRange(draftMessage, trigger.rangeStart, trigger.rangeEnd, ""), - interactionMode: item.command, - }; - } - let replacement = ""; if (item.type === "path") { replacement = `${serializeComposerFileLink(item.path)} `; @@ -227,10 +189,7 @@ export function resolveComposerCommandSelection(input: { } else if (item.type === "provider-slash-command") { replacement = `/${item.command.name} `; } - return { - ...replaceTextRange(draftMessage, trigger.rangeStart, trigger.rangeEnd, replacement), - interactionMode: null, - }; + return replaceTextRange(draftMessage, trigger.rangeStart, trigger.rangeEnd, replacement); } export function useComposerCommandMenu({ @@ -251,7 +210,6 @@ export function useComposerCommandMenu({ offersUsageLimits = false, enabled = true, onChangeDraftMessage, - onUpdateInteractionMode, onUsageLimits, onFreshSession, offersSideChat = false, @@ -274,7 +232,6 @@ export function useComposerCommandMenu({ readonly offersUsageLimits?: boolean; readonly enabled?: boolean; readonly onChangeDraftMessage: (value: string) => void; - readonly onUpdateInteractionMode?: (mode: ProviderInteractionMode) => void; readonly onUsageLimits?: () => void; /** Arms a fresh session for the next message; absent where `/clear` is unavailable. */ readonly onFreshSession?: () => void; @@ -384,7 +341,6 @@ export function useComposerCommandMenu({ offersUsageLimits, offersFreshSession: onFreshSession !== undefined, offersSideChat, - allowInteractionMode: onUpdateInteractionMode !== undefined, selectedProviderStatus: selectedProviderStatus ? { ...selectedProviderStatus, @@ -636,19 +592,9 @@ export function useComposerCommandMenu({ return; } - const result = resolveComposerCommandSelection({ - draftMessage, - trigger, - item, - allowInteractionMode: - onUpdateInteractionMode !== undefined && - selectedProviderStatus?.showInteractionModeToggle !== false, - }); + const result = resolveComposerCommandSelection({ draftMessage, trigger, item }); setSelection({ start: result.cursor, end: result.cursor }); onChangeDraftMessage(result.text); - if (result.interactionMode !== null) { - onUpdateInteractionMode?.(result.interactionMode); - } }; return { diff --git a/apps/mobile/src/features/threads/use-legacy-plan-mode-enabled.ts b/apps/mobile/src/features/threads/use-legacy-plan-mode-enabled.ts deleted file mode 100644 index 694a34e632e3f..0000000000000 --- a/apps/mobile/src/features/threads/use-legacy-plan-mode-enabled.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useAtomValue } from "@effect/atom-react"; -import { AsyncResult } from "effect/reactivity"; - -import { mobilePreferencesAtom } from "../../state/preferences"; -import { resolveLegacyPlanModeEnabled } from "../../state/legacy-plan-mode"; - -export function useLegacyPlanModeState(): { readonly enabled: boolean } { - const preferences = useAtomValue(mobilePreferencesAtom); - const loaded = AsyncResult.isSuccess(preferences); - return { - enabled: resolveLegacyPlanModeEnabled({ - loaded, - preference: loaded ? preferences.value.planModeEnabled : undefined, - }), - }; -} diff --git a/apps/mobile/src/persistence/mobile-preferences.ts b/apps/mobile/src/persistence/mobile-preferences.ts index 436f3b172cdae..6dcfe21548a2c 100644 --- a/apps/mobile/src/persistence/mobile-preferences.ts +++ b/apps/mobile/src/persistence/mobile-preferences.ts @@ -116,7 +116,6 @@ export interface Preferences { readonly microphones?: ReadonlyArray; readonly projectGroupingEnabled?: boolean; readonly projectGroupingMode?: SidebarProjectGroupingMode; - readonly planModeEnabled?: boolean; readonly toolCallVisibility?: ToolCallVisibility; readonly workingShelfEnabled?: boolean; readonly useLastProjectForNewThreads?: boolean; @@ -201,7 +200,6 @@ function sanitizePreferences(parsed: Preferences): Preferences { microphones?: ReadonlyArray; projectGroupingEnabled?: boolean; projectGroupingMode?: SidebarProjectGroupingMode; - planModeEnabled?: boolean; toolCallVisibility?: ToolCallVisibility; workingShelfEnabled?: boolean; useLastProjectForNewThreads?: boolean; @@ -326,9 +324,6 @@ function sanitizePreferences(parsed: Preferences): Preferences { ) { preferences.projectGroupingMode = parsed.projectGroupingMode; } - if (typeof parsed.planModeEnabled === "boolean") { - preferences.planModeEnabled = parsed.planModeEnabled; - } if (isToolCallVisibility(parsed.toolCallVisibility)) { preferences.toolCallVisibility = parsed.toolCallVisibility; } diff --git a/apps/mobile/src/state/legacy-plan-mode.ts b/apps/mobile/src/state/legacy-plan-mode.ts deleted file mode 100644 index f4ab2f8c681c2..0000000000000 --- a/apps/mobile/src/state/legacy-plan-mode.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - DEFAULT_PROVIDER_INTERACTION_MODE, - type ProviderInteractionMode, - type ServerProvider, -} from "@t3tools/contracts"; - -type InteractionModeProvider = Pick; - -export function resolveProviderInteractionMode( - provider: InteractionModeProvider | null | undefined, - interactionMode: ProviderInteractionMode | null | undefined, -): ProviderInteractionMode { - return provider?.showInteractionModeToggle === false - ? DEFAULT_PROVIDER_INTERACTION_MODE - : (interactionMode ?? DEFAULT_PROVIDER_INTERACTION_MODE); -} - -export function resolveLegacyPlanModeEnabled(input: { - readonly loaded: boolean; - readonly preference: boolean | undefined; -}): boolean { - return input.loaded && input.preference === true; -} - -export function resolvePendingTaskInteractionMode(input: { - readonly planModeEnabled: boolean; - readonly draftInteractionMode: ProviderInteractionMode | undefined; - readonly provider?: InteractionModeProvider | null; -}): ProviderInteractionMode { - if (input.provider?.showInteractionModeToggle === false || !input.planModeEnabled) { - return DEFAULT_PROVIDER_INTERACTION_MODE; - } - return input.draftInteractionMode ?? DEFAULT_PROVIDER_INTERACTION_MODE; -} diff --git a/apps/mobile/src/state/thread-outbox-model.ts b/apps/mobile/src/state/thread-outbox-model.ts index 4796ec19f27ec..32f4a52237726 100644 --- a/apps/mobile/src/state/thread-outbox-model.ts +++ b/apps/mobile/src/state/thread-outbox-model.ts @@ -7,6 +7,7 @@ import type { EnvironmentShellStatus } from "@t3tools/client-runtime/state/shell import type { ThreadFamilyRead } from "@t3tools/client-runtime/state/threads"; import { CommandId, + DEFAULT_PROVIDER_INTERACTION_MODE, EnvironmentId, IsoDateTime, MessageId, @@ -28,7 +29,6 @@ import { DraftComposerAttachmentSchema } from "../lib/composer-image-schema"; import type { ComposerDispatchMode } from "@t3tools/client-runtime/state/composer-dispatch"; import type { DraftComposerAttachment } from "../lib/composerImages"; import { scopedThreadKey } from "../lib/scopedEntities"; -import { resolveProviderInteractionMode } from "./legacy-plan-mode"; // Keep current writes until a compatible native baseline includes the v4 reader. const THREAD_OUTBOX_SCHEMA_VERSION = 3; @@ -105,6 +105,16 @@ export interface ThreadSettingsSnapshot { readonly interactionMode: ProviderInteractionModeType; } +// Providers without a Build/Plan toggle always run in build mode. +function resolveProviderInteractionMode( + provider: Pick | undefined, + interactionMode: ProviderInteractionModeType, +): ProviderInteractionModeType { + return provider?.showInteractionModeToggle === false + ? DEFAULT_PROVIDER_INTERACTION_MODE + : interactionMode; +} + export function resolveQueuedThreadSettings( message: QueuedThreadMessage, thread: ThreadSettingsSnapshot, diff --git a/apps/mobile/src/state/thread-task-permissions.test.ts b/apps/mobile/src/state/thread-task-permissions.test.ts index 1bebfb373e327..5a31c1955b928 100644 --- a/apps/mobile/src/state/thread-task-permissions.test.ts +++ b/apps/mobile/src/state/thread-task-permissions.test.ts @@ -271,11 +271,7 @@ describe("mobile task permissions", () => { expect(state.enqueue).not.toHaveBeenCalled(); expect(state.clearDraft).not.toHaveBeenCalled(); composer.onChangeDraftMessage("Edited locally"); - composer.onUpdateInteractionMode("plan"); expect(state.draft.text).toBe("Edited locally"); - expect(state.updateSettings).toHaveBeenCalledWith("secondary:thread", { - interactionMode: "plan", - }); }); it("keeps an armed fresh session for the message after an approval redirect", async () => { diff --git a/apps/mobile/src/state/use-thread-composer-state.ts b/apps/mobile/src/state/use-thread-composer-state.ts index e19009fcc16ba..465a8a425d292 100644 --- a/apps/mobile/src/state/use-thread-composer-state.ts +++ b/apps/mobile/src/state/use-thread-composer-state.ts @@ -26,10 +26,8 @@ import { orchestrationV2WorktreePending, type EnvironmentId, type ModelSelection, - type ProviderInteractionMode, type RuntimeMode, type ScopedThreadRef, - type ServerProvider, type ThreadId, } from "@t3tools/contracts"; @@ -49,7 +47,6 @@ import { describeUnavailableModelSelection, isModelSelectionUnavailable, } from "../lib/modelOptions"; -import { resolveProviderInteractionMode } from "./legacy-plan-mode"; import type { DraftComposerImageAttachment } from "../lib/composerImages"; import { scopedThreadKey } from "../lib/scopedEntities"; @@ -181,19 +178,8 @@ export function useThreadDraftForThread(input: { }; } -/** A provider without the interaction-mode toggle runs in the default mode. */ -export function updateThreadDraftModelSelection( - threadKey: string, - value: ModelSelection, - providers: ReadonlyArray, -): void { - const provider = providers.find((candidate) => candidate.instanceId === value.instanceId); - updateComposerDraftSettings(threadKey, { - modelSelection: value, - ...(provider?.showInteractionModeToggle === false - ? { interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE } - : {}), - }); +export function updateThreadDraftModelSelection(threadKey: string, value: ModelSelection): void { + updateComposerDraftSettings(threadKey, { modelSelection: value }); } export function useThreadComposerState(input: { @@ -328,15 +314,8 @@ export function useThreadComposerState(input: { serverConfig?.providers ?? [], ); const runtimeMode = selectedDraft.runtimeMode ?? selectedThread?.runtimeMode ?? null; - const selectedProvider = selectedEnvironmentRuntime?.serverConfig?.providers.find( - (provider) => provider.instanceId === modelSelection?.instanceId, - ); - const interactionMode = selectedThread - ? resolveProviderInteractionMode( - selectedProvider, - selectedDraft.interactionMode ?? selectedThread.interactionMode, - ) - : null; + // Plan mode is not offered to users; every send runs in build mode. + const interactionMode = selectedThread ? DEFAULT_PROVIDER_INTERACTION_MODE : null; const canSwitchThreadProvider = threadAllowsProviderSwitch({ thread: selectedThreadShell, projection: selectedThreadProjection?.projection, @@ -781,10 +760,7 @@ export function useThreadComposerState(input: { context: draft.context, modelSelection, runtimeMode: draft.runtimeMode ?? thread.runtimeMode, - interactionMode: resolveProviderInteractionMode( - provider, - draft.interactionMode ?? thread.interactionMode, - ), + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, ...(compactBeforeSend ? { compactBeforeSend, dispatchMode: "queue" as const } : followUpDispatchMode === null @@ -865,20 +841,6 @@ export function useThreadComposerState(input: { removeComposerDraftAttachment(threadKey, imageId); }; - const onUpdateInteractionMode = (value: ProviderInteractionMode) => { - if (!selectedThreadKey) { - return; - } - const modelSelection = - getComposerDraftSnapshot(selectedThreadKey).modelSelection ?? selectedThread?.modelSelection; - const provider = selectedEnvironmentRuntime?.serverConfig?.providers.find( - (candidate) => candidate.instanceId === modelSelection?.instanceId, - ); - updateComposerDraftSettings(selectedThreadKey, { - interactionMode: resolveProviderInteractionMode(provider, value), - }); - }; - return { feedbackSubmissions, dismissFeedback, @@ -917,7 +879,6 @@ export function useThreadComposerState(input: { onNativePasteText, onRemoveDraftImage, onSendMessage, - onUpdateInteractionMode, sideChatUnavailableReason, }; } diff --git a/apps/web/src/components/ChatView.logic.test.ts b/apps/web/src/components/ChatView.logic.test.ts index 2e772cc0998c0..7de02cc3941ad 100644 --- a/apps/web/src/components/ChatView.logic.test.ts +++ b/apps/web/src/components/ChatView.logic.test.ts @@ -3,7 +3,6 @@ import { buildExpiredTerminalContextToastCopy, mergeDirectPreviewAnnotation, planComposerTurn, - restorePlanFollowUpComposer, snapshotComposerSend, withMessageContext, deriveAvailableRevertTurnCounts, @@ -1143,61 +1142,6 @@ describe("planComposerTurn", () => { }); }); -describe("restorePlanFollowUpComposer", () => { - // The draft is the failed follow-up's thread; the refs and cursor are whatever thread is on - // screen. - it.each([ - { - name: "puts the follow-up back while its thread is open", - before: { draft: "", refs: "", cursor: null }, - routeIsCurrent: true, - after: { draft: "Tighten step 2", refs: "Tighten step 2", cursor: "Tighten step 2" }, - }, - { - name: "keeps a draft typed while the follow-up was in flight", - before: { draft: "Next idea", refs: "Next idea", cursor: null }, - routeIsCurrent: true, - after: { draft: "Next idea", refs: "Next idea", cursor: null }, - }, - { - name: "leaves another thread's composer alone after navigating away", - before: { draft: "", refs: "Other thread", cursor: null }, - routeIsCurrent: false, - after: { draft: "Tighten step 2", refs: "Other thread", cursor: null }, - }, - ])("$name", ({ before, routeIsCurrent, after }) => { - const composer: { draft: string; refs: string; cursor: string | null } = { ...before }; - restorePlanFollowUpComposer({ - snapshot: { - prompt: "Tighten step 2", - environmentContexts: [], - terminalContexts: [], - reviewComments: [], - previewAnnotations: [], - threadContexts: [], - }, - draftHasUserContent: composer.draft.trim().length > 0, - routeIsCurrent, - writeComposerRefs: (snapshot) => { - composer.refs = snapshot.prompt; - }, - writePrompt: (prompt) => { - composer.draft = prompt; - }, - writeTerminalContexts: () => undefined, - writeReviewComments: () => undefined, - writePreviewAnnotations: () => undefined, - writeThreadContexts: () => undefined, - writeEnvironmentContexts: () => undefined, - resetCursor: ({ prompt }) => { - composer.cursor = prompt; - }, - }); - - expect(composer).toEqual(after); - }); -}); - describe("withMessageContext", () => { const context = { records: [] } as unknown as NonNullable< Parameters[0]["context"] diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index d4b54a7e5a848..2b8424f9efe0a 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -15,11 +15,7 @@ import type { SetStateAction } from "react"; import { applyClaudePromptEffortPrefix, resolvePromptInjectedEffort } from "@t3tools/shared/model"; import { getProviderModelCapabilities } from "../providerModels"; import { sanitizeThreadErrorMessage } from "@t3tools/client-runtime/errors"; -import type { - EnvironmentThread, - ThreadStructure, - EnvironmentThreadShell, -} from "@t3tools/client-runtime/state/shell"; +import type { EnvironmentThread, ThreadStructure } from "@t3tools/client-runtime/state/shell"; import { ANTIGRAVITY_DEFAULT_MODEL, PROVIDER_SEND_TURN_MAX_ATTACHMENTS, @@ -34,7 +30,6 @@ import { type ModelSelection, type OrchestrationMessageContext, type PreviewAnnotationPayload, - type ProviderInteractionMode, ProviderDriverKind, type ProviderInstanceId, type ServerProvider, @@ -76,8 +71,7 @@ import { } from "../composerDraftStore"; import * as Schema from "effect/Schema"; import { appAtomRegistry } from "../rpc/atomRegistry"; -import { environmentThreadShells, environmentThreadDetails } from "../state/threads"; -import { waitForAtomValue } from "../state/waitForAtomValue"; +import { environmentThreadDetails } from "../state/threads"; import { filterTerminalContextsWithText, formatTerminalContextLabel, @@ -94,7 +88,7 @@ import { } from "../lib/composerContextRecords"; import type { ComposerDispatchMode } from "@t3tools/client-runtime/state/composer-dispatch"; import type { DraftThreadEnvMode } from "../composerDraftStore"; -import { collapseExpandedComposerCursor, type ComposerSubmissionIntent } from "../composer-logic"; +import { type ComposerSubmissionIntent } from "../composer-logic"; import type { ReviewCommentContext } from "../reviewCommentContext"; import { derivePhase, type TimelineEntry } from "../session-logic"; import type { PreviewMiniPlayerSource } from "../previewMiniPlayerStore"; @@ -539,21 +533,6 @@ export function resolveComposerProviderSelection(input: { }; } -export function resolveComposerInteractionMode(input: { - planModeEnabled: boolean; - provider: Pick | null | undefined; - interactionMode: ProviderInteractionMode; -}): { enabled: boolean; interactionMode: ProviderInteractionMode } { - const enabled = - input.planModeEnabled && - input.provider != null && - input.provider.showInteractionModeToggle !== false; - return { - enabled, - interactionMode: enabled ? input.interactionMode : "default", - }; -} - export function getAntigravitySendBlockReason( provider: | Pick @@ -819,22 +798,6 @@ export function shouldShowBranchMismatchBanner(input: { return input.composerHasContent || input.wasShownForCurrentMismatch; } -export function shouldShowPlanFollowUpPrompt(input: { - pendingUserInputCount: number; - interactionMode: ProviderInteractionMode; - latestTurnSettled: boolean; - hasActionableProposedPlan: boolean; - hasComposerAttachments: boolean; -}): boolean { - return ( - input.pendingUserInputCount === 0 && - input.interactionMode === "plan" && - input.latestTurnSettled && - input.hasActionableProposedPlan && - !input.hasComposerAttachments - ); -} - const sessionDismissedBranchMismatchKeys = new Set(); export function dismissBranchMismatchForSession(key: string): void { @@ -873,18 +836,6 @@ function threadHasStarted(thread: Thread | null | undefined): boolean { return Boolean(thread && (thread.latestRun !== null || thread.itemCount > 0 || thread.runtime)); } -export function threadShellHasStarted( - shell: - | Pick - | null - | undefined, -): boolean { - return Boolean( - shell && - (shell.latestRun !== null || shell.latestUserMessageAt !== null || shell.runtime !== null), - ); -} - // Imported history lacks a session until its first prompt; resolve its instance before locking the driver. export function deriveLockedProvider(input: { thread: Thread | null | undefined; @@ -939,19 +890,6 @@ export function getStartedThreadModelChangeBlockReason(input: { }; } -export async function waitForStartedServerThread( - threadRef: ScopedThreadRef, - timeoutMs = 1_000, -): Promise { - const threadAtom = environmentThreadShells.threadShellAtom(threadRef); - return waitForAtomValue({ - registry: appAtomRegistry, - atom: threadAtom, - predicate: threadHasStarted, - timeoutMs, - }); -} - export async function waitForRevertedMessage( threadRef: ScopedThreadRef, turnCount: number, @@ -1168,50 +1106,6 @@ export function shouldRefocusComposerOnWindowFocus( ); } -export interface PlanFollowUpComposerSnapshot { - readonly prompt: string; - readonly terminalContexts: ReadonlyArray; - readonly reviewComments: ReadonlyArray; - readonly previewAnnotations: ReadonlyArray; - readonly threadContexts: ReadonlyArray; - readonly environmentContexts: ReadonlyArray; -} - -// A draft typed while the send was in flight is newer and stays. Composer refs and the cursor -// belong to the thread on screen, so they follow only while the follow-up's route is current. -export function restorePlanFollowUpComposer(input: { - readonly snapshot: PlanFollowUpComposerSnapshot; - readonly draftHasUserContent: boolean; - readonly routeIsCurrent: boolean; - readonly writeComposerRefs: (snapshot: PlanFollowUpComposerSnapshot) => void; - readonly writePrompt: (prompt: string) => void; - readonly writeTerminalContexts: (contexts: ReadonlyArray) => void; - readonly writeReviewComments: (comments: ReadonlyArray) => void; - readonly writePreviewAnnotations: (annotations: ReadonlyArray) => void; - readonly writeThreadContexts: (records: ReadonlyArray) => void; - readonly writeEnvironmentContexts: (records: ReadonlyArray) => void; - readonly resetCursor: (options: { - cursor: number; - prompt: string; - detectTrigger: boolean; - }) => void; -}): void { - if (input.draftHasUserContent) return; - if (input.routeIsCurrent) input.writeComposerRefs(input.snapshot); - input.writePrompt(input.snapshot.prompt); - input.writeTerminalContexts(input.snapshot.terminalContexts); - input.writeReviewComments(input.snapshot.reviewComments); - input.writePreviewAnnotations(input.snapshot.previewAnnotations); - input.writeThreadContexts(input.snapshot.threadContexts); - input.writeEnvironmentContexts(input.snapshot.environmentContexts); - if (!input.routeIsCurrent) return; - input.resetCursor({ - cursor: collapseExpandedComposerCursor(input.snapshot.prompt, input.snapshot.prompt.length), - prompt: input.snapshot.prompt, - detectTrigger: true, - }); -} - /** Includes the annotation screenshot only within the attachment cap; exceeding it rejects the whole turn. */ export function mergeDirectPreviewAnnotation(input: { readonly images: ReadonlyArray; diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index ff4a642b5c31d..92149651480db 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -28,7 +28,6 @@ import { useRightPanelActions } from "./chat/useRightPanelActions"; import { type TerminalLaunchContext, useChatTerminalActions } from "./chat/useChatTerminalActions"; import { useComposerSend } from "./chat/useComposerSend"; import { useComposerResume } from "./chat/useComposerResume"; -import { useImplementPlanInNewThread } from "./chat/useProposedPlanSend"; import { ThreadFind, ThreadFindCanvas } from "./chat/ThreadFindProvider"; import { closeThreadFind, openThreadFind, useThreadFindOpen } from "./chat/threadFindStore"; import { THREAD_FIND_BAR_RESERVED_HEIGHT } from "./chat/ThreadFindBar"; @@ -138,9 +137,7 @@ import { derivePhase, deriveActiveWorkStartedAt, deriveCanInterruptRunningThread, - findLatestProposedPlan, findRevertCheckpoint, - hasActionableProposedPlan, isLatestRunSettled, } from "../session-logic"; import { type LegendListRef } from "@legendapp/list/react"; @@ -318,7 +315,7 @@ import { ComposerBannerStack, type ComposerBannerStackItem } from "./chat/Compos import { modelFallbackBannerItem } from "./chat/ComposerUsageLimits"; import { ComposerSurface } from "./chat/ComposerSurface"; import { resolveThreadSyncPhase } from "../threadSync"; -import { hasAvailableCompactionProvider } from "./chat/ContextWindowMeter.logic"; +import { hasAvailableCompactionProvider } from "./chat/compaction.logic"; import { deriveLatestContextWindowSnapshot, hasDismissedResumeCompaction, @@ -333,11 +330,9 @@ import { buildLocalDraftThread, deriveCommittedServerUserMessageIds, dismissBranchMismatchForSession, - shouldShowPlanFollowUpPrompt, shouldRenderPreviewMiniPlayer, deriveLockedProvider, resolveFileAttachmentUrl, - resolveComposerInteractionMode, resolveComposerProviderSelection, resolveThreadMetadataUpdateForNextTurn, resolveSendEnvMode, @@ -583,9 +578,6 @@ export default function ChatView(props: ChatViewProps) { const composerRuntimeMode = useComposerDraftStore( (store) => store.getComposerDraft(composerDraftTarget)?.runtimeMode ?? null, ); - const composerInteractionMode = useComposerDraftStore( - (store) => store.getComposerDraft(composerDraftTarget)?.interactionMode ?? null, - ); const composerActiveProvider = useComposerDraftStore( (store) => store.getComposerDraft(composerDraftTarget)?.activeProvider ?? null, ); @@ -1312,12 +1304,6 @@ export default function ChatView(props: ChatViewProps) { const selectedProvider = selectedProviderEntry?.driverKind ?? requestedDriverKind; const activeProviderInstanceId = selectedProviderEntry?.instanceId ?? null; const activeProviderStatus = selectedProviderEntry?.snapshot ?? null; - const { enabled: interactionModeEnabled, interactionMode } = resolveComposerInteractionMode({ - planModeEnabled: settings.planModeEnabled, - provider: activeProviderStatus, - interactionMode: - composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE, - }); const conversationProviderStatus = providerStatuses.find((status) => status.instanceId === activeRuntime?.providerInstanceId) ?? activeProviderStatus; @@ -1374,19 +1360,6 @@ export default function ChatView(props: ChatViewProps) { const onHeldRequestIdsChange = useStableCallback((requestIds: ReadonlySet) => { setHeldApprovalRequestIds((current) => (shallow(current, requestIds) ? current : requestIds)); }); - const activeProposedPlan = (() => { - if (!latestRunSettled) { - return null; - } - return findLatestProposedPlan(serverProjection, activeLatestRun?.runId ?? null); - })(); - const showPlanFollowUpPrompt = shouldShowPlanFollowUpPrompt({ - pendingUserInputCount: pendingUserInputs.length, - interactionMode, - latestTurnSettled: latestRunSettled, - hasActionableProposedPlan: hasActionableProposedPlan(activeProposedPlan), - hasComposerAttachments: composerHasAttachments, - }); // Answering an approval or question on any client resumes quota spending, so include it in the key. const usageLimitsKey = activeProviderInstanceId === null || (isServerThread && activeThread === undefined) @@ -1875,7 +1848,7 @@ export default function ChatView(props: ChatViewProps) { threadId: activeThreadId, message: { messageId: newMessageId(), role: "user", text, attachments: [] }, runtimeMode, - interactionMode, + interactionMode: DEFAULT_INTERACTION_MODE, dispatchMode: "queue", }, }); @@ -2478,18 +2451,8 @@ export default function ChatView(props: ChatViewProps) { activeEnvironmentUnavailable || feedbackUploading || pendingApprovals.length > 0 || - pendingUserInputs.length > 0 || - showPlanFollowUpPrompt; + pendingUserInputs.length > 0; const compactDisabled = compactThreadUnavailable; - const compactDisabledReason = compactDisabled - ? !canOperateThread - ? "This connection cannot change threads." - : !activeProject - ? "Choose a project before compacting" - : !manualCompactionProviderAvailable - ? "Compaction is unavailable for this provider" - : "Compacting is unavailable right now" - : null; const resumeCompactionTokens = activeContextWindow && resolveIdleCompaction( @@ -2855,14 +2818,11 @@ export default function ChatView(props: ChatViewProps) { const { getModelDisabledReason, - handleInteractionModeChange, - handleInteractionModeChangeStable, handleRuntimeModeChangeStable, onEnvModeChangeStable, onProviderModelSelectStable, onStartFromOriginChange, openProviderSetupStable, - toggleInteractionModeStable, } = useComposerSelection({ environmentId, composerDraftTarget, @@ -2876,8 +2836,6 @@ export default function ChatView(props: ChatViewProps) { supportsProviderSwitchingViaHandoff, multipleModelSelections, runtimeMode, - interactionMode, - interactionModeEnabled, isLocalDraftThread, canOverrideServerThreadEnvMode, setPendingServerThreadEnvModeEntry, @@ -2896,7 +2854,6 @@ export default function ChatView(props: ChatViewProps) { threadDetailLoading, activeEnvironmentUnavailable, runtimeMode, - interactionMode, localCheckoutBranchMismatch, composerRef, persistThreadSettingsForNextTurn, @@ -2904,21 +2861,6 @@ export default function ChatView(props: ChatViewProps) { scrollToEnd, setThreadError, }); - const onImplementPlanInNewThread = useImplementPlanInNewThread({ - environmentId, - routeThreadKey, - activeThread, - activeThreadBranch, - activeProposedPlan, - isServerThread, - isSendBusy, - composerRef, - beginLocalDispatch, - resetLocalDispatch, - activeProject, - activeEnvironmentUnavailable, - defaultRuntimeMode, - }); const { onSend } = useComposerSend({ environmentId, routeThreadKey, @@ -2930,7 +2872,6 @@ export default function ChatView(props: ChatViewProps) { activeThreadBranch, activeProject, activeProjectDefaultModelSelection, - activeProposedPlan, activeMessageCount, isServerThread, isLocalDraftThread, @@ -2961,7 +2902,6 @@ export default function ChatView(props: ChatViewProps) { keepFullHistory, freshSessionAvailable: freshSessionCommandAvailable, resumeCompactionTokens, - showPlanFollowUpPrompt, usageLimitsKey, usageLimitsOffered, openUsageLimits, @@ -2990,13 +2930,11 @@ export default function ChatView(props: ChatViewProps) { setKeepFullHistory, anchorNewTurn, acknowledgeActiveThreadWoke, - handleInteractionModeChange, onSideChat: sideChat.available ? sendSideChatStable : undefined, sideChatDisabledReason, handleNewThreadInActiveProject, persistThreadSettingsForNextTurn, }); - const onImplementPlanInNewThreadStable = useStableCallback(onImplementPlanInNewThread); const onWorktreeSetupWorkLocally = () => { if (!worktreeSetup || worktreeSetup.phase !== "running" || !draftId) { @@ -3634,7 +3572,6 @@ export default function ChatView(props: ChatViewProps) { draftId={draftId} activeThreadId={activeThreadId} activeThread={activeThread} - activeThreadShell={activeThreadShell} promptHistoryEntries={timelineEntries} forceExpandedOnMobile={ forceExpandedMobileComposer && isDraftHeroState @@ -3701,14 +3638,8 @@ export default function ChatView(props: ChatViewProps) { activePendingDraftAnswers={activePendingDraftAnswers} activePendingQuestionIndex={activePendingQuestionIndex} respondingRequestIds={respondingRequestIds} - showPlanFollowUpPrompt={showPlanFollowUpPrompt} - activeProposedPlan={activeProposedPlan} - threadSyncPhase={ - activeEnvironmentUnavailable ? null : threadSyncPhase - } runtimeMode={runtimeMode} defaultRuntimeMode={defaultRuntimeMode} - interactionMode={interactionMode} lockedProvider={modelPickerLockedProvider} providerStatuses={providerStatuses as ServerProvider[]} providerCatalogKnown={serverConfig !== null} @@ -3718,12 +3649,8 @@ export default function ChatView(props: ChatViewProps) { activeThreadModelSelection={activeThread?.modelSelection} activeProjectId={activeProject?.id ?? null} projectName={activeProject?.title} - contextWindowEntries={serverVisibleTurnItems} - contextWindowLiveUsage={activeThreadLiveTokenUsage} - contextWindowProviderThread={activeProviderThread} compactThreadUnavailable={compactThreadUnavailable} compactDisabled={compactDisabled} - compactDisabledReason={compactDisabledReason} resolvedTheme={resolvedTheme} settings={settings} keybindings={keybindings} @@ -3753,7 +3680,6 @@ export default function ChatView(props: ChatViewProps) { onSend={onComposerSendStable} onResume={onComposerResumeStable} onInterrupt={onInterruptStable} - onImplementPlanInNewThread={onImplementPlanInNewThreadStable} onSelectActivePendingUserInputOption={ onSelectActivePendingUserInputOptionStable } @@ -3770,9 +3696,7 @@ export default function ChatView(props: ChatViewProps) { onProviderModelSelect={onProviderModelSelectStable} onOpenProviderSetup={openProviderSetupStable} getModelDisabledReason={getModelDisabledReason} - toggleInteractionMode={toggleInteractionModeStable} handleRuntimeModeChange={handleRuntimeModeChangeStable} - handleInteractionModeChange={handleInteractionModeChangeStable} focusComposer={focusComposerStable} scheduleComposerFocus={scheduleComposerFocusStable} setThreadError={setThreadError} diff --git a/apps/web/src/components/Sidebar.browser.test.tsx b/apps/web/src/components/Sidebar.browser.test.tsx index 875a2225d70a1..656012ae8a8e5 100644 --- a/apps/web/src/components/Sidebar.browser.test.tsx +++ b/apps/web/src/components/Sidebar.browser.test.tsx @@ -42,7 +42,6 @@ describe("sidebar header new thread", () => { { mode: "boring", count: 2, shift: false, picker: true }, { mode: "boring", count: 2, shift: true, picker: false }, { mode: "boring", count: 1, shift: false, picker: false }, - { mode: "legacy", count: 2, shift: false, picker: false }, ] as const)("$mode sidebar, $count projects, shift=$shift: picker=$picker", async (scenario) => { using stack = new DisposableStack(); const previousSettings = getClientSettings(); @@ -50,7 +49,6 @@ describe("sidebar header new thread", () => { __setClientSettingsForTests({ ...DEFAULT_CLIENT_SETTINGS, boringSidebarEnabled: scenario.mode === "boring", - legacySidebarEnabled: scenario.mode === "legacy", }); const projects = [project("active"), project("idle")].slice(0, scenario.count); const currentProjectRef = { diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 905b28ded8618..d863e285a80e7 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -133,7 +133,7 @@ import { useTerminalFocus } from "../hooks/useTerminalFocus"; import { isCommandPaletteOpen, openCommandPalette } from "../commandPaletteBus"; import { registerSettleAndAdvance } from "../settleAndAdvance"; import { startNewThreadFromContext } from "../lib/chatThreadActions"; -import { useBoringSidebarEnabled, useClientSettings } from "../hooks/useSettings"; +import { useClientSettings } from "../hooks/useSettings"; import { useCopyToClipboard } from "../hooks/useCopyToClipboard"; import { useLocalStorage } from "../hooks/useLocalStorage"; import { useNowMinute } from "../hooks/useNowMinute"; @@ -989,8 +989,7 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve ); }, }); - const boringSidebarEnabled = useBoringSidebarEnabled(); - const legacySidebarEnabled = useClientSettings((settings) => settings.legacySidebarEnabled); + const boringSidebarEnabled = useClientSettings((settings) => settings.boringSidebarEnabled); const folderRowsMultiline = useClientSettings((settings) => settings.boringSidebarMultilineRows); const newThreadContext = useHandleNewThread(); // Row callbacks capture only the handler; the rest of the context changes with the route. @@ -2954,7 +2953,6 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve const handleNewThreadClick = (event?: ReactMouseEvent) => { if ( - legacySidebarEnabled || shouldCreateNewThreadInCurrentProject( event?.shiftKey ?? false, scopeGroups.length, @@ -3201,7 +3199,6 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve newThreadShortcutLabel={newThreadShortcutLabel} newThreadInProjectShortcutLabel={newThreadInProjectShortcutLabel} showNewThreadInProjectHint={ - !legacySidebarEnabled && !shouldCreateNewThreadInCurrentProject( false, scopeGroups.length, diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index be106a251592e..e9040012d684a 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -28,7 +28,6 @@ import type { PreviewAnnotationPayload, ThreadContextRecord, EnvironmentContextRecord, - ProviderInteractionMode, ResolvedKeybindingsConfig, RuntimeMode, RuntimeRequestId, @@ -82,12 +81,7 @@ import { replaceTextRange, } from "../../composer-logic"; import { DEFAULT_COMPOSER_PLACEHOLDER } from "../../composerPlaceholder"; -import { - deriveComposerSendState, - getAntigravitySendBlockReason, - resolveComposerInteractionMode, - threadShellHasStarted, -} from "../ChatView.logic"; +import { deriveComposerSendState, getAntigravitySendBlockReason } from "../ChatView.logic"; import { dataTransferHasComposerMention, makeComposerMentionDragHandlers, @@ -118,7 +112,6 @@ import { reconcileAttachmentContextReferences, useRetainedContextPayloads, } from "./composerContextUndo"; -import type { ThreadSyncPhase } from "../../threadSync"; import { ComposerBanner } from "./ComposerBanner"; import { ComposerSurface } from "./ComposerSurface"; import { ComposerBannerStack, type ComposerBannerStackItem } from "./ComposerBannerStack"; @@ -210,16 +203,10 @@ import { import { ComposerPrimaryActions } from "./ComposerPrimaryActions"; import { ComposerFooterPrimaryActions } from "./ComposerFooterControls"; import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel"; -import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner"; import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight"; import { buildPullRequestReferenceContext } from "../pullRequest/pullRequestDetail.logic"; import { getComposerPromptInjectionState, getComposerReasoningStep } from "./composerProviderState"; import { readPromptUltrathink } from "./TraitsPicker"; -import { - providerSupportsManualCompaction, - resolveContextWindowModelDisplayName, - shouldReserveContextWindowMeter, -} from "./ContextWindowMeter.logic"; import { buildAttachmentVideoPreview, buildExpandedImagePreview, @@ -241,24 +228,17 @@ import { toastManager } from "../ui/toast"; import { BuildingIcon, PlayIcon } from "lucide-react"; import { Button } from "../ui/button"; import { useManagedPolicy } from "../../state/managedPolicy"; -import { proposedPlanTitle } from "@t3tools/shared/proposedPlanText"; import { hasProviderSetup } from "./ProviderStatusBanner"; import { type AppModelOption, getAppModelOptionsForInstance } from "../../modelSelection"; import type { UnifiedSettings } from "@t3tools/contracts/settings"; -import { isVideoAttachment, type SessionPhase, type Thread, type ThreadShell } from "../../types"; +import { isVideoAttachment, type SessionPhase, type Thread } from "../../types"; import { type ComposerPromptHistoryPosition } from "./composerPromptHistory"; import type { PendingUserInputDraftAnswer } from "@t3tools/client-runtime/state/pending-user-input"; -import type { - LatestProposedPlanState, - PendingApproval, - PendingUserInput, - TimelineEntry, -} from "../../session-logic"; +import type { PendingApproval, PendingUserInput, TimelineEntry } from "../../session-logic"; import { resolveComposerDispatchMode, type ComposerDispatchMode, } from "@t3tools/client-runtime/state/composer-dispatch"; -import { deriveLatestContextWindowSnapshot } from "@t3tools/client-runtime/state/context-window"; import { resolveProviderSkillsForCwd, resolveProviderSlashCommandsForCwd, @@ -342,8 +322,6 @@ export interface ChatComposerHandle { selectedProvider: ProviderDriverKind; selectedModel: string; selectedProviderModels: ReadonlyArray; - interactionMode: ProviderInteractionMode; - interactionModeEnabled: boolean; shownPendingApprovalIds: ReadonlyArray; }; validateProviderInput: (providerInput: string) => boolean; @@ -369,7 +347,6 @@ export interface ChatComposerProps { activeThreadId: ThreadId | null; activeThread: Thread | undefined; - activeThreadShell: ThreadShell | null; promptHistoryEntries: ReadonlyArray; forceExpandedOnMobile: boolean; projectSelectionRequired: boolean; @@ -418,13 +395,8 @@ export interface ChatComposerProps { activePendingQuestionIndex: number; respondingRequestIds: RuntimeRequestId[]; - showPlanFollowUpPrompt: boolean; - activeProposedPlan: LatestProposedPlanState | null; - threadSyncPhase: ThreadSyncPhase | null; - runtimeMode: RuntimeMode; defaultRuntimeMode?: RuntimeMode; - interactionMode: ProviderInteractionMode; lockedProvider: ProviderDriverKind | null; providerStatuses: ServerProvider[]; @@ -435,12 +407,8 @@ export interface ChatComposerProps { activeProjectId: ProjectId | null; projectName?: string | undefined; - contextWindowEntries: Parameters[0]; - contextWindowLiveUsage: Parameters[1]; - contextWindowProviderThread: Parameters[2]; compactThreadUnavailable: boolean; compactDisabled: boolean; - compactDisabledReason: string | null; resolvedTheme: "light" | "dark"; settings: UnifiedSettings; @@ -477,7 +445,6 @@ export interface ChatComposerProps { ) => void; onResume: () => void; onInterrupt: () => void; - onImplementPlanInNewThread: () => void; onSelectActivePendingUserInputOption: (questionId: string, optionValue: string) => void; onAdvanceActivePendingUserInput: () => void; onDismissActivePendingUserInput: (requestId: RuntimeRequestId) => void; @@ -497,9 +464,7 @@ export interface ChatComposerProps { ) => void; onOpenProviderSetup: (instanceId: ProviderInstanceId) => void; getModelDisabledReason: (instanceId: ProviderInstanceId, model: string) => string | null; - toggleInteractionMode: () => void; handleRuntimeModeChange: (mode: RuntimeMode) => void; - handleInteractionModeChange: (mode: ProviderInteractionMode) => void; focusComposer: () => void; scheduleComposerFocus: () => void; @@ -550,10 +515,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) activePendingDraftAnswers, activePendingQuestionIndex, respondingRequestIds, - showPlanFollowUpPrompt, - activeProposedPlan, runtimeMode, - interactionMode: requestedInteractionMode, lockedProvider, providerStatuses, providerCatalogKnown, @@ -562,12 +524,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) reportedModelSelection, activeProjectId, projectName, - contextWindowEntries, - contextWindowLiveUsage, - contextWindowProviderThread, compactThreadUnavailable, compactDisabled, - compactDisabledReason, resolvedTheme, settings, keybindings, @@ -589,7 +547,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) onSend, onResume, onInterrupt, - onImplementPlanInNewThread, onSelectActivePendingUserInputOption, onAdvanceActivePendingUserInput, onDismissActivePendingUserInput, @@ -598,9 +555,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) onProviderModelSelect, onOpenProviderSetup, getModelDisabledReason, - toggleInteractionMode, handleRuntimeModeChange, - handleInteractionModeChange, focusComposer, scheduleComposerFocus, setThreadError, @@ -609,11 +564,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) editingQueuedAttachments, onRemoveEditingQueuedAttachment, } = props; - const activeContextWindow = deriveLatestContextWindowSnapshot( - contextWindowEntries, - contextWindowLiveUsage, - contextWindowProviderThread, - ); const primaryEnvironmentId = usePrimaryEnvironmentId(); const composerDraftTargetKey = composerTargetKey(composerDraftTarget); const { @@ -920,8 +870,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) ? providerInstanceEntries.find((entry) => hasProviderSetup(entry.snapshot))?.instanceId : undefined)) : undefined; - const resolvedCompactDisabledReason = - compactDisabledReason ?? (noProviderAvailable ? "Compacting is unavailable right now" : null); const { modes: compatibleRuntimeModes, mode: compatibleRuntimeMode } = resolveCompatibleRuntimeMode( runtimeMode, @@ -954,7 +902,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) (multipleModelSelections === null ? providerSendBlockReason : null))); const isSendDisabled = sendDisabledReason !== null; const selectedProviderStatus = selectedProviderEntry?.snapshot ?? null; - const compactCommandAvailable = providerSupportsManualCompaction(selectedProviderEntry); const selectedProviderSkills = selectedProviderStatus ? resolveProviderSkillsForCwd(selectedProviderStatus, gitCwd) : NO_PROVIDER_SKILLS; @@ -970,11 +917,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) const selectedPromptEffort = composerProviderState.promptEffort; const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch; - const { enabled: planModeUiEnabled, interactionMode } = resolveComposerInteractionMode({ - planModeEnabled: settings.planModeEnabled, - provider: selectedProviderStatus, - interactionMode: requestedInteractionMode, - }); const selectedModelSelection: ModelSelection = createModelSelection( selectedInstanceId, selectedModel, @@ -1000,19 +942,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) : (normalizeModelSlug(selectedModelForPicker, selectedProvider) ?? selectedModelForPicker); })(); - const activeThreadModelDisplayName = resolveContextWindowModelDisplayName( - activeThreadModelSelection, - modelOptionsByInstance, - ); - const reserveContextWindowMeter = shouldReserveContextWindowMeter({ - meterEnabled: settings.contextWindowMeterEnabled, - detailLoading: props.threadSyncPhase === "loading", - threadStarted: threadShellHasStarted(props.activeThreadShell), - providerReportsContextWindow: selectedProviderStatus - ? selectedProviderStatus.reportsContextWindow === true - : null, - }); - const [composerCursor, setComposerCursor] = useState(() => { const value = isLiteralPendingAnswer ? (activePendingProgress?.customAnswer ?? prompt) : prompt; return collapseExpandedComposerCursor(value, value.length, isLiteralPendingAnswer); @@ -1159,7 +1088,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) pullRequestProjectId, pullRequestRepository, threadPullRequests, - planModeUiEnabled, sideChatAvailable: props.sideChatAvailable === true, sideChatDisabledReason: props.sideChatDisabledReason ?? null, freshSessionAvailable: props.onFreshSession !== undefined, @@ -1205,16 +1133,14 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) const activePendingUserInput = pendingUserInputs[0] ?? null; const isChoiceOnlyPendingQuestion = activePendingProgress?.activeQuestion?.allowCustomAnswer === false; - const showComposerTopDrawer = - pendingUserInputs.length > 0 || - (!isComposerCollapsedMobile && showPlanFollowUpPrompt && activeProposedPlan !== null); + const showComposerTopDrawer = pendingUserInputs.length > 0; const showCollapsedMobilePromptRow = isComposerCollapsedMobile; const showComposerAttachAction = fileStagingLimit !== null && (!activePendingProgress || (supportsQuestionAttachments && activePendingProgress.activeQuestion?.allowCustomAnswer !== false)); - const composerFooterHasWideActions = showPlanFollowUpPrompt || activePendingProgress !== null; + const composerFooterHasWideActions = activePendingProgress !== null; const editPromptFromTraits = (edit: (prompt: string) => string) => { const currentPrompt = getComposerDraft(composerDraftTarget)?.prompt ?? ""; @@ -1961,14 +1887,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) if (applied) setComposerHighlightedItemId(null); return; } - if (!planModeUiEnabled) return; - void handleInteractionModeChange(item.command === "plan" ? "plan" : "default"); - const applied = applyPromptReplacement(trigger.rangeStart, trigger.rangeEnd, "", { - expectedText: snapshot.value.slice(trigger.rangeStart, trigger.rangeEnd), - }); - if (applied) { - setComposerHighlightedItemId(null); - } return; } if (item.type === "provider-slash-command") { @@ -2130,7 +2048,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) if (activePendingProgress) { return activePendingProgress.isLastQuestion && Boolean(activePendingResolvedAnswers); } - return showPlanFollowUpPrompt || composerSendState.hasSendableContent; + return composerSendState.hasSendableContent; }; const submitComposerRef = useRef< @@ -2317,7 +2235,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) isCompacting, followUpBehavior, isSoftwareKeyboardOpen, - planModeUiEnabled, isLiteralPendingAnswer, pendingUserInputs, promptHistoryEntries, @@ -2338,7 +2255,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) setComposerTrigger, setComposerHighlightedItemId, dismissComposerTrigger, - toggleInteractionMode, nudgeComposerMenuHighlight, onSelectComposerItem, resolveActiveComposerTrigger, @@ -2435,13 +2351,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) reportedModelSelection={reportedModelSelection} promptUltrathink={promptUltrathink} onPromptEdit={editPromptFromTraitsStable} - planModeEnabled={settings.planModeEnabled} - planModeUiEnabled={planModeUiEnabled} - interactionMode={interactionMode} compatibleRuntimeMode={compatibleRuntimeMode} defaultRuntimeMode={props.defaultRuntimeMode} compatibleRuntimeModeOptions={compatibleRuntimeModeOptions} - toggleInteractionMode={toggleInteractionMode} handleRuntimeModeChange={handleRuntimeModeChange} composerControlsCollapsed={composerControlsCollapsed} effortDisplay={settings.effortDisplay} @@ -2861,12 +2773,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) void onInterrupt(); }; const handleInterruptPrimaryActionStable = useStableCallback(handleInterruptPrimaryAction); - const handleImplementPlanInNewThreadPrimaryAction = () => { - void onImplementPlanInNewThread(); - }; - const handleImplementPlanInNewThreadPrimaryActionStable = useStableCallback( - handleImplementPlanInNewThreadPrimaryAction, - ); const scheduleComposerCollapseCheck = () => { if (!isMobileViewport || mobileComposerExpandInFlightRef.current) { return; @@ -2986,7 +2892,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) reportedModelSelection, prompt: promptRef.current, cursor: readComposerSnapshotStable().expandedCursor, - planModeEnabled: settings.planModeEnabled, direction, }); if (!change) return; @@ -3124,8 +3029,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) selectedProvider, selectedModel, selectedProviderModels, - interactionMode, - interactionModeEnabled: planModeUiEnabled, }), setMultipleModelSelections, validateProviderInput: (providerInput: string) => { @@ -3179,7 +3082,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) selectedModel, selectedInstanceId, reportedModelSelection, - settings.planModeEnabled, selectedModelSelection, multipleModelSelections, setMultipleModelSelections, @@ -3189,8 +3091,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) selectedPromptEffort, selectedProvider, selectedProviderModels, - interactionMode, - planModeUiEnabled, compactThreadContextStable, ], ); @@ -3320,11 +3220,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) onAdvance={onAdvanceActivePendingUserInput} onDismiss={onDismissActivePendingUserInput} /> - ) : !isComposerCollapsedMobile && showPlanFollowUpPrompt && activeProposedPlan ? ( - ) : null} @@ -3530,13 +3425,11 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) ? isChoiceOnlyPendingQuestion ? "Choose an option above" : "Type your own answer, or leave this blank to use the selected option" - : showPlanFollowUpPrompt && activeProposedPlan - ? "Add feedback to refine the plan, or leave this blank to implement it" - : projectSelectionRequired - ? "Choose a project above to start a thread" - : showProviderUnavailable - ? "Enable a provider in Settings to send a message" - : DEFAULT_COMPOSER_PLACEHOLDER + : projectSelectionRequired + ? "Choose a project above to start a thread" + : showProviderUnavailable + ? "Enable a provider in Settings to send a message" + : DEFAULT_COMPOSER_PLACEHOLDER } disabled={ projectSelectionRequired || @@ -3557,8 +3450,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) pendingAction={pendingPrimaryAction} isRunning={false} canInterrupt={false} - showPlanFollowUpPrompt={false} - promptHasText={false} isSendBusy={isSendBusy} sendDisabledReason={voiceSendDisabledReason} isEnvironmentUnavailable={ @@ -3571,7 +3462,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) preserveComposerFocusOnPointerDown onPreviousPendingQuestion={onPreviousActivePendingUserInputQuestion} onInterrupt={handleInterruptPrimaryActionStable} - onImplementPlanInNewThread={handleImplementPlanInNewThreadPrimaryActionStable} /> ) : null} @@ -3623,11 +3513,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) 0 || voiceInputActive} voiceInputActive={voiceInputActive} isSendBusy={isSendBusy} sendDisabledReason={voiceSendDisabledReason} @@ -3669,15 +3550,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) onResume={onResume} onPreviousPendingQuestion={onPreviousActivePendingUserInputQuestion} onInterrupt={handleInterruptPrimaryActionStable} - onImplementPlanInNewThread={handleImplementPlanInNewThreadPrimaryActionStable} compactBeforeSendTokens={props.resumeCompactionTokens} keepFullHistory={props.keepFullHistory} onToggleKeepFullHistory={props.onToggleKeepFullHistory} - compactDisabled={compactDisabled || noProviderAvailable || isSendBusy} - compactDisabledReason={resolvedCompactDisabledReason} - {...(compactCommandAvailable - ? { onCompactContext: compactThreadContextStable } - : {})} > {speechInput.available ? ( <> diff --git a/apps/web/src/components/chat/CompactComposerControlsMenu.tsx b/apps/web/src/components/chat/CompactComposerControlsMenu.tsx index e5fbddf56c769..1472f55bf60b8 100644 --- a/apps/web/src/components/chat/CompactComposerControlsMenu.tsx +++ b/apps/web/src/components/chat/CompactComposerControlsMenu.tsx @@ -1,29 +1,19 @@ -import { ProviderInteractionMode, RuntimeMode } from "@t3tools/contracts"; +import type { RuntimeMode } from "@t3tools/contracts"; import { memo, useRef } from "react"; import { EllipsisIcon } from "lucide-react"; -import { - Menu, - MenuPopup, - MenuRadioGroup, - MenuRadioItem, - MenuSeparator as MenuDivider, - MenuTrigger, -} from "../ui/menu"; +import { Menu, MenuPopup, MenuTrigger } from "../ui/menu"; import { ComposerControl, ComposerControlIcon } from "./ComposerControl"; import { focusRequestedPermission, useComposerMenuProps } from "./composerEventScope"; import { useComposerMenuState } from "./useComposerMenuState"; import { ComposerPermissionsSection, type RuntimeModeOption } from "./ComposerFooterControls"; export const CompactComposerControlsMenu = memo(function CompactComposerControlsMenu(props: { - interactionMode: ProviderInteractionMode; runtimeMode: RuntimeMode; runtimeModeOptions: ReadonlyArray; - showInteractionModeToggle: boolean; - showPermissions?: boolean; + showPermissions: boolean; size?: "sm" | "xs"; /** Close the portaled popup when its trigger hides. */ hidden?: boolean; - onToggleInteractionMode: () => void; onRuntimeModeChange: (mode: RuntimeMode) => void; }) { const composerFloatingLayerProps = useComposerMenuProps(); @@ -44,36 +34,20 @@ export const CompactComposerControlsMenu = memo(function CompactComposerControls size={size} className="shrink-0" aria-label="More composer controls" - data-composer-shortcut={props.showPermissions !== false ? "composer.mode" : undefined} + data-composer-shortcut={props.showPermissions ? "composer.mode" : undefined} /> } > - {props.showInteractionModeToggle ? ( - <> -
Mode
- { - if (!value || value === props.interactionMode) return; - props.onToggleInteractionMode(); - }} - > - Chat - Plan - - {props.showPermissions !== false ? : null} - - ) : null} - {props.showPermissions === false ? null : ( + {props.showPermissions ? ( - )} + ) : null}
); diff --git a/apps/web/src/components/chat/ComposerControlsBar.tsx b/apps/web/src/components/chat/ComposerControlsBar.tsx index 0002beeaf9fef..7c4e6f04b77a0 100644 --- a/apps/web/src/components/chat/ComposerControlsBar.tsx +++ b/apps/web/src/components/chat/ComposerControlsBar.tsx @@ -15,7 +15,6 @@ import { type ModelSelection, type ProviderDriverKind, type ProviderInstanceId, - type ProviderInteractionMode, type RuntimeMode, type ServerProvider, } from "@t3tools/contracts"; @@ -51,16 +50,12 @@ export function ComposerControlsBar(props: { reportedModelSelection: ChatComposerProps["reportedModelSelection"]; promptUltrathink: PromptUltrathink; onPromptEdit: (edit: (prompt: string) => string) => void; - planModeEnabled: boolean; - planModeUiEnabled: boolean; effortDisplay: EffortDisplay; /** The strip under a new thread names the permissions, so the row does not repeat them. */ permissionsShownInStrip: boolean; - interactionMode: ProviderInteractionMode; compatibleRuntimeMode: RuntimeMode; defaultRuntimeMode: RuntimeMode | undefined; compatibleRuntimeModeOptions: ReadonlyArray; - toggleInteractionMode: () => void; handleRuntimeModeChange: (mode: RuntimeMode) => void; composerControlsCollapsed: boolean; composerControlsHidden: boolean; @@ -102,13 +97,9 @@ export function ComposerControlsBar(props: { reportedModelSelection, promptUltrathink, onPromptEdit, - planModeEnabled, - planModeUiEnabled, - interactionMode, compatibleRuntimeMode, defaultRuntimeMode, compatibleRuntimeModeOptions, - toggleInteractionMode, handleRuntimeModeChange, composerControlsCollapsed, composerControlsHidden, @@ -147,7 +138,6 @@ export function ComposerControlsBar(props: { pickerOwnsModeShortcut, } = resolveComposerControlsOwnership({ matchesDefault: accessMatchesDefault || props.permissionsShownInStrip, - planModeUiEnabled, collapsed: composerControlsCollapsed, measuredHiddenBlockCount, }); @@ -163,7 +153,6 @@ export function ComposerControlsBar(props: { reportedModelSelection, promptUltrathink, onPromptEdit, - planModeEnabled, }); const effort = describeComposerEffort(traitsInput, props.effortDisplay, controlSize); const restingBlockDefs = showModeBlock @@ -172,15 +161,12 @@ export function ComposerControlsBar(props: { id: "mode", content: (