EN / field notes OpenHands feature map

OpenHands / F27

Workspace tools: terminal, browser, planner, tasks and usage

The conversation's right-hand drawer holds the agent's workspace tools: a read-only Terminal that mirrors the agent's shell commands, a Browser tab that shows the agent browser tool's last screenshot, a Planner that renders .agents_tmp/PLAN.md (or starts a planning helper), a Task List that mirrors the agent's task_tracker, and a Usage tab with the context meter, a manual "Compact context" action and token/cost totals. Around them sit the composer's context-window ring, the chat header's Overview column (peek, pinnable rows, secondary drawer), and two client tools the agent can call to drive the UI: canvas_ui_control (open a tab or a file) and launch_child_conversation.

30 mapped behaviors · 21 recipes and supporting checks · source snapshot 9 October 2026
From upstream main at 8793c111. Read the maintained source.

How to get to it

  • Conversation header panel button (right-panel-toggle, aria "Show panel"), then a drawer tab: conversation-tab-files, -commits, -tasklist (only after the agent used task_tracker), -planner, -terminal, -browser, -usage. Clicking the active tab closes the drawer (F08).
  • Drawer tabs menu: the "..." in tabs-pane-header, then conversation-tabs-menu-open-<tab> (also pin/unpin, F08).
  • Direct URL /conversations/<id> (drawer closed after load) or /conversations/<id>/panel (full-width drawer with Back at 1024 px or narrower, the phone entry, F07.panel-route; wider, it redirects to /conversations/<id> with the drawer open).
  • Composer context ring (context-window-meter) > Usage or bar; conversation "..." > Display Usage and Cost (F07.display-cost).
  • Chat header (i) (conversation-overview-toggle): hover for the peek, click for the Overview column and its "..." menu.
  • Composer + (chat-plus-button) > Show Available Skills / Hooks / Agent Tools & Metadata (also in the conversation "..." menu, F07).
  • The agent itself: canvas_ui_control (open a tab or file), launch_child_conversation, and file-path links in its messages (markdown-file-path-link).

Before you start

Start with the common launch and health checks, then follow this family’s preconditions in order. Recipes share the fixtures and state named below.

Preconditions:

  • Baseline state (launched, doctored, onboard --skip done) and control-openhands llm preset deepseek (every populated state needs real agent turns).
  • Desktop viewport unless a bullet says phone.
  • F27.browser-screenshot is blocked when the agent server logs Error preloading chromium (no browser tools in Agent Tools & Metadata > Available Tools); it needs an agent server with working Chromium.
  • F27.plugins-modal needs a conversation started with at least one plugin (the menu item is hidden otherwise).
  • F27.terminal-waiting needs a conversation in the error state: arrange one with a profile whose key is invalid (no model cost; see the bullet).
  • F27.usage-provider-balance (card shown) needs an OpenRouter key; with DeepSeek only the hidden case is checkable.
  • F27.overview-identity needs a repo with a GitHub origin, its own so the shared qa-repo of F08 keeps none: control-openhands fixture git-repo --name qa-f27-repo --remote https://github.com/qa-example/qa-f27-repo.git (--remote only sets origin, also when the repo already exists, which the output reports as remoteChanged; no model is needed for the rows).

Behavior inventory

