mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 01:31:22 +03:00
fix(app): keep Working during reasoning-only turns (#51090)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev>
This commit is contained in:
committed by
GitHub
Unverified
parent
77c5f7a516
commit
81efb7f146
@@ -543,6 +543,26 @@ test.describe("Working", () => {
|
||||
await expect(working).toHaveCount(0)
|
||||
})
|
||||
|
||||
test("keeps Working instead of Used 1 Thought while reasoning is the only activity", async ({ page }) => {
|
||||
const timeline = await setupTimeline(page, {
|
||||
messages: [
|
||||
userMessage(),
|
||||
assistantMessage([reasoningPart("prt_only_thought", "Still considering the answer.")], { completed: false }),
|
||||
],
|
||||
settings: { timelineDetail: detailed },
|
||||
})
|
||||
const working = page.locator('[data-component="session-working"]')
|
||||
const group = page.locator('[data-component="collapsed-tool-group"]')
|
||||
await expect(working).toBeVisible()
|
||||
await expect(group).toHaveCount(0)
|
||||
await expect(page.getByRole("button", { name: "Used 1 Thought" })).toHaveCount(0)
|
||||
|
||||
await timeline.send(partUpdated(toolPart("prt_after_thought", "read", "running", { filePath: "package.json" })))
|
||||
await expect(group).toBeVisible()
|
||||
await expect(group).toHaveAttribute("data-timeline-part-ids", `${assistantID}:reasoning:0,prt_after_thought`)
|
||||
await expect(working).toBeVisible()
|
||||
})
|
||||
|
||||
for (const name of ["shell", "patch", "subagent"] as const) {
|
||||
test(`hides Working during ${name} input and execution, then restores it on completion`, async ({ page }) => {
|
||||
const timeline = await setupTimeline(page, {
|
||||
|
||||
@@ -308,15 +308,25 @@ export namespace Timeline {
|
||||
const previousUserMessage = index > 0
|
||||
const compaction = entries.some((entry) => entry.type === "notice" && entry.message.type === "compaction")
|
||||
const lastContent = lastAssistant?.content.at(-1)
|
||||
const thinking =
|
||||
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
|
||||
const working =
|
||||
isActive &&
|
||||
status.type === "busy" &&
|
||||
lastAssistant?.time.completed === undefined &&
|
||||
!lastAssistant?.error &&
|
||||
!lastAssistant?.retry &&
|
||||
!lastAssistant?.retry
|
||||
const thinking =
|
||||
working &&
|
||||
(detail ? detail.thinking.placement === "separate" : showReasoning) &&
|
||||
lastContent?.type === "reasoning" &&
|
||||
lastContent.time?.completed === undefined
|
||||
const thoughtOnly =
|
||||
working &&
|
||||
detail?.thinking.placement === "grouped" &&
|
||||
assistantMessages.every((message) =>
|
||||
message.content.every(
|
||||
(content) => content.type === "reasoning" || !isRenderable(content, showReasoning, detail),
|
||||
),
|
||||
)
|
||||
|
||||
if (previousUserMessage) rows.push(new TimelineRow.TurnGap({ userMessageID: turnID }))
|
||||
if (userMessage) rows.push(new TimelineRow.UserMessage({ userMessageID: turnID }))
|
||||
@@ -326,6 +336,7 @@ export namespace Timeline {
|
||||
// An assistant message can produce several rows because its content parts are
|
||||
// rendered separately. Notices end a segment so none of those rows cross it.
|
||||
const appendAssistantSegment = (messages: SessionMessageAssistant[]) => {
|
||||
if (thoughtOnly) return
|
||||
const refs = messages.flatMap((message, messageIndex) =>
|
||||
contentEntries(message)
|
||||
.filter(
|
||||
|
||||
@@ -264,6 +264,54 @@ describe("current session timeline rows", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("omits a grouped reasoning-only row while busy until other content or completion", () => {
|
||||
const thought = { type: "reasoning", text: "Thinking", time: { created: 1, completed: 2 } } as const
|
||||
const rows = (content: SessionMessageAssistant["content"], busy: boolean) =>
|
||||
createTimelineProjection({
|
||||
sessionMessages: storyDocument(content, busy).messages,
|
||||
status: { type: busy ? "busy" : "idle" },
|
||||
reasoningMode: "compact",
|
||||
timelineDetail: timelinePresets[2].value,
|
||||
}).rows
|
||||
|
||||
expect(rows([{ ...thought, time: { created: 1 } }], true).map((row) => row._tag)).toEqual(["UserMessage"])
|
||||
expect(rows([thought], true).map((row) => row._tag)).toEqual(["UserMessage"])
|
||||
expect(rows([thought, { type: "text", text: "" }], true).map((row) => row._tag)).toEqual(["UserMessage"])
|
||||
expect(rows([thought, { type: "text", text: "Answer" }], true).map((row) => row._tag)).toEqual([
|
||||
"UserMessage",
|
||||
"AssistantPart",
|
||||
"AssistantPart",
|
||||
])
|
||||
expect(
|
||||
rows([thought, storyTool("read", "read", "running", { filePath: "package.json" })], true).map((row) => row._tag),
|
||||
).toEqual(["UserMessage", "AssistantPart"])
|
||||
expect(rows([thought], false).map((row) => row._tag)).toEqual(["UserMessage", "AssistantPart"])
|
||||
|
||||
const failed = storyDocument([thought], true)
|
||||
const messages = failed.messages.map((message) =>
|
||||
message.type === "assistant" ? { ...message, error: { type: "provider.error", message: "Failed" } } : message,
|
||||
)
|
||||
expect(
|
||||
createTimelineProjection({
|
||||
sessionMessages: messages,
|
||||
status: failed.status,
|
||||
reasoningMode: "compact",
|
||||
timelineDetail: timelinePresets[2].value,
|
||||
}).rows.map((row) => row._tag),
|
||||
).toEqual(["UserMessage", "AssistantPart", "Error"])
|
||||
|
||||
expect(
|
||||
createTimelineProjection({
|
||||
sessionMessages: failed.messages.map((message) =>
|
||||
message.type === "assistant" ? { ...message, error: { type: "Interrupted", message: "Stopped" } } : message,
|
||||
),
|
||||
status: failed.status,
|
||||
reasoningMode: "compact",
|
||||
timelineDetail: { ...timelinePresets[2].value, notices: { placement: "hidden" } },
|
||||
}).rows.map((row) => row._tag),
|
||||
).toEqual(["UserMessage", "AssistantPart"])
|
||||
})
|
||||
|
||||
test("stops thinking on idle, message completion, errors and retries", () => {
|
||||
const document = storyDocument([{ type: "reasoning", text: "Current thought" }], true)
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user