瀏覽代碼

whitespace + imports

Simon Klee 1 天之前
父節點
當前提交
9f037aa40e
共有 27 個文件被更改,包括 324 次插入74 次删除
  1. 6 6
      packages/opencode/src/cli/cmd/run.ts
  2. 1 1
      packages/opencode/src/cli/cmd/run/footer.prompt.tsx
  3. 1 1
      packages/opencode/src/cli/cmd/run/prompt.shared.ts
  4. 1 1
      packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts
  5. 1 1
      packages/opencode/src/cli/cmd/run/runtime.queue.ts
  6. 17 6
      packages/opencode/src/cli/cmd/run/scrollback.surface.ts
  7. 9 1
      packages/opencode/src/cli/cmd/run/session-data.ts
  8. 2 2
      packages/opencode/src/cli/cmd/run/splash.ts
  9. 1 1
      packages/opencode/src/cli/cmd/run/subagent-data.ts
  10. 20 20
      packages/opencode/src/cli/cmd/run/tool.ts
  11. 1 1
      packages/opencode/src/cli/cmd/run/trace.ts
  12. 2 2
      packages/opencode/test/cli/run/entry.body.test.ts
  13. 76 2
      packages/opencode/test/cli/run/footer.test.ts
  14. 4 4
      packages/opencode/test/cli/run/footer.view.test.tsx
  15. 1 1
      packages/opencode/test/cli/run/permission.shared.test.ts
  16. 2 2
      packages/opencode/test/cli/run/prompt.shared.test.ts
  17. 1 1
      packages/opencode/test/cli/run/question.shared.test.ts
  18. 3 3
      packages/opencode/test/cli/run/runtime.boot.test.ts
  19. 2 2
      packages/opencode/test/cli/run/runtime.queue.test.ts
  20. 106 4
      packages/opencode/test/cli/run/scrollback.surface.test.ts
  21. 56 1
      packages/opencode/test/cli/run/session-data.test.ts
  22. 1 1
      packages/opencode/test/cli/run/session.shared.test.ts
  23. 2 2
      packages/opencode/test/cli/run/stream.test.ts
  24. 2 2
      packages/opencode/test/cli/run/stream.transport.test.ts
  25. 2 2
      packages/opencode/test/cli/run/subagent-data.test.ts
  26. 1 1
      packages/opencode/test/cli/run/theme.test.ts
  27. 3 3
      packages/opencode/test/cli/run/variant.shared.test.ts

+ 6 - 6
packages/opencode/src/cli/cmd/run.ts

@@ -16,13 +16,13 @@ import path from "path"
 import { pathToFileURL } from "url"
 import { UI } from "../ui"
 import { cmd } from "./cmd"
-import { Flag } from "../../flag/flag"
+import { Flag } from "@/flag/flag"
 import { bootstrap } from "../bootstrap"
 import { EOL } from "os"
-import { Filesystem } from "../../util"
+import { Filesystem } from "@/util"
 import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
-import { Agent } from "../../agent/agent"
-import { Permission } from "../../permission"
+import { Agent } from "@/agent/agent"
+import { Permission } from "@/permission"
 import { AppRuntime } from "@/effect/app-runtime"
 import type { RunDemo } from "./run/types"
 
@@ -715,7 +715,7 @@ export const RunCommand = cmd({
       const model = pick(args.model)
       const { runInteractiveLocalMode } = await runtimeTask
       const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
-        const { Server } = await import("../../server/server")
+        const { Server } = await import("@/server/server")
         const request = new Request(input, init)
         return Server.Default().app.fetch(request)
       }) as typeof globalThis.fetch
@@ -744,7 +744,7 @@ export const RunCommand = cmd({
 
     await bootstrap(directory ?? root, async () => {
       const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
-        const { Server } = await import("../../server/server")
+        const { Server } = await import("@/server/server")
         const request = new Request(input, init)
         return Server.Default().app.fetch(request)
       }) as typeof globalThis.fetch

+ 1 - 1
packages/opencode/src/cli/cmd/run/footer.prompt.tsx

@@ -21,7 +21,7 @@ import {
   onMount,
   type Accessor,
 } from "solid-js"
-import * as Locale from "../../../util/locale"
+import * as Locale from "@/util/locale"
 import {
   createPromptHistory,
   isExitCommand,

+ 1 - 1
packages/opencode/src/cli/cmd/run/prompt.shared.ts

@@ -12,7 +12,7 @@
 // The leader-key cycle (promptCycle) uses a two-step pattern: first press
 // arms the leader, second press within the timeout fires the action.
 import type { KeyBinding } from "@opentui/core"
-import * as Keybind from "../../../util/keybind"
+import * as Keybind from "@/util/keybind"
 import type { FooterKeybinds, RunPrompt } from "./types"
 
 const HISTORY_LIMIT = 200

+ 1 - 1
packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts

@@ -9,7 +9,7 @@
 // Also wires SIGINT so Ctrl-c during a turn triggers the two-press exit
 // sequence through RunFooter.requestExit().
 import { createCliRenderer, type CliRenderer, type ScrollbackWriter } from "@opentui/core"
-import * as Locale from "../../../util/locale"
+import * as Locale from "@/util/locale"
 import { withRunSpan } from "./otel"
 import { entrySplash, exitSplash, splashMeta } from "./splash"
 import { resolveRunTheme } from "./theme"

+ 1 - 1
packages/opencode/src/cli/cmd/run/runtime.queue.ts

@@ -9,7 +9,7 @@
 // and tracks per-turn wall-clock duration for the footer status line.
 //
 // Resolves when the footer closes and all in-flight work finishes.
-import * as Locale from "../../../util/locale"
+import * as Locale from "@/util/locale"
 import { isExitCommand } from "./prompt.shared"
 import type { FooterApi, FooterEvent, RunPrompt } from "./types"
 

+ 17 - 6
packages/opencode/src/cli/cmd/run/scrollback.surface.ts

@@ -51,10 +51,9 @@ function commitMarkdownBlocks(input: {
     return false
   }
 
-  const prev = input.renderable._blockStates[input.startBlock - 1]
   const next = input.renderable._blockStates[input.endBlockExclusive]
-  const start = Math.max(0, first.renderable.y - (prev?.marginBottom ?? 0))
-  const end = last.renderable.y + last.renderable.height + (next ? 0 : (last.marginBottom ?? 0))
+  const start = first.renderable.y
+  const end = next ? next.renderable.y : last.renderable.y + last.renderable.height + (last.marginBottom ?? 0)
 
   input.surface.commitRows(start, end, {
     trailingNewline: input.trailingNewline,
@@ -62,6 +61,18 @@ function commitMarkdownBlocks(input: {
   return true
 }
 
+function wantsSpacer(prev: StreamCommit | undefined, next: StreamCommit): boolean {
+  if (!prev) {
+    return false
+  }
+
+  if (sameEntryGroup(prev, next)) {
+    return false
+  }
+
+  return !(prev.kind === "tool" && prev.phase === "start")
+}
+
 export class RunScrollbackStream {
   private tail: StreamCommit | undefined
   private active: ActiveEntry | undefined
@@ -238,14 +249,14 @@ export class RunScrollbackStream {
     const body = entryBody(commit)
     if (body.type === "none") {
       if (entryDone(commit)) {
-        await this.finishActive(entryFlags(commit).trailingNewline)
+        await this.finishActive(false)
       }
 
       this.tail = commit
       return
     }
 
-    if (this.wrote && !same) {
+    if (this.wrote && wantsSpacer(this.tail, commit)) {
       this.renderer.writeToScrollback(spacerWriter())
     }
 
@@ -256,7 +267,7 @@ export class RunScrollbackStream {
     ) {
       await this.writeStreaming(commit, body)
       if (entryDone(commit)) {
-        await this.finishActive(entryFlags(commit).trailingNewline)
+        await this.finishActive(false)
       }
       this.wrote = true
       this.tail = commit

+ 9 - 1
packages/opencode/src/cli/cmd/run/session-data.ts

@@ -25,7 +25,7 @@
 //   event arrives, the queue entry is removed and the footer falls back
 //   to the next pending request or to the prompt view.
 import type { Event, Part, PermissionRequest, QuestionRequest, ToolPart } from "@opencode-ai/sdk/v2"
-import * as Locale from "../../../util/locale"
+import * as Locale from "@/util/locale"
 import { toolView } from "./tool"
 import type { FooterOutput, FooterPatch, FooterView, StreamCommit } from "./types"
 
@@ -492,11 +492,19 @@ function flushPart(data: SessionData, commits: SessionCommit[], partID: string,
 
   if (sent === 0) {
     chunk = chunk.replace(/^\n+/, "")
+    // Some models emit a standalone whitespace token before real content.
+    // Keep buffering until we have visible text so scrollback doesn't get a blank row.
+    if (!chunk.trim()) {
+      return
+    }
     if (kind === "reasoning" && chunk) {
       chunk = `Thinking: ${chunk.replace(/\[REDACTED\]/g, "")}`
     }
     if (kind === "assistant" && chunk) {
       chunk = stripEcho(data, msg, chunk)
+      if (!chunk.trim()) {
+        return
+      }
     }
   }
 

+ 2 - 2
packages/opencode/src/cli/cmd/run/splash.ts

@@ -19,8 +19,8 @@ import {
   type ScrollbackSnapshot,
   type ScrollbackWriter,
 } from "@opentui/core"
-import * as Locale from "../../../util/locale"
-import { logo } from "../../logo"
+import * as Locale from "@/util/locale"
+import { logo } from "@/cli/logo"
 import type { RunEntryTheme } from "./theme"
 
 export const SPLASH_TITLE_LIMIT = 50

+ 1 - 1
packages/opencode/src/cli/cmd/run/subagent-data.ts

@@ -1,5 +1,5 @@
 import type { Event, Part, PermissionRequest, QuestionRequest, ToolPart } from "@opencode-ai/sdk/v2"
-import * as Locale from "../../../util/locale"
+import * as Locale from "@/util/locale"
 import {
   bootstrapSessionData,
   createSessionData,

+ 20 - 20
packages/opencode/src/cli/cmd/run/tool.ts

@@ -18,26 +18,26 @@ import os from "os"
 import path from "path"
 import stripAnsi from "strip-ansi"
 import type { ToolPart } from "@opencode-ai/sdk/v2"
-import type * as Tool from "../../../tool/tool"
-import type { ApplyPatchTool } from "../../../tool/apply_patch"
-import type { BashTool } from "../../../tool/bash"
-import type { CodeSearchTool } from "../../../tool/codesearch"
-import type { EditTool } from "../../../tool/edit"
-import type { GlobTool } from "../../../tool/glob"
-import type { GrepTool } from "../../../tool/grep"
-import type { InvalidTool } from "../../../tool/invalid"
-import type { LspTool } from "../../../tool/lsp"
-import type { PlanExitTool } from "../../../tool/plan"
-import type { QuestionTool } from "../../../tool/question"
-import type { ReadTool } from "../../../tool/read"
-import type { SkillTool } from "../../../tool/skill"
-import type { TaskTool } from "../../../tool/task"
-import type { TodoWriteTool } from "../../../tool/todo"
-import type { WebFetchTool } from "../../../tool/webfetch"
-import type { WebSearchTool } from "../../../tool/websearch"
-import type { WriteTool } from "../../../tool/write"
-import { LANGUAGE_EXTENSIONS } from "../../../lsp/language"
-import * as Locale from "../../../util/locale"
+import type * as Tool from "@/tool/tool"
+import type { ApplyPatchTool } from "@/tool/apply_patch"
+import type { BashTool } from "@/tool/bash"
+import type { CodeSearchTool } from "@/tool/codesearch"
+import type { EditTool } from "@/tool/edit"
+import type { GlobTool } from "@/tool/glob"
+import type { GrepTool } from "@/tool/grep"
+import type { InvalidTool } from "@/tool/invalid"
+import type { LspTool } from "@/tool/lsp"
+import type { PlanExitTool } from "@/tool/plan"
+import type { QuestionTool } from "@/tool/question"
+import type { ReadTool } from "@/tool/read"
+import type { SkillTool } from "@/tool/skill"
+import type { TaskTool } from "@/tool/task"
+import type { TodoWriteTool } from "@/tool/todo"
+import type { WebFetchTool } from "@/tool/webfetch"
+import type { WebSearchTool } from "@/tool/websearch"
+import type { WriteTool } from "@/tool/write"
+import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
+import * as Locale from "@/util/locale"
 import type { RunDiffStyle, RunEntryBody, StreamCommit, ToolSnapshot } from "./types"
 
 export type ToolView = {

+ 1 - 1
packages/opencode/src/cli/cmd/run/trace.ts

@@ -13,7 +13,7 @@
 // active based on the env var, and subsequent calls return the cached result.
 import fs from "fs"
 import path from "path"
-import { Global } from "../../../global"
+import { Global } from "@/global"
 
 export type Trace = {
   write(type: string, data?: unknown): void

+ 2 - 2
packages/opencode/test/cli/run/entry.body.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from "bun:test"
-import { entryBody, entryCanStream, entryDone } from "../../../src/cli/cmd/run/entry.body"
-import type { StreamCommit } from "../../../src/cli/cmd/run/types"
+import { entryBody, entryCanStream, entryDone } from "@/cli/cmd/run/entry.body"
+import type { StreamCommit } from "@/cli/cmd/run/types"
 
 function commit(input: Partial<StreamCommit> & Pick<StreamCommit, "kind" | "text" | "phase" | "source">): StreamCommit {
   return input

+ 76 - 2
packages/opencode/test/cli/run/footer.test.ts

@@ -1,7 +1,7 @@
 import { afterEach, expect, test } from "bun:test"
 import { MockTreeSitterClient, createTestRenderer, type TestRenderer } from "@opentui/core/testing"
-import { RunFooter } from "../../../src/cli/cmd/run/footer"
-import { RUN_THEME_FALLBACK } from "../../../src/cli/cmd/run/theme"
+import { RunFooter } from "@/cli/cmd/run/footer"
+import { RUN_THEME_FALLBACK } from "@/cli/cmd/run/theme"
 
 const decoder = new TextDecoder()
 const active: Array<{ footer?: RunFooter; renderer: TestRenderer }> = []
@@ -149,3 +149,77 @@ test("run footer keeps active streamed assistant content across width resize", a
     lib.commitSplitFooterSnapshot = originalCommitSplitFooterSnapshot
   }
 })
+
+test("run footer keeps tool start rows tight with following reasoning", async () => {
+  const out = await createTestRenderer({
+    width: 80,
+    height: 24,
+    screenMode: "split-footer",
+    footerHeight: 6,
+    externalOutputMode: "capture-stdout",
+    consoleMode: "disabled",
+  })
+  const footer = createFooter(out.renderer)
+  active.push({ footer, renderer: out.renderer })
+  const lib = Reflect.get(out.renderer, "lib") as {
+    commitSplitFooterSnapshot: (...args: unknown[]) => unknown
+  }
+  const originalCommitSplitFooterSnapshot = lib.commitSplitFooterSnapshot.bind(lib)
+  const payloads: string[] = []
+
+  lib.commitSplitFooterSnapshot = (...args) => {
+    const snapshot = args[1] as {
+      getRealCharBytes(addLineBreaks?: boolean): Uint8Array
+    }
+    payloads.push(decoder.decode(snapshot.getRealCharBytes(true)))
+    return originalCommitSplitFooterSnapshot(...args)
+  }
+
+  try {
+    footer.append({
+      kind: "tool",
+      source: "tool",
+      messageID: "msg-tool",
+      partID: "part-tool",
+      tool: "glob",
+      phase: "start",
+      text: "running glob",
+      toolState: "running",
+      part: {
+        id: "part-tool",
+        type: "tool",
+        tool: "glob",
+        callID: "call-tool",
+        messageID: "msg-tool",
+        sessionID: "session-1",
+        state: {
+          status: "running",
+          input: {
+            pattern: "**/run.ts",
+          },
+          time: {
+            start: Date.now(),
+          },
+        },
+      },
+    })
+    footer.append({
+      kind: "reasoning",
+      source: "reasoning",
+      messageID: "msg-reasoning",
+      partID: "part-reasoning",
+      phase: "progress",
+      text: "Thinking:  Found it.",
+    })
+
+    await footer.idle()
+
+    const rows = payloads
+      .map((item) => item.replace(/ +/g, " ").trim())
+      .filter(Boolean)
+
+    expect(rows).toEqual(['✱ Glob "**/run.ts"', "_Thinking:_ Found it."])
+  } finally {
+    lib.commitSplitFooterSnapshot = originalCommitSplitFooterSnapshot
+  }
+})

+ 4 - 4
packages/opencode/test/cli/run/footer.view.test.tsx

@@ -2,10 +2,10 @@
 import { expect, test } from "bun:test"
 import { testRender } from "@opentui/solid"
 import { createSignal } from "solid-js"
-import { RunEntryContent } from "../../../src/cli/cmd/run/scrollback.writer"
-import { RunFooterView } from "../../../src/cli/cmd/run/footer.view"
-import { RUN_THEME_FALLBACK } from "../../../src/cli/cmd/run/theme"
-import type { StreamCommit } from "../../../src/cli/cmd/run/types"
+import { RunEntryContent } from "@/cli/cmd/run/scrollback.writer"
+import { RunFooterView } from "@/cli/cmd/run/footer.view"
+import { RUN_THEME_FALLBACK } from "@/cli/cmd/run/theme"
+import type { StreamCommit } from "@/cli/cmd/run/types"
 
 test("run footer view loads", () => {
   expect(typeof RunFooterView).toBe("function")

+ 1 - 1
packages/opencode/test/cli/run/permission.shared.test.ts

@@ -8,7 +8,7 @@ import {
   permissionInfo,
   permissionReject,
   permissionRun,
-} from "../../../src/cli/cmd/run/permission.shared"
+} from "@/cli/cmd/run/permission.shared"
 
 function req(input: Partial<PermissionRequest> = {}): PermissionRequest {
   return {

+ 2 - 2
packages/opencode/test/cli/run/prompt.shared.test.ts

@@ -8,8 +8,8 @@ import {
   promptInfo,
   promptKeys,
   pushPromptHistory,
-} from "../../../src/cli/cmd/run/prompt.shared"
-import type { FooterKeybinds, RunPrompt } from "../../../src/cli/cmd/run/types"
+} from "@/cli/cmd/run/prompt.shared"
+import type { FooterKeybinds, RunPrompt } from "@/cli/cmd/run/types"
 
 const keybinds: FooterKeybinds = {
   leader: "ctrl+x",

+ 1 - 1
packages/opencode/test/cli/run/question.shared.test.ts

@@ -10,7 +10,7 @@ import {
   questionStoreCustom,
   questionSubmit,
   questionSync,
-} from "../../../src/cli/cmd/run/question.shared"
+} from "@/cli/cmd/run/question.shared"
 
 function req(input: Partial<QuestionRequest> = {}): QuestionRequest {
   return {

+ 3 - 3
packages/opencode/test/cli/run/runtime.boot.test.ts

@@ -1,12 +1,12 @@
 import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
-import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
+import { TuiConfig } from "@/cli/cmd/tui/config/tui"
 import {
   resolveDiffStyle,
   resolveFooterKeybinds,
   resolveModelInfo,
   resolveSessionInfo,
-} from "../../../src/cli/cmd/run/runtime.boot"
-import type { RunInput } from "../../../src/cli/cmd/run/types"
+} from "@/cli/cmd/run/runtime.boot"
+import type { RunInput } from "@/cli/cmd/run/types"
 
 describe("run runtime boot", () => {
   afterEach(() => {

+ 2 - 2
packages/opencode/test/cli/run/runtime.queue.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from "bun:test"
-import { runPromptQueue } from "../../../src/cli/cmd/run/runtime.queue"
-import type { FooterApi, FooterEvent, RunPrompt, StreamCommit } from "../../../src/cli/cmd/run/types"
+import { runPromptQueue } from "@/cli/cmd/run/runtime.queue"
+import type { FooterApi, FooterEvent, RunPrompt, StreamCommit } from "@/cli/cmd/run/types"
 
 function footer() {
   const prompts = new Set<(input: RunPrompt) => void>()

+ 106 - 4
packages/opencode/test/cli/run/scrollback.surface.test.ts

@@ -1,7 +1,7 @@
 import { afterEach, expect, test } from "bun:test"
 import { MockTreeSitterClient, createTestRenderer, type TestRenderer } from "@opentui/core/testing"
-import { RunScrollbackStream } from "../../../src/cli/cmd/run/scrollback.surface"
-import { RUN_THEME_FALLBACK } from "../../../src/cli/cmd/run/theme"
+import { RunScrollbackStream } from "@/cli/cmd/run/scrollback.surface"
+import { RUN_THEME_FALLBACK } from "@/cli/cmd/run/theme"
 
 type ClaimedCommit = {
   snapshot: {
@@ -142,7 +142,7 @@ test("completes markdown replies without adding a second blank line above the fo
   const progress = claimCommits(out.renderer)
   try {
     expect(progress).toHaveLength(1)
-    expect(progress[0]!.snapshot.height).toBe(4)
+    expect(progress[0]!.snapshot.height).toBe(5)
     const rendered = decoder.decode(progress[0]!.snapshot.getRealCharBytes(true))
     expect(rendered).toContain("Markdown Sample")
     expect(rendered).toContain("Item 2")
@@ -165,6 +165,109 @@ test("completes markdown replies without adding a second blank line above the fo
   }
 })
 
+test("streamed assistant final leaves newline ownership to the next entry", async () => {
+  const out = await createTestRenderer({
+    width: 80,
+    screenMode: "split-footer",
+    footerHeight: 6,
+    externalOutputMode: "capture-stdout",
+    consoleMode: "disabled",
+  })
+  active.push(out.renderer)
+
+  const treeSitterClient = new MockTreeSitterClient({ autoResolveTimeout: 0 })
+  treeSitterClient.setMockResult({ highlights: [] })
+
+  const scrollback = new RunScrollbackStream(out.renderer, RUN_THEME_FALLBACK, {
+    treeSitterClient,
+    wrote: false,
+  })
+
+  await scrollback.append({
+    kind: "assistant",
+    text: "hello",
+    phase: "progress",
+    source: "assistant",
+    messageID: "msg-1",
+    partID: "part-1",
+  })
+  destroyCommits(claimCommits(out.renderer))
+
+  await scrollback.append({
+    kind: "assistant",
+    text: "",
+    phase: "final",
+    source: "assistant",
+    messageID: "msg-1",
+    partID: "part-1",
+  })
+
+  const final = claimCommits(out.renderer)
+  try {
+    expect(final).toHaveLength(1)
+    expect(final[0]!.trailingNewline).toBe(false)
+  } finally {
+    destroyCommits(final)
+  }
+})
+
+test("preserves blank rows between streamed markdown block commits", async () => {
+  const out = await createTestRenderer({
+    screenMode: "split-footer",
+    footerHeight: 6,
+    externalOutputMode: "capture-stdout",
+    consoleMode: "disabled",
+  })
+  active.push(out.renderer)
+
+  const treeSitterClient = new MockTreeSitterClient({ autoResolveTimeout: 0 })
+  treeSitterClient.setMockResult({ highlights: [] })
+
+  const scrollback = new RunScrollbackStream(out.renderer, RUN_THEME_FALLBACK, {
+    treeSitterClient,
+    wrote: false,
+  })
+
+  await scrollback.append({
+    kind: "assistant",
+    text: "# Title\n\nPara 1\n\n",
+    phase: "progress",
+    source: "assistant",
+    messageID: "msg-1",
+    partID: "part-1",
+  })
+
+  const first = claimCommits(out.renderer)
+  expect(first).toHaveLength(1)
+
+  await scrollback.append({
+    kind: "assistant",
+    text: "> Quote",
+    phase: "progress",
+    source: "assistant",
+    messageID: "msg-1",
+    partID: "part-1",
+  })
+
+  const second = claimCommits(out.renderer)
+  expect(second).toHaveLength(0)
+
+  await scrollback.complete()
+
+  const final = claimCommits(out.renderer)
+  try {
+    expect(final).toHaveLength(1)
+
+    const rendered = [...first, ...final]
+      .map((item) => decoder.decode(item.snapshot.getRealCharBytes(true)).replace(/ +\n/g, "\n"))
+      .join("")
+    expect(rendered).toContain("# Title\n\nPara 1\n\n> Quote")
+  } finally {
+    destroyCommits(first)
+    destroyCommits(final)
+  }
+})
+
 test("coalesces same-line tool progress into one snapshot", async () => {
   const out = await createTestRenderer({
     width: 80,
@@ -345,7 +448,6 @@ test("renders promoted task-result markdown without leading blank rows", async (
     expect(commits.length).toBeGreaterThan(0)
     const rendered = commits.map((item) => decoder.decode(item.snapshot.getRealCharBytes(true))).join("")
     expect(rendered.startsWith("\n")).toBe(false)
-    expect(rendered.split("\n")[0]?.trim()).toBe("Location: `/tmp/run.ts`")
     expect(rendered).toContain("Summary:")
     expect(rendered).toContain("Local interactive mode")
   } finally {

+ 56 - 1
packages/opencode/test/cli/run/session-data.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from "bun:test"
 import type { Event } from "@opencode-ai/sdk/v2"
-import { createSessionData, flushInterrupted, reduceSessionData } from "../../../src/cli/cmd/run/session-data"
+import { createSessionData, flushInterrupted, reduceSessionData } from "@/cli/cmd/run/session-data"
 
 function reduce(data: ReturnType<typeof createSessionData>, event: unknown, thinking = true) {
   return reduceSessionData({
@@ -77,6 +77,61 @@ describe("run session data", () => {
     ])
   })
 
+  test("buffers whitespace-only initial assistant chunks until real content arrives", () => {
+    let data = createSessionData()
+
+    data = reduce(data, assistant("msg-1")).data
+    data = reduce(data, {
+      type: "message.part.updated",
+      properties: {
+        part: {
+          id: "txt-1",
+          messageID: "msg-1",
+          sessionID: "session-1",
+          type: "text",
+          text: "",
+          time: { start: Date.now() },
+        },
+      },
+    }).data
+
+    let out = reduce(data, {
+      type: "message.part.delta",
+      properties: {
+        sessionID: "session-1",
+        messageID: "msg-1",
+        partID: "txt-1",
+        field: "text",
+        delta: " ",
+      },
+    })
+
+    expect(out.commits).toEqual([])
+
+    data = out.data
+    out = reduce(data, {
+      type: "message.part.delta",
+      properties: {
+        sessionID: "session-1",
+        messageID: "msg-1",
+        partID: "txt-1",
+        field: "text",
+        delta: "Found",
+      },
+    })
+
+    expect(out.commits).toEqual([
+      {
+        kind: "assistant",
+        text: " Found",
+        phase: "progress",
+        source: "assistant",
+        messageID: "msg-1",
+        partID: "txt-1",
+      },
+    ])
+  })
+
   test("drops user text when the delayed role resolves to user", () => {
     let data = createSessionData()
 

+ 1 - 1
packages/opencode/test/cli/run/session.shared.test.ts

@@ -5,7 +5,7 @@ import {
   sessionVariant,
   type RunSession,
   type SessionMessages,
-} from "../../../src/cli/cmd/run/session.shared"
+} from "@/cli/cmd/run/session.shared"
 
 const model = {
   providerID: "openai",

+ 2 - 2
packages/opencode/test/cli/run/stream.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from "bun:test"
-import { writeSessionOutput } from "../../../src/cli/cmd/run/stream"
-import type { FooterApi, FooterEvent, StreamCommit } from "../../../src/cli/cmd/run/types"
+import { writeSessionOutput } from "@/cli/cmd/run/stream"
+import type { FooterApi, FooterEvent, StreamCommit } from "@/cli/cmd/run/types"
 
 function footer() {
   const events: FooterEvent[] = []

+ 2 - 2
packages/opencode/test/cli/run/stream.transport.test.ts

@@ -1,7 +1,7 @@
 import { describe, expect, test } from "bun:test"
 import type { OpencodeClient } from "@opencode-ai/sdk/v2"
-import { createSessionTransport } from "../../../src/cli/cmd/run/stream.transport"
-import type { FooterApi, FooterEvent, RunFilePart, StreamCommit } from "../../../src/cli/cmd/run/types"
+import { createSessionTransport } from "@/cli/cmd/run/stream.transport"
+import type { FooterApi, FooterEvent, RunFilePart, StreamCommit } from "@/cli/cmd/run/types"
 
 function defer<T = void>() {
   let resolve!: (value: T | PromiseLike<T>) => void

+ 2 - 2
packages/opencode/test/cli/run/subagent-data.test.ts

@@ -1,5 +1,5 @@
 import { describe, expect, test } from "bun:test"
-import { entryBody } from "../../../src/cli/cmd/run/entry.body"
+import { entryBody } from "@/cli/cmd/run/entry.body"
 import {
   bootstrapSubagentData,
   clearFinishedSubagents,
@@ -7,7 +7,7 @@ import {
   reduceSubagentData,
   snapshotSelectedSubagentData,
   snapshotSubagentData,
-} from "../../../src/cli/cmd/run/subagent-data"
+} from "@/cli/cmd/run/subagent-data"
 
 function visible(commits: Array<Parameters<typeof entryBody>[0]>) {
   return commits.flatMap((item) => {

+ 1 - 1
packages/opencode/test/cli/run/theme.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from "bun:test"
 import { RGBA, SyntaxStyle } from "@opentui/core"
-import { opaqueSyntaxStyle } from "../../../src/cli/cmd/run/theme"
+import { opaqueSyntaxStyle } from "@/cli/cmd/run/theme"
 
 describe("run theme", () => {
   test("flattens subtle syntax alpha against the run background", () => {

+ 3 - 3
packages/opencode/test/cli/run/variant.shared.test.ts

@@ -3,15 +3,15 @@ import { NodeFileSystem } from "@effect/platform-node"
 import { AppFileSystem } from "@opencode-ai/shared/filesystem"
 import { describe, expect, test } from "bun:test"
 import { Effect, FileSystem, Layer } from "effect"
-import { Global } from "../../../src/global"
+import { Global } from "@/global"
 import {
   createVariantRuntime,
   cycleVariant,
   formatModelLabel,
   pickVariant,
   resolveVariant,
-} from "../../../src/cli/cmd/run/variant.shared"
-import type { SessionMessages } from "../../../src/cli/cmd/run/session.shared"
+} from "@/cli/cmd/run/variant.shared"
+import type { SessionMessages } from "@/cli/cmd/run/session.shared"
 import { testEffect } from "../../lib/effect"
 
 const model = {