30 stable behavior IDs and their expected behavior
  • F27.terminal-empty with the runtime active and no command run yet, the Terminal tab reads "No terminal output yet. Commands run by the agent will appear here." Read recipe ↓
  • F27.terminal-waiting while the runtime is starting or the conversation is in an error state, the Terminal tab reads "Waiting for runtime to start..."; a paused (stopped) conversation shows the empty message instead, by design. Read recipe ↓
  • F27.terminal-output the agent's commands appear as $ <command> followed by their output; typing into the terminal does nothing (stdin is disabled). Read recipe ↓
  • F27.terminal-history-reload after a page reload the earlier commands and output are still shown (issue #17566). Read recipe ↓
  • F27.terminal-live-append a command the agent runs while the page is open is appended live. Read recipe ↓
  • F27.browser-empty before any browsing the URL bar reads "No URL loaded", "Open in New Tab" is a disabled button, and the body reads "No page loaded yet. Ask OpenHands to open a URL. Example: "Open https://example.com"". Read recipe ↓
  • F27.browser-screenshot after the agent's browser tool ran, the tab shows the last screenshot under a bar with its URL and an enabled "Open in New Tab" link. Read recipe ↓
  • F27.planner-empty with no plan the Planner reads "There is currently no plan for this repo" with an enabled "Create a plan" button; the drawer's "Build ⌘↩" button is disabled. Read recipe ↓
  • F27.planner-create "Create a plan" toasts "Planning agent initialized", switches the composer to plan mode ("Let’s work on a plan") and disables itself, also after a reload. Read recipe ↓
  • F27.planner-plan when the workspace has .agents_tmp/PLAN.md, the Planner renders it as Markdown with an enabled "Build ⌘↩" bar. Read recipe ↓
  • F27.planner-build "Build ⌘↩" sends "Execute the plan based on the .agents_tmp/PLAN.md file." to the agent. Read recipe ↓
  • F27.tasklist the Task List tab appears only once the agent used task_tracker; it lists each task with its status icon and highlights the in-progress one; the drawer's tabs menu opens it too. Read recipe ↓
  • F27.usage-metrics the Usage tab shows the context meter ("X% used (Y% left)", tokens / window), Token Usage (Input, Cache Hit, Cache Write, Output, Total) and Total Cost. Read recipe ↓
  • F27.usage-empty before any metrics arrive the Usage tab reads "No metrics data available". Read recipe ↓
  • F27.usage-compact "Compact context" (agent idle) toasts "Context compaction started" then "Context compacted" (or "…freed N tokens"), and the agent server records a Condensation; the (i) tooltip explains it. Read recipe ↓
  • F27.usage-provider-balance the provider balance card shows only when the agent server reports a balance (OpenRouter); otherwise it is absent without an error message. Read recipe ↓
  • F27.composer-context-meter the composer's context ring opens a popover with the fill label, a Compact context button, tokens and a Usage item; the bar or Usage item opens the Usage tab; Escape (focus returns to the ring) or an outside click closes it. Read recipe ↓
  • F27.overview-peek while the drawer is open, hovering the chat header (i) button shows a peek of the Overview rows (with the drawer closed there is no peek; clicking opens the column). Read recipe ↓
  • F27.overview-pin the Overview "..." menu pins and unpins rows (Workspace, Git parts); an unpinned row stays hidden after a reload. Read recipe ↓
  • F27.overview-menu-open the Overview "..." menu's Git > Commits item opens the drawer on Commits with Uncommitted collapsed; Git > Changes opens Commits with Uncommitted expanded; both close the Overview. Read recipe ↓
  • F27.overview-identity the Overview's Workspace row shows the attached folder's basename (None without a workspace), and once the workspace's origin is a GitHub remote the Git block adds an owner/repo link to the repository and a branch link. Read recipe ↓
  • F27.overview-drawer Overview menu > Pull Requests opens the secondary drawer with a close button; without a git repository it reads "Connect a git repository to view pull requests and issues". Read recipe ↓
  • F27.canvas-ui-open-tab the agent's canvas_ui_control open_tab opens the drawer on the requested tab (files, browser, terminal, planner, tasklist), and the choice persists like a user tab click. Read recipe ↓
  • F27.canvas-ui-navigate-file canvas_ui_control navigate_to_file (and clicking a file path in chat) opens Files with that file selected. Read recipe ↓
  • F27.canvas-ui-phone at phone width an agent open_tab makes the requested tab visible on /conversations/<id>/panel. Read recipe ↓
  • F27.launch-child the agent's launch_child_conversation (local) toasts "Launched a local child conversation" with an Open link; the child runs, and the launch result ([child-conversation] {"status":"launched",…} with the child id) is posted back to the parent agent as a user message. Read recipe ↓
  • F27.info-modals-phone the composer + menu's Skills, Hooks and Agent Tools & Metadata modals fit a 390 px viewport (issue #17562); content is covered by F07. Read recipe ↓
  • F27.plugins-modal a conversation started with plugins offers a plugins item that lists active-plugin-<name> rows.
  • F27.phone at 390 px the panel route shows the Usage, Task List and Planner tabs without overflow, and the tab row stays visible. Read recipe ↓

Readable recipes

Read each script from top to bottom. Code is copied from the map; prose gives the action, expected observation, and conditions. <id>, <run> and similar placeholders stand for values from your own run. Short forms such as browser count continue the same control-openhands invocation; they are kept as documented.

Expected observations describe the recipe’s contract. Captures below selected recipes show representative real states from this snapshot; they do not mark every mapped behavior as passed. Follow cleanup before moving to another family.

Empty tools #

  1. Wait
    control-openhands conversation start --prompt "Reply with only the word OK. Do not run any tools." --wait --timeout 240

    (note the id as <quiet-id>), then

  2. Do
    control-openhands browser click 'testid=right-panel-toggle'
  3. Do
    control-openhands browser click 'testid=conversation-tab-terminal'
  4. Wait
    control-openhands browser wait-text 'No terminal output yet' --timeout 10000
  5. Do
    control-openhands browser click 'testid=conversation-tab-browser'
  6. Check
    control-openhands browser text 'testid=browser-chrome-url'

    (No URL loaded),

  7. Check
    control-openhands browser count 'testid=browser-chrome-open-external'

    (0),

  8. Do
    control-openhands browser eval "[...document.querySelectorAll('[data-testid=browser-chrome-bar] button')].map(b=>[b.disabled,b.getAttribute('aria-label')])"

    ([[true,"Open in New Tab"]]) and

  9. Wait
    control-openhands browser wait-text 'No page loaded yet' --timeout 5000
  10. Do
    control-openhands browser click 'testid=conversation-tab-planner'
  11. Wait
    control-openhands browser wait-text 'There is currently no plan for this repo' --timeout 10000
  12. Check
    control-openhands browser enabled 'role=button[name="Create a plan"]'

    (true) and

  13. Check
    control-openhands browser enabled 'testid=planner-tab-build-button'

    (false).

  14. Expect
    The tab bar has no conversation-tab-tasklist.
  15. Note
    Screenshot with
  16. Do
    control-openhands browser screenshot --feature F27.browser-empty --name empty
Finished OK conversation beside an empty Terminal panel.
A no-tools conversation leaves the Terminal panel with no output. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

A tiny real DeepSeek conversation replied OK; Terminal shows No terminal output yet.

One no-tools model request; no terminal command, browser tool, plan or task-list execution demonstrated.

How this screenshot was taken

agent server: 1.53.0 · automation: 1.19.0 (launcher default) · canvas: 1.26.0

control-openhands conversation start --prompt 'Reply with only the word OK. Do not run any tools.' --wait --timeout 120
control-openhands browser click testid=right-panel-toggle
control-openhands browser click testid=conversation-tab-terminal
control-openhands browser wait-text 'No terminal output yet' --timeout 10000
control-openhands browser screenshot --feature F27.terminal-empty --name empty-terminal

Usage totals #

  1. Note
    In <quiet-id> run
  2. Do
    control-openhands browser click 'testid=conversation-tab-usage'
  3. Check
    control-openhands browser text 'testid=usage-panel'
  4. Check
    control-openhands browser attr 'testid=context-meter-bar' style
  5. Expect
    The text reads Context Window, 1% used (99% left), <tokens> / 1,000,000, Compact context, Token Usage, Input, Cache Hit, Cache Write, Output, Total and Total Cost $0.00…; the bar style is width: <percent>%.
  6. Check
    control-openhands browser count 'testid=provider-balance-card'

    is 0 with DeepSeek (the GET /api/llm/balance 404 is expected).

  7. Note
    Screenshot with
  8. Do
    control-openhands browser screenshot --feature F27.usage-metrics --name usage
Usage panel showing context window, token counts and total model cost beside the OK reply.
Usage breaks down context, tokens and cost for the real conversation. CLI capture · 1440 × 1000 · 9 October 2026 · Canvas 8793c111

Usage panel shows context-window percentage, token usage and cost for the completed conversation.

DeepSeek local run only; provider balance, compact-context effects and other workspace tools not asserted. The expected /api/llm/balance 404 occurred; pageErrors was 0. Displayed model cost was $0.0016.

How this screenshot was taken

agent server: 1.53.0 · automation: 1.19.0 (launcher default) · canvas: 1.26.0

control-openhands conversation start --prompt 'Reply with only the word OK. Do not run any tools.' --wait --timeout 120
control-openhands browser click testid=right-panel-toggle
control-openhands browser click testid=conversation-tab-usage
control-openhands browser text testid=usage-panel
control-openhands browser screenshot --feature F27.usage-metrics --name usage

Compact context #

  1. Note
    With the agent idle run
  2. Do
    control-openhands browser tooltip 'testid=compact-context-info'

    (Summarizes older messages to free context space while keeping recent details.),

  3. Do
    control-openhands browser mouse-click 1300 700

    (closes the tooltip),

  4. Do
    control-openhands browser click 'testid=compact-context-button' --observe '[data-testid=compact-context-button]' --observe-ms 4000
  5. Check
    control-openhands browser toasts --history

    (a plain browser toasts right after the click often lists only the first toast: the second lands about 2.5 s later) and

  6. Check
    control-openhands conversation events <quiet-id> --last 4
  7. Expect
    The last two history toasts are Context compaction started and Context compacted (a short history frees nothing, so no token count); the events include CondensationRequest and Condensation.

Composer context ring #

  1. Do
    control-openhands browser click 'testid=conversation-tab-files'
  2. Do
    control-openhands browser click 'testid=context-window-meter'
  3. Check
    control-openhands browser text 'testid=context-window-meter-popover'

    (Context Window, 1% used (99% left), Compact context, 11.7k / 1.0M-style tokens, Usage).

  4. Do
    control-openhands browser click 'testid=context-window-plan-usage'
  5. Check
    control-openhands browser count 'testid=usage-panel'

    is 1 and the popover count is 0.

  6. Note
    Reopen the ring and
  7. Do
    control-openhands browser click 'testid=context-window-meter-bar-button'
  8. Note
    opens Usage the same way.
  9. Note
    Reopen it,
  10. Do
    control-openhands browser click 'testid=context-window-compact-button'
  11. Note
    and, a few seconds later,
  12. Check
    control-openhands browser toasts --history
  13. Note
    ends with Context compaction started, then Context compacted; the popover stays open.
  14. Do
    control-openhands browser press Escape
  15. Note
    closes it (control-openhands browser count 'testid=context-window-meter-popover' is 0) and returns focus to the ring (control-openhands browser eval "document.activeElement.dataset.testid" is context-window-meter).
  16. Note
    Reopen it with
  17. Do
    control-openhands browser click 'testid=context-window-meter'
  18. Do
    control-openhands browser mouse-click 600 400
  19. Note
    closes it too (0).

Agent-driven tools #

  1. Wait
    control-openhands conversation start --prompt "Do these steps in order, in the workspace only. 1) Use the task_tracker tool to set exactly three tasks: 'qa-one' status done, 'qa-two' status in_progress, 'qa-three' status todo. 2) Run the shell command: echo QA_TERM_MARK_1 3) Create the file .agents_tmp/PLAN.md with the content '# QA Plan' followed by a bullet list of three items: alpha, beta, gamma. 4) Call the canvas_ui_control tool with command open_tab and tab terminal. Then reply DONE." --wait --timeout 300

    (note <tools-id>).

  2. Expect
    The drawer was closed; the agent's tool call opened it:
  3. Check
    control-openhands browser attr 'testid=right-panel-toggle' aria-pressed

    is true and

  4. Check
    control-openhands browser text 'testid=tabs-pane-header'
  5. Note
    starts with Terminal.
  6. Check
    control-openhands browser text '.xterm-rows'

    shows $ echo QA_TERM_MARK_1, QA_TERM_MARK_1 and the PLAN.md command with its output.

  7. Do
    control-openhands browser click '.xterm-screen'
  8. Do
    control-openhands browser press KeyZ
  9. Do
    control-openhands browser press Enter
  10. Note
    change neither the rows nor the event list (conversation events <tools-id> --last 1).
  11. Check
    control-openhands conversation events <tools-id> --kinds ActionEvent,ObservationEvent,MessageEvent

    shows open_tab with Tool call dispatched to client.

Terminal after reload #

  1. Note
    Before clicking any other tab in <tools-id> (a user tab click is persisted and would mask the next check), run
  2. Do
    control-openhands browser reload
  3. Do
    control-openhands browser click 'testid=right-panel-toggle'
  4. Check
    control-openhands browser text 'testid=tabs-pane-header'

    (read now for F27.canvas-ui-open-tab below; it starts with Terminal, so the drawer is already on Terminal: do not click conversation-tab-terminal, because clicking the active tab closes the drawer) and

  5. Check
    control-openhands browser text '.xterm-rows'
  6. Note
    Expected: the earlier commands are still listed.
  7. Note
    Known failure (#17566 reproduces, 2026-10-06): the text is empty and the tab reads "No terminal output yet" while the chat still lists the actions.
  8. Note
    Live append is checked after the Build step below.

Agent tab survives reload #

  1. Expect
    The tabs-pane-header text read in the reload step above (right after right-panel-toggle) starts with Terminal, the agent's last choice: src/services/canvas-ui.ts saves the tab per conversation like a user click (#18045), and
  2. Do
    control-openhands browser eval "JSON.parse(localStorage.getItem('conversation-state-<tools-id>')).selectedTab"

    is terminal (browser screenshot --feature F27.canvas-ui-open-tab --name reload-reopens-terminal).

  3. Note
    For contrast,
  4. Do
    control-openhands browser click 'testid=conversation-tab-usage'
  5. Do
    control-openhands browser reload
  6. Do
    control-openhands browser click 'testid=right-panel-toggle'
  7. Note
    : a user-clicked tab is restored (Usage).

Task List #

  1. Note
    In <tools-id> run
  2. Do
    control-openhands browser click 'testid=conversation-tab-tasklist'
  3. Wait
    control-openhands browser wait '[data-active="true"] >> has-text=qa-two' --timeout 10000

    (the rows render a moment after the click; an immediate count reads 0),

  4. Check
    control-openhands browser count '[data-active="true"] >> has-text=qa-two'

    (1) and

  5. Check
    control-openhands browser count '[data-active="false"] >> has-text=qa-three'

    (1); screenshot with

  6. Do
    control-openhands browser screenshot --feature F27.tasklist --name tasks

    (done/in-progress/todo icons).

  7. Note
    Menu entry:
  8. Do
    control-openhands browser click 'testid=tabs-pane-header >> testid=ellipsis-button'
  9. Do
    control-openhands browser click 'testid=conversation-tabs-menu-open-tasklist'
  10. Note
    the header starts with Task List.

Plan and Build #

  1. Note
    In <tools-id> run
  2. Do
    control-openhands browser click 'testid=conversation-tab-planner'
  3. Check
    control-openhands browser snapshot 'testid=app-route'
  4. Note
    : it ends with heading "QA Plan" [level=1] and a list alpha, beta, gamma;
  5. Check
    control-openhands browser enabled 'testid=planner-tab-build-button'

    is true.

  6. Do
    control-openhands browser click 'testid=planner-tab-build-button'
  7. Check
    control-openhands browser count 'testid=user-message >> has-text=Execute the plan based on the .agents_tmp/PLAN.md file.'

    (1) and

  8. Wait
    control-openhands conversation wait <tools-id> --timeout 240
  9. Note
    the agent reads PLAN.md and answers.
  10. Note
    Live append (F27.terminal-live-append):
  11. Do
    control-openhands browser click 'testid=conversation-tab-terminal'
  12. Check
    control-openhands browser text '.xterm-rows'
  13. Note
    now show the new $ cat .agents_tmp/PLAN.md block and its output (but not the pre-reload history).

Create a plan #

  1. Note
    In <quiet-id> (no plan yet) run
  2. Do
    control-openhands browser goto /conversations/<quiet-id>
  3. Do
    control-openhands browser click 'testid=right-panel-toggle'

    (the browser is on <tools-id>, and a fresh load has the drawer closed; it reopens on the tab last chosen there, Usage after the context-ring bullet (its Usage item and bar save usage): if that is already Planner, skip the next click, which would close the drawer), then

  4. Do
    control-openhands browser click 'testid=conversation-tab-planner'
  5. Do
    control-openhands browser click 'role=button[name="Create a plan"]'
  6. Check
    control-openhands browser toasts

    (Planning agent initialized) and

  7. Check
    control-openhands browser enabled 'role=button[name="Create a plan"]'

    (false).

  8. Expect
    The composer placeholder is Let’s work on a plan (browser screenshot --feature F27.planner-create --name after-create).
  9. Note
    After
  10. Do
    control-openhands browser reload
  11. Note
    and reopening Planner the button stays disabled, and
  12. Check
    control-openhands api GET /api/conversations/<quiet-id>

    lists one sub_conversation_ids entry (the helper; it is not in the sidebar list).

Open a file from the agent #

  1. Note
    On /conversations/<tools-id> (control-openhands browser goto /conversations/<tools-id>; Create a plan left <quiet-id>, and --stay sends into the open page) run
  2. Wait
    control-openhands conversation start --stay --prompt "Call the canvas_ui_control tool with command navigate_to_file and path .agents_tmp/PLAN.md, then reply OK." --wait --timeout 200
  3. Check
    control-openhands browser count 'testid=file-quick-row-item-.agents_tmp/PLAN.md'

    (1) and

  4. Check
    control-openhands browser text 'testid=files-tab-content'

    (Rich, Plain, QA Plan, alpha…).

  5. Note
    Chat link entry:
  6. Do
    control-openhands browser click 'testid=right-panel-toggle'

    (closes the drawer),

  7. Do
    control-openhands browser click 'testid=markdown-file-path-link >> nth=0'
  8. Check
    control-openhands browser attr 'testid=right-panel-toggle' aria-pressed

    is true with the same file selected.

Launch a child #

  1. Note
    On /conversations/<tools-id> (code mode, not a plan-mode conversation) run
  2. Do
    control-openhands conversation start --stay --prompt "Call the launch_child_conversation tool with target local, isolation shared and task 'Reply with only the word PONG. Do not run any tools.' Then reply OK."
  3. Note
    and poll
  4. Check
    control-openhands browser toasts
  5. Note
    every few seconds: one toast reads Launched a local child conversation Open.
  6. Note
    Take the child id from
  7. Check
    control-openhands conversation events <tools-id> --kinds MessageEvent

    (the [child-conversation] {"status":"launched",…,"conversation_id":"<child-id>"…} message), then

  8. Wait
    control-openhands conversation wait <child-id> --timeout 120

    (finished) and

  9. Check
    control-openhands conversation events <child-id> --kinds MessageEvent

    (agent PONG).

  10. Check
    control-openhands browser count 'testid=conversation-card >> has-text=PONG'

    is 1.

Overview #

  1. Note
    On /conversations/<quiet-id> (control-openhands browser goto /conversations/<quiet-id>; Launch a child left <tools-id>) run
  2. Do
    control-openhands browser click 'testid=right-panel-toggle'

    (the peek exists only while the drawer is open; a fresh page load has it closed),

  3. Do
    control-openhands browser hover 'testid=conversation-overview-toggle'
  4. Check
    control-openhands browser text 'testid=conversation-overview-peek'

    (Overview, Changes, +0, -0, Workspace, None).

  5. Do
    control-openhands browser click 'testid=conversation-overview-toggle'
  6. Check
    control-openhands browser attr 'testid=conversation-overview-toggle' aria-pressed

    is true and right-panel-toggle is false.

  7. Note
    Pin:
  8. Do
    control-openhands browser click 'testid=conversation-overview-ellipsis'
  9. Do
    control-openhands browser click 'testid=conversation-overview-menu-pin-workspace'
  10. Check
    control-openhands browser count 'testid=conversation-overview-workspace'

    is 0 and the pin button's aria-pressed is false; after

  11. Do
    control-openhands browser reload
  12. Note
    and browser click 'testid=conversation-overview-toggle' the count is still 0.
  13. Note
    Re-pin the same way (count 1).
  14. Note
    Git parts work the same: conversation-overview-menu-pin-git-changes hides testid=conversation-overview-diffs (count 0, also after a reload) and a second click restores it.
  15. Note
    Close the menu with
  16. Do
    control-openhands browser press Escape
  17. Check
    control-openhands browser count 'testid=conversation-overview-context-menu'

    is 0,

  18. Do
    control-openhands browser eval "document.activeElement.dataset.testid"

    is conversation-overview-ellipsis and testid=conversation-overview-panel still counts 1 (a second click on the ellipsis closes the menu too).

  19. Note
    Changes:
  20. Do
    control-openhands browser click 'testid=conversation-overview-diffs'
  21. Note
    the overview panel count is 0, right-panel-toggle is pressed and tabs-pane-header starts with Commits.
  22. Note
    Drawer: reopen the Overview,
  23. Do
    control-openhands browser click 'testid=conversation-overview-ellipsis'
  24. Do
    control-openhands browser click 'testid=conversation-overview-menu-open-git-pull_requests'
  25. Check
    control-openhands browser text 'testid=conversation-overview-drawer'

    (Pull Requests / Connect a git repository to view pull requests and issues), then

  26. Do
    control-openhands browser click 'testid=conversation-overview-drawer-close'
  27. Wait
    control-openhands browser wait 'testid=conversation-overview-drawer' --state hidden --timeout 5000

    (an immediate visible still reads true during the slide-out animation).

Overview menu opens Commits #

  1. Note
    On /conversations/<tools-id> (it has an uncommitted .agents_tmp/PLAN.md; control-openhands browser goto /conversations/<tools-id>, since the Overview bullet is on <quiet-id>) run
  2. Do
    control-openhands browser reload
  3. Do
    control-openhands browser click 'testid=conversation-overview-toggle'
  4. Do
    control-openhands browser click 'testid=conversation-overview-ellipsis'
  5. Do
    control-openhands browser click 'testid=conversation-overview-menu-open-git-commits'
  6. Check
    control-openhands browser count 'testid=conversation-overview-panel'

    is 0, tabs-pane-header starts with Commits and

  7. Check
    control-openhands browser attr 'testid=uncommitted-changes-row-toggle' aria-expanded

    is false.

  8. Note
    Repeat with conversation-overview-menu-open-git-changes: same tab, aria-expanded true and
  9. Check
    control-openhands browser count 'testid=uncommitted-changes-row-content'
  10. Note
    1 (the .agents_tmp/PLAN.md row).
  11. Note
    Screenshot with
  12. Do
    control-openhands browser screenshot --feature F27.overview-menu-open --name changes

Workspace and repository identity #

  1. Wait
    control-openhands conversation start --workspace qa-f27-repo --prompt "List the files in this folder with ls. Do nothing else." --wait --timeout 240

    (note <repo-id>) and, staying on the page it leaves open,

  2. Do
    control-openhands browser click 'testid=conversation-overview-toggle'
  3. Wait
    control-openhands browser wait 'testid=conversation-overview-git-repo' --timeout 20000
  4. Check
    control-openhands browser text 'testid=conversation-overview-workspace'

    (Workspace, qa-f27-repo: the folder's basename, not its path),

  5. Check
    control-openhands browser text 'testid=conversation-overview-git-repo'

    (qa-example/qa-f27-repo),

  6. Check
    control-openhands browser attr 'testid=conversation-overview-git-repo' href

    (https://github.com/qa-example/qa-f27-repo),

  7. Check
    control-openhands browser text 'testid=conversation-overview-git-branch'

    (main; control-openhands browser attr 'testid=conversation-overview-git-branch' href is https://github.com/qa-example/qa-f27-repo/tree/main) and

  8. Check
    control-openhands browser snapshot 'testid=conversation-overview-panel'
  9. Note
    : Changes +0 -0, the repo and branch links, the Commits and Pull Requests items, then Workspace qa-f27-repo.
  10. Do
    control-openhands browser screenshot --feature F27.overview-identity --name repo-rows
  11. Expect
    The repo and branch rows come from a git probe the page runs while the conversation is active: without an LLM key the conversation ends in error, and after
  12. Do
    control-openhands browser reload
  13. Do
    control-openhands browser click 'testid=conversation-overview-toggle'
  14. Note
    the panel lists only Workspace qa-f27-repo (control-openhands browser count 'testid=conversation-overview-git-repo' is 0; see Gotchas), so read the rows before any reload.
  15. Note
    With a key the conversation finishes and the rows survive a reload: after browser reload, the toggle and browser wait 'testid=conversation-overview-git-repo' --timeout 20000, the count is 1, and
  16. Check
    control-openhands browser text 'testid=agent-message >> nth=-1'
  17. Note
    still shows the reply (driven 2026-10-08 with the prompt List the files in this folder with ls. Do nothing else., reply The folder contains: README.md src).

No browser tool here (F27.browser-screenshot, blocked) #

  1. Wait
    control-openhands conversation start --prompt "Use your browser tool to navigate to https://example.com and then reply with the page title only." --wait --timeout 240
  2. Note
    made the agent fall back to curl.
  3. Note
    Confirm with
  4. Do
    control-openhands browser click 'testid=chat-plus-button'
  5. Check
    control-openhands browser click 'testid=show-agent-tools-button'
  6. Do
    control-openhands browser click 'testid=system-message-modal >> role=tab[name="Available Tools"]'
  7. Check
    control-openhands browser snapshot 'testid=system-message-modal'
  8. Note
    : the tools are terminal, file_editor, task_tracker, canvas_ui_control, launch_child_conversation, finish, think, switch_llm, invoke_skill (no browser tools).
  9. Note
    Close with
  10. Do
    control-openhands browser click 'testid=close-system-message-modal'
  11. Note
    With a working browser tool, expect browser-chrome-url to hold the URL and browser-chrome-open-external count 1.

Info modals at phone width #

  1. Do
    control-openhands browser viewport phone
  2. Note
    for each of skills/skills-modal, hooks/hooks-modal, agent-tools/system-message-modal:
  3. Do
    control-openhands browser click 'testid=chat-plus-button'
  4. Check
    control-openhands browser click 'testid=show-skills-button'
  5. Wait
    control-openhands browser wait 'testid=skills-modal' --timeout 10000
  6. Check
    control-openhands browser bbox 'testid=skills-modal'
  7. Do
    control-openhands browser screenshot --feature F27.info-modals-phone --name skills
  8. Do
    control-openhands browser click 'testid=close-skills-modal'

    (substitute the names).

  9. Note
    Each box is 351 px wide at x 19.5 with insideViewport true and pageHorizontalOverflow false.

Agent tab at phone width #

  1. Note
    Still at phone width on /conversations/<id>, run
  2. Wait
    control-openhands conversation start --stay --prompt "Call the canvas_ui_control tool with command open_tab and tab terminal, then reply OK." --wait --timeout 200
  3. Check
    control-openhands browser url

    (it ends /conversations/<id>/panel),

  4. Check
    control-openhands browser count 'testid=tabs-pane-header'

    (1) and

  5. Check
    control-openhands browser attr 'role=tab[name="Terminal"]' aria-selected

    (true).

  6. Expect
    The panel page has no right-panel-toggle.
  7. Note
    Screenshot with
  8. Do
    control-openhands browser screenshot --feature F27.canvas-ui-phone --name panel-terminal

Phone panel tabs #

  1. Note
    At phone width run
  2. Do
    control-openhands browser goto /conversations/<tools-id>/panel
  3. Note
    tabs that do not fit the 390 px row move into the tabs menu (with Commits active, as the Overview menu bullet leaves it, Usage is not in the row and conversation-tab-usage does not exist), so open Usage with
  4. Do
    control-openhands browser click 'testid=tabs-pane-header >> testid=ellipsis-button'
  5. Do
    control-openhands browser click 'testid=conversation-tabs-menu-open-usage'
  6. Check
    control-openhands browser bbox 'testid=usage-panel'

    (390 wide, insideViewport true, no overflow) and

  7. Do
    control-openhands browser screenshot --feature F27.phone --name usage
  8. Do
    control-openhands browser click 'testid=conversation-tab-planner'
  9. Check
    control-openhands browser bbox 'testid=conversation-tab-planner'
  10. Check
    control-openhands browser bbox 'testid=planner-tab-build-button'
  11. Note
    : the tab row stays inside the viewport (the Planner tab at y 5.5, insideViewport true) and the Build bar sits below the 39 px top bar (Build at y 49.5); screenshot with
  12. Do
    control-openhands browser screenshot --feature F27.phone --name planner
  13. Note
    Return with
  14. Do
    control-openhands browser viewport desktop
  15. Note
    the app replaces /panel with /conversations/<tools-id>.

Terminal waiting #

  1. Note
    Arrange an errored conversation without model cost: echo sk-qa-invalid > $OH_VERIFY_RUN/private/qa-bad.key,
  2. Arrange
    control-openhands llm set --profile qa-bad --model deepseek/deepseek-flash --api-key-file $OH_VERIFY_RUN/private/qa-bad.key --no-validate

    (activates it), then

  3. Wait
    control-openhands conversation start --prompt "Reply OK." --wait --timeout 120

    (status error, the chat shows the authentication error).

  4. Do
    control-openhands browser click 'testid=right-panel-toggle'
  5. Do
    control-openhands browser click 'testid=conversation-tab-terminal'
  6. Wait
    control-openhands browser wait-text 'Waiting for runtime to start' --timeout 10000
  7. Note
    screenshot with
  8. Do
    control-openhands browser screenshot --feature F27.terminal-waiting --name error
  9. Note
    Restore with
  10. Arrange
    control-openhands llm preset deepseek --api-key-file <key file>

    (re-activates deepseek-flash) and

  11. Arrange
    control-openhands api DELETE /api/profiles/qa-bad --write
  12. Note
    For contrast, a conversation paused with F07's Stop Runtime (conversation status paused, composer Stopped) shows "No terminal output yet": PAUSED is not in RUNTIME_INACTIVE_STATES.

State not reached #

  1. Expect
    A new conversation's metrics arrive before the drawer can be opened (the Usage tab shows Loading..., then numbers); even the errored conversation above shows all-zero totals, not the empty state; and
  2. Do
    control-openhands service stop agent-server
  3. Note
    replaces the page with "Manage backends". "No metrics data available" was not observed.

Gotchas and known limits

  • After a reload the Task List (and tab bar entry) appears only once history has loaded; wait about 2 s before counting [data-active="true"] rows.
  • [data-active] is also used by sidebar conversation cards: always combine it with has-text=<task title>.
  • browser click --observe takes a plain CSS selector ('[data-testid=compact-context-button]'); the testid= form fails with a querySelectorAll SyntaxError.
  • "Create a plan" leaves that conversation in plan mode: the next composer message goes to the planner helper, which has no launch_child_conversation or canvas_ui_control. Use a separate conversation for client-tool prompts.
  • The child-launch toast's Open link animates; browser click on it times out with "element is not stable". Read the child id from the parent's [child-conversation] message instead.
  • GET …/file/download?path=…/.agents_tmp/PLAN.md 404 and GET /api/llm/balance 404 are expected app-origin errors (no plan yet; no balance endpoint). Count them in browser errors --app-only but do not report them.
  • Escape closes the composer context popover and the Overview "..." menu and returns focus to the trigger (#18044); an outside click, or a second click on the trigger, closes them too.
  • The Overview column is 28 px narrower than its content (scrollWidth 296, clientWidth 268). After some clicks inside it, Playwright's scroll-into-view shifts it and the card looks cut off on the left; browser reload restores it before taking screenshots.
  • The agent may summarize a client-tool action with its own title (for example "Launch local child conversation to reply PONG") instead of "Launching a child conversation"; assert the toast and events, not the chat title.
  • The Overview peek appears only while the drawer is open (canPeekOnHover needs isRightPanelShown); a browser goto or reload starts with the drawer closed.
  • The Overview's repo and branch rows depend on a bash probe (useLocalGitInfo) that runs only while the conversation's status is idle, running, waiting for confirmation or finished and the agent is not in error. Right after conversation start the probe has run and its result stays cached on the page, but on an errored conversation a reload hides the rows again, and conversation pause does not apply to an errored conversation. Read the rows before any goto or reload.
  • At phone width (and in a narrow drawer) the tab row shows only the tabs that fit; the rest are reachable only through the tabs menu (conversation-tabs-menu-open-<tab>), and which tabs fit depends on the active tab's label width.
  • Plain agent-canvas instances on one machine share the tmux socket /tmp/tmux-<uid>/openhands (OpenHands/OpenHands#17946), so another instance stopping can reset this one's terminal. control-openhands launch gives each run its own TMUX_TMPDIR, so in verification runs a reset means this run's agent server restarted.
  • Agent Tools & Metadata remembers the last tab (it reopens on Available Tools).
  • Known open issues: terminal history after reload (#17566) still fails (reproduced 2026-10-06); info modals at phone width (#17562) pass.
  • Agent tab requests (open_tab, navigate_to_file, chat file links) are saved per conversation like a user tab click (#18045): after the agent opens Terminal or a file and the page reloads, the drawer starts closed and the panel toggle reopens it on that tab; clicking that tab then closes the drawer. At phone width they navigate to /conversations/<id>/panel.

Source paths: src/components/features/terminal/, src/hooks/use-terminal.ts, src/components/features/browser/, src/routes/browser-tab.tsx, src/routes/planner-tab.tsx, src/components/features/conversation/conversation-tabs/conversation-planner-build-bar.tsx, src/hooks/use-handle-plan-click.ts, src/hooks/use-handle-build-plan-click.ts, src/routes/task-list-tab.tsx, src/routes/usage-tab.tsx, src/components/features/conversation/usage-panel/, src/components/features/chat/components/context-window-meter.tsx, src/components/features/conversation/conversation-overview-*.tsx, src/services/canvas-ui.ts, src/api/canvas-ui-client-tool.ts, src/services/child-conversation-launch.ts, src/components/features/conversation-panel/ (info modals).