Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/opencode/src/cli/cmd/run/footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ export class RunFooter implements FooterApi {
agent: this.options.agentLabel,
model: current ? modelInfo(this.providers(), current).model : this.state().model,
duration: next.duration,
time: next.time,
}),
)
.catch((error) => {
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/src/cli/cmd/run/runtime.queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,13 @@ export async function runPromptQueue(input: QueueInput): Promise<void> {
}

if (sent.mode !== "shell") {
const duration = Locale.duration(Math.max(0, Date.now() - start))
const end = Date.now()
const duration = Locale.duration(Math.max(0, end - start))
emit(
{
type: "turn.duration",
duration,
time: Locale.time(end),
},
{
duration,
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/run/scrollback.surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ export class RunScrollbackStream {
this.markRendered(await this.finishActive(trailingNewline))
}

public async writeTurnSummary(input: { agent: string; model: string; duration: string }): Promise<void> {
public async writeTurnSummary(input: { agent: string; model: string; duration: string; time?: string }): Promise<void> {
await this.append(turnSummaryCommit(input))
}

Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/cli/cmd/run/scrollback.writer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export function spacerWriter(): ScrollbackWriter {
})
}

export function turnSummaryWriter(input: { agent: string; model: string; duration: string; theme: RunTheme }) {
export function turnSummaryWriter(input: { agent: string; model: string; duration: string; time?: string; theme: RunTheme }) {
return createScrollbackWriter(
() => (
<box width="100%" height={1}>
Expand All @@ -342,7 +342,7 @@ export function turnSummaryWriter(input: { agent: string; model: string; duratio
<span style={{ fg: input.theme.block.text }}>{input.agent}</span>
<span style={{ fg: input.theme.block.muted }}>
{" "}
· {input.model} · {input.duration}
· {input.model} · {input.duration}{input.time ? ` · ${input.time}` : ""}
</span>
</text>
</box>
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/src/cli/cmd/run/turn-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ export function turnSummaryCommit(input: {
agent: string
model: string
duration: string
time?: string
messageID?: string
}): StreamCommit {
return {
kind: "system",
text: `▣ ${input.agent} · ${input.model} · ${input.duration}`,
text: `▣ ${input.agent} · ${input.model} · ${input.duration}${input.time ? ` · ${input.time}` : ""}`,
phase: "final",
source: "system",
summary: {
agent: input.agent,
model: input.model,
duration: input.duration,
time: input.time,
},
messageID: input.messageID,
}
Expand All @@ -42,6 +44,7 @@ export function messageTurnSummaryCommit(
agent: Locale.titlecase(info.agent),
model: model ?? info.modelID,
duration: Locale.duration(completed - info.time.created),
time: Locale.time(completed),
messageID: info.id,
})
}
2 changes: 2 additions & 0 deletions packages/opencode/src/cli/cmd/run/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export type TurnSummary = {
agent: string
model: string
duration: string
time?: string
}

export type ScrollbackOptions = {
Expand Down Expand Up @@ -266,6 +267,7 @@ export type FooterEvent =
| {
type: "turn.duration"
duration: string
time: string
}
| {
type: "stream.patch"
Expand Down
17 changes: 17 additions & 0 deletions packages/opencode/test/cli/run/scrollback.surface.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ test("turn summary starts at the left edge", async () => {
}
})

test("turn summary appends completion time after duration", async () => {
const out = await setup()

try {
await out.scrollback.writeTurnSummary({ agent: "Build", model: "Little Frank", duration: "2.2s", time: "3:41 PM" })

const commits = claim(out.renderer)
try {
expect(renderRows(commits.at(-1)!)[0]).toBe("▣ Build · Little Frank · 2.2s · 3:41 PM")
} finally {
destroy(commits)
}
} finally {
out.scrollback.destroy()
}
})

test("theme swaps restyle active reasoning without resetting the stream", async () => {
const previousSyntax = SyntaxStyle.fromStyles({ default: { fg: "#123456" } })
const nextSyntax = SyntaxStyle.fromStyles({ default: { fg: "#abcdef" } })
Expand Down
9 changes: 6 additions & 3 deletions packages/opencode/test/cli/run/session-replay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
import { replayLocalRows, replaySession } from "@/cli/cmd/run/session-replay"
import type { SessionMessages } from "@/cli/cmd/run/session.shared"
import type { RunProvider } from "@/cli/cmd/run/types"
import * as Locale from "@/util/locale"

function userMessage(id: string, text: string): SessionMessages[number] {
return {
Expand Down Expand Up @@ -279,14 +280,15 @@ describe("run session replay", () => {
}),
expect.objectContaining({
kind: "system",
text: "▣ Build · gpt-5 · 2.8s",
text: `▣ Build · gpt-5 · 2.8s · ${Locale.time(3000)}`,
phase: "final",
source: "system",
messageID: "msg-1",
summary: {
agent: "Build",
model: "gpt-5",
duration: "2.8s",
time: Locale.time(3000),
},
}),
])
Expand Down Expand Up @@ -314,11 +316,12 @@ describe("run session replay", () => {
expect(out.commits.at(-1)).toEqual(
expect.objectContaining({
kind: "system",
text: "▣ Build · Little Frank · 2.8s",
text: `▣ Build · Little Frank · 2.8s · ${Locale.time(3000)}`,
summary: {
agent: "Build",
model: "Little Frank",
duration: "2.8s",
time: Locale.time(3000),
},
}),
)
Expand Down Expand Up @@ -346,7 +349,7 @@ describe("run session replay", () => {
expect(out.commits.filter((commit) => commit.summary)).toEqual([
expect.objectContaining({
kind: "system",
text: "▣ Build · gpt-5 · 2.0s",
text: `▣ Build · gpt-5 · 2.0s · ${Locale.time(3000)}`,
messageID: "msg-step-2",
}),
])
Expand Down
20 changes: 20 additions & 0 deletions packages/opencode/test/cli/run/turn-summary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from "bun:test"
import { turnSummaryCommit } from "@/cli/cmd/run/turn-summary"

test("turn summary commit appends completion time after duration", () => {
const commit = turnSummaryCommit({ agent: "Sisyphus - Ultraworker", model: "GPT-5.5", duration: "6.2s", time: "3:41 PM" })

expect(commit.text).toBe("▣ Sisyphus - Ultraworker · GPT-5.5 · 6.2s · 3:41 PM")
expect(commit.summary).toEqual({
agent: "Sisyphus - Ultraworker",
model: "GPT-5.5",
duration: "6.2s",
time: "3:41 PM",
})
})

test("turn summary commit keeps existing text without completion time", () => {
expect(turnSummaryCommit({ agent: "Sisyphus - Ultraworker", model: "GPT-5.5", duration: "6.2s" }).text).toBe(
"▣ Sisyphus - Ultraworker · GPT-5.5 · 6.2s",
)
})
6 changes: 6 additions & 0 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,11 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
if (!user || !user.time) return 0
return props.message.time.completed - user.time.created
})
const completedTime = createMemo(() => {
if (!duration()) return ""
if (!props.message.time.completed) return ""
return Locale.time(props.message.time.completed)
})

const childShortcut = useCommandShortcut("session.child.first")
const backgroundShortcut = useCommandShortcut("session.background")
Expand Down Expand Up @@ -1549,6 +1554,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
<span style={{ fg: theme.textMuted }}> · {model()}</span>
<Show when={duration()}>
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
<span style={{ fg: theme.textMuted }}> · {completedTime()}</span>
</Show>
<Show when={props.message.error?.name === "MessageAbortedError"}>
<span style={{ fg: theme.textMuted }}> · interrupted</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ exports[`TUI inline tool wrapping separates a task row from a preceding inline d
`;

exports[`TUI inline tool wrapping separates an inline row from the previous assistant summary 1`] = `
" ▣ Build · Little Frank · 53.1s
" ▣ Build · Little Frank · 53.1s · 3:41 PM

✓ Build Task — Review changes
↳ 48 toolcalls · 1m 40s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function AssistantSummaryBeforeInlineFixture() {
return (
<box flexDirection="column" width={72}>
<box ref={(el: BoxRenderable) => alwaysSeparate.add(el)} paddingLeft={3}>
<text>▣ Build · Little Frank · 53.1s</text>
<text>▣ Build · Little Frank · 53.1s · 3:41 PM</text>
</box>
<InlineToolRow icon="✓" complete={true} pending="">
{"Build Task — Review changes\n↳ 48 toolcalls · 1m 40s"}
Expand Down
Loading