Jelajahi Sumber

fix(app): show running shell command (#38080)

Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
opencode-agent[bot] 3 minggu lalu
induk
melakukan
c9db6e9a1f

+ 17 - 0
packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts

@@ -32,6 +32,23 @@ for (const expanded of [false, true]) {
   })
 }
 
+test("shows and expands a running shell command without shimmering it", async ({ page }) => {
+  const id = "prt_shell_running_command"
+  const command = "sleep 10 && echo done"
+  await setupTimeline(page, {
+    messages: [userMessage(), assistantMessage([shell(id, "running", "still running", command)], { completed: false })],
+    settings: { shellToolPartsExpanded: false },
+  })
+
+  const tool = page.locator(`[data-timeline-part-id="${id}"]`)
+  await expect(tool.locator('[data-component="text-shimmer"]')).toHaveAttribute("data-active", "true")
+  await expect(tool.locator('[data-component="shell-submessage"]')).toHaveText(command)
+  await expect(tool.locator('[data-component="shell-submessage"] [data-component="text-shimmer"]')).toHaveCount(0)
+  await tool.locator('[data-slot="collapsible-trigger"]').click()
+  await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true")
+  await expect(tool.locator('[data-slot="bash-pre"]')).toContainText("still running")
+})
+
 test("transitions thinking and hidden reasoning through busy to idle", async ({ page }) => {
   const reasoningID = "prt_reasoning_hidden"
   const assistant = assistantMessage([reasoningPart(reasoningID, "## Inspecting stability")], { completed: false })

+ 3 - 2
packages/session-ui/src/components/basic-tool.tsx

@@ -32,6 +32,7 @@ export interface BasicToolProps {
   open?: boolean
   onOpenChange?: (open: boolean) => void
   forceOpen?: boolean
+  allowOpenWhilePending?: boolean
   defer?: boolean
   locked?: boolean
   animated?: boolean
@@ -176,7 +177,7 @@ export function BasicTool(props: BasicToolProps) {
   })
 
   const handleOpenChange = (value: boolean) => {
-    if (pending()) return
+    if (pending() && !props.allowOpenWhilePending) return
     if (props.locked && !value) return
     setOpen(value)
   }
@@ -247,7 +248,7 @@ export function BasicTool(props: BasicToolProps) {
           </Switch>
         </div>
       </div>
-      <Show when={hasChildren() && !props.hideDetails && !props.locked && !pending()}>
+      <Show when={hasChildren() && !props.hideDetails && !props.locked && (!pending() || props.allowOpenWhilePending)}>
         <Collapsible.Arrow />
       </Show>
     </div>

+ 2 - 1
packages/session-ui/src/components/message-part.tsx

@@ -2126,13 +2126,14 @@ ToolRegistry.register({
       <BasicTool
         {...props}
         icon="console"
+        allowOpenWhilePending
         trigger={(open) => (
           <div data-slot="basic-tool-tool-info-structured">
             <div data-slot="basic-tool-tool-info-main">
               <span data-slot="basic-tool-tool-title">
                 <TextShimmer text={i18n.t("ui.tool.shell")} active={pending()} />
               </span>
-              <Show when={!pending() && !open() && props.input.command}>
+              <Show when={!open() && props.input.command}>
                 <ShellSubmessage text={props.input.command} animate={sawPending} />
               </Show>
             </div>