Jelajahi Sumber

fix(tui): show shell working directory in prompt

Dax Raad 2 minggu lalu
induk
melakukan
2ddc91a0e8

+ 4 - 4
packages/tui/src/mini/tool.ts

@@ -644,17 +644,17 @@ function snapQuestion(p: ToolProps): ToolSnapshot {
 function scrollBashStart(p: ToolProps): string {
   const cmd = p.input.command ?? ""
   const wd = p.input.workdir ?? ""
-  const formatted = wd && wd !== "." ? displayPath(p, wd) : ""
+  const formatted = wd && wd !== "." ? displayPath(p, wd, { home: true }) : ""
   const dir = formatted === "." ? "" : formatted
   if (cmd && !dir) {
     return `$ ${cmd}`
   }
 
   if (!cmd) {
-    return dir ? `# Running in ${dir}` : ""
+    return dir ? `${dir}$` : ""
   }
 
-  return `# Running in ${dir}\n$ ${cmd}`
+  return `${dir}$ ${cmd}`
 }
 
 function scrollBashProgress(p: ToolProps): string {
@@ -670,7 +670,7 @@ function scrollBashProgress(p: ToolProps): string {
   }
 
   const wdRaw = (p.input.workdir ?? "").trim()
-  const wd = wdRaw ? displayPath(p, wdRaw) : ""
+  const wd = wdRaw ? displayPath(p, wdRaw, { home: true }) : ""
   const lines = out.split("\n")
   const first = (lines[0] || "").trim()
   const second = (lines[1] || "").trim()

+ 7 - 1
packages/tui/src/routes/session/index.tsx

@@ -2562,6 +2562,7 @@ function Shell(props: ToolProps) {
   const ctx = use()
   const client = useClient()
   const data = useData()
+  const pathFormatter = usePathFormatter()
   const permission = createMemo(() => {
     const request = data.session.permission.list(ctx.sessionID)?.[0]
     return request?.source?.type === "tool" && request.source.callID === props.part.id
@@ -2575,6 +2576,7 @@ function Shell(props: ToolProps) {
   })
   const isRunning = createMemo(() => props.part.state.status === "running" || backgroundRunning())
   const command = createMemo(() => stringValue(props.input.command))
+  const workdir = createMemo(() => pathFormatter.format(stringValue(props.input.workdir)))
   const [expanded, setExpanded] = createSignal(false)
   const [backgroundOutput, setBackgroundOutput] = createSignal("")
   const [outputTruncated, setOutputTruncated] = createSignal(false)
@@ -2648,7 +2650,11 @@ function Shell(props: ToolProps) {
   })
   const maxLines = 10
   const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6))
-  const input = createMemo(() => (command() ? `${isRunning() ? "" : "$ "}${command()}` : ""))
+  const input = createMemo(() => {
+    if (!command()) return ""
+    const prompt = workdir() && workdir() !== "." ? `${workdir()}$ ` : isRunning() ? "" : "$ "
+    return `${prompt}${command()}`
+  })
   const content = createMemo(() => [input(), output()].filter(Boolean).join("\n\n"))
   const collapsed = createMemo(() => collapseToolOutput(content(), maxLines, maxChars()))
   const limited = createMemo(() => {

+ 50 - 0
packages/tui/test/mini/entry.body.test.ts

@@ -1,4 +1,6 @@
 import { describe, expect, test } from "bun:test"
+import os from "os"
+import path from "path"
 import type { SessionMessageAssistantTool } from "@opencode-ai/client/promise"
 import { entryBody, entryCanStream, entryDone } from "../../src/mini/entry.body"
 import type { StreamCommit, ToolSnapshot } from "../../src/mini/types"
@@ -11,6 +13,7 @@ function commit(input: Partial<StreamCommit> & Pick<StreamCommit, "kind" | "text
 function toolCommit(input: {
   tool: string
   state: SessionMessageAssistantTool["state"]
+  directory?: string
   phase?: StreamCommit["phase"]
   toolState?: StreamCommit["toolState"]
   text?: string
@@ -23,6 +26,7 @@ function toolCommit(input: {
     phase: input.phase ?? "final",
     source: "tool",
     tool: input.tool,
+    directory: input.directory,
     toolState:
       input.toolState ??
       (input.state.status === "error" ? "error" : input.state.status === "completed" ? "completed" : "running"),
@@ -372,6 +376,52 @@ describe("run entry body", () => {
     })
   })
 
+  test("renders a shell workdir before the prompt", () => {
+    expect(
+      entryBody(
+        toolCommit({
+          tool: "shell",
+          directory: "/work/project",
+          phase: "start",
+          toolState: "running",
+          state: {
+            status: "running",
+            input: {
+              command: "ls",
+              workdir: "packages/foo",
+            },
+            metadata: {},
+          },
+        }),
+      ),
+    ).toEqual({
+      type: "text",
+      content: "packages/foo$ ls",
+    })
+
+    expect(
+      entryBody(
+        toolCommit({
+          tool: "shell",
+          directory: path.join(os.homedir(), "project"),
+          phase: "start",
+          toolState: "running",
+          state: {
+            status: "running",
+            input: {
+              command: "pwd",
+              workdir: path.join(os.homedir(), "outside", "folder"),
+            },
+            metadata: {},
+          },
+        }),
+      ),
+    ).toEqual({
+      type: "text",
+      content: "~/outside/folder$ pwd",
+    })
+  })
+
   test("renders direct shell commits without a synthetic shell header", () => {
     expect(
       entryBody(