Skip to content

feat(session-ui): add run button for shell code blocks - #59810

Closed
mtabaresdev wants to merge 1 commit into
anomalyco:devfrom
mtabaresdev:markdown-run-button
Closed

mtabaresdev wants to merge 1 commit into
anomalyco:devfrom
mtabaresdev:markdown-run-button

Conversation

@mtabaresdev

Copy link
Copy Markdown

Summary

Adds a play button to shell code blocks in markdown that executes commands via the session.shell API, similar to Claude's interface.

  • Run and copy buttons are always visible (no hover required)
  • Shell output auto-expands at both levels when executed from the run button
  • Added language detection for runnable shells (bash/sh/shell/zsh/fish)

Changes

  • Add RunMarkdownCommand capability to markdown context
  • Create run button with play icon and spinner states
  • Wire runCommand through app to session.shell API
  • Auto-expand tool groups containing shell messages
  • Auto-expand shell content by default
  • Add unit tests for language detection

Test plan

  • Run bun test packages/session-ui - tests pass
  • Run bun typecheck in affected packages - all pass
  • Manual testing in bun run dev:web - play button works, output expands

🤖 Generated with Claude Code

Add play button to shell code blocks in markdown that executes commands
via session.shell API. Both run and copy buttons are now always visible
(no hover required). Shell output auto-expands at both group and content
levels when executed from the run button.

Changes:
- Add RunMarkdownCommand capability type to markdown context
- Create run button with play icon and spinner states
- Add isRunnableCode() helper to check shell/bash/sh/zsh/fish languages
- Wire runCommand prop through MarkdownProvider to app
- Auto-expand tool groups containing shell messages
- Auto-expand shell message content by default
- Add unit tests for runnable language detection
- Add i18n keys for run/running states

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Oct 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 72 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The run button works: I tested it in a Playwright component spec using the markdown fixture's new runCommand option, and unit tests and tsc -b pass. A few things should change before this merges.

Public API is unchanged. RunMarkdownCommand and the runCommand prop on MarkdownProvider are internal to session-ui, and the PR only calls the existing session.shell endpoint.

Concerns, most important first (details inline):

  • Partial commands can run. The button is added to code blocks that are still streaming. I clicked it on an unfinished ```bash block and it ran the half-written command.
  • One click runs model-written text, with no confirmation. It goes through the same session.shell path as typing a command in the composer, so no permission check applies. Please add an explicit confirmation, or get a maintainer to agree that one click is acceptable.
  • Shell output now always opens, for every shell call. The session-timeline-row.tsx changes apply to all shell tool calls and shell messages, not only ones started from the run button, and they override the user's "collapsed" timeline setting. This is outside the feature; please drop it, or limit it to commands started from the button.
  • The copy button is now always visible. This is a separate visual change, and it touches the same CSS as #59159.

Smaller points:

  • The controller.tsx edit changes nothing: details can only be "collapsed" or "expanded", so !== "collapsed" means the same as === "expanded". It can be dropped.
  • The new play icon has exactly the same path as the existing outline-video icon.

Before

After


if (wrapper instanceof HTMLElement) applyCodeMetadata(wrapper, block.language)

if (wrapper instanceof HTMLElement) syncRunButton(wrapper, canRun && isRunnableCode(block.language), runLabels)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds the run button whether or not the block is complete, so it shows up while a fenced block is still streaming. I checked this in a component spec: with streaming on and the text ```bash\nrm -rf /tmp/build (fence not closed), clicking the button sent the partial command to runCommand. The model could still be adding arguments or more lines. Please only enable it once block.complete is true, here and in the new-wrapper path below.

if (!sessionID) return

try {
await serverSDK.api.session.shell({ sessionID, command })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

session.shell is the user shell path, the same one the composer's shell mode uses, so no permission check applies. With this button, a single click runs whatever the model wrote, in the user's working directory. In shell mode the user at least types or submits the command. Please add a confirmation step, for example showing the command and asking to confirm, or get explicit maintainer agreement that one click is acceptable. Also, nothing here deals with the agent being busy or the session being archived.


const key = () => `context:${row().group.key}`

// Auto-expand groups containing shell messages (unless explicitly closed by the user)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and the tool.name === "shell" changes in toolDefaultOpen and contentDefaultOpen below) opens every context group that contains a shell part, and every agent shell tool call, by default. It also returns before the timeline-detail shell.details setting is checked, so users who chose "collapsed" will see these expanded anyway. The description says output expands when run from the button, but this applies to every shell call. Please drop this, or limit it to commands started from the run button.

(input.timelineDetail().shell.placement === "hidden" &&
(value?.status === "timeout" || (value?.status === "exited" && value.exit !== undefined && value.exit !== 0)))
)
// Always expand shell messages by default (user can still collapse them manually)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now always returns true when timelineDetail is set, so the user's shell.details: "collapsed" setting no longer has any effect on shell messages. That's a regression for anyone who collapsed them on purpose.

@@ -348,17 +348,28 @@
position: absolute;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the hover-to-reveal opacity rules makes the copy button visible on every code block. That's a design change separate from the run button, and it overlaps with #59159, which also changes how the copy button is shown. Please keep it out of this PR, or show the run button the same way the copy button is shown today.

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 72-hour window.

Feel free to open a new pull request that follows our guidelines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants