Skip to content

Thinking-level (reasoning effort) picker in the chat composer #44

Description

@DragonnZhang

Problem or Motivation

OpenWork supports six per-session thinking levels (off / low / medium /
high / xhigh / max), but there is no way to change the thinking level from
the chat composer
. Today the only place to influence it is Settings → AI, which
sets a per-workspace default for new chats. To dial reasoning effort up for a
single hard task or down for a quick one, you have to leave the conversation, open
settings, change the default, and start a new chat.

Comparable desktop apps put this control right in the composer, next to the model
picker: Claude Code Desktop ships a dedicated effort menu (⌘⇧E) alongside its
model menu, so you can raise or lower reasoning effort per message without leaving
the prompt.

OpenWork already has the neighbor for it — the composer has a model picker
dropdown — but the thinking-level equivalent is missing even though all the
plumbing is already in place:

  • The composer (FreeFormInput) already receives thinkingLevel and
    onThinkingLevelChange props — they are threaded from ChatPage /
    DraftChatPageChatDisplayChatInputZoneInputContainer
    FreeFormInput, and both pages already persist the value (draft
    createOptions.thinkingLevel / session options). But FreeFormInput never
    renders a trigger for it — the prop is only read for an ACP command refresh.
  • The level metadata already exists: THINKING_LEVELS (id + nameKey +
    descriptionKey) in packages/shared/src/agent/thinking-levels.ts, and the
    thinking.* translation keys are already present in all locales.

So the value is fully wired end-to-end — the app just has no UI to set it from the
place you actually compose.

Proposed Solution

Add a thinking-level picker to the composer toolbar, immediately before the
model picker, mirroring the existing model-picker dropdown:

  • A toolbar button showing the current level's name (e.g. "Medium") with a brain
    icon and a chevron; tooltip "Thinking".
  • Opening it lists all six levels (THINKING_LEVELS order) with name +
    description, a check on the active level, and calls the already-wired
    onThinkingLevelChange(level) on select — so the choice persists exactly like a
    model change does.
  • Hidden in compactMode (same rule the model picker uses).

This is a frontend-only change:

  • It renders a Radix dropdown (the same StyledDropdownMenu* primitives the model
    picker uses) and calls the existing onThinkingLevelChange prop. No backend / no
    qwen-code change — the value already flows to the runtime today.
  • It introduces no new i18n keys: level names/descriptions reuse the existing
    thinking.* keys and the trigger tooltip reuses settings.ai.thinking.

Alternatives Considered

  • Settings → AI thinking default already exists, but it is a workspace
    default for new chats
    , not a per-conversation control. You can't raise effort
    for the current task without starting over.
  • A keyboard-only shortcut (à la ⌘⇧E) could open a menu, but OpenWork's
    established pattern for composer controls is a visible toolbar dropdown (the
    model picker), so a visible picker is the more discoverable, consistent fit. A
    shortcut can be layered on later.

Additional Context

Feasibility: frontend-only / doable. Verified against apps/electron — the
thinkingLevel / onThinkingLevelChange props already reach FreeFormInput and
both the draft and active chat pages already persist the value; only the UI trigger
is missing.

Acceptance criteria (CDP e2e assertion)

A new e2e/assertions/thinking-level-picker.assert.ts drives the real built app
over CDP and asserts the full path:

  1. The composer renders a thinking-level trigger button.
  2. Clicking it opens a menu listing all six thinking levels.
  3. The trigger reflects a known current level (the default), and the active level
    is marked as selected in the menu.
  4. Selecting a different level closes the menu and updates the trigger
    label to the newly selected level — proving the picker actually changes the
    session's thinking level, not merely displays a list.

Part of the autonomous desktop-feature loop (loop-bot).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions