mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(tui): use disclosure icon for low activity errors (#52097)
Co-authored-by: thdxr <826656+thdxr@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0f931d3939
commit
948047a8ca
@@ -91,7 +91,6 @@ export function activitySummary(items: readonly Item[], instructions: number, cl
|
||||
.map(([name, count]) => `${count} ${name}${count === 1 ? "" : "s"}`)
|
||||
.join(", "),
|
||||
active: items.some((item) => isActive(item, closed)),
|
||||
failed: items.some(failed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +99,3 @@ function isActive(item: Item, closed: boolean) {
|
||||
return !closed && item.part.time?.completed === undefined && item.message.time.completed === undefined
|
||||
return item.part.state.status === "streaming" || item.part.state.status === "running"
|
||||
}
|
||||
|
||||
function failed(item: Item) {
|
||||
if (item.part.type !== "tool") return false
|
||||
if (item.part.state.status === "error") return true
|
||||
if (canonicalToolName(item.part.name) !== "execute" || item.part.state.status === "streaming") return false
|
||||
const metadata = item.part.state.metadata
|
||||
return metadata?.error === true || executeCalls(metadata?.toolCalls).some((call) => call.status === "error")
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ function ActivityGroup(props: GroupProps) {
|
||||
<GroupAnchor groupID={disclosure.id()} active={summary().label !== ""}>
|
||||
<Show when={summary().label}>
|
||||
<InlineToolRow
|
||||
icon={summary().failed ? "✗" : disclosure.expanded() ? "−" : "+"}
|
||||
icon={disclosure.expanded() ? "−" : "+"}
|
||||
color={hover() ? theme.text.base : theme.text.muted}
|
||||
complete={true}
|
||||
pending={summary().label}
|
||||
|
||||
@@ -200,6 +200,57 @@ test("a low activity group with nothing finished stays collapsed behind a status
|
||||
}
|
||||
})
|
||||
|
||||
test("failed low activity uses a disclosure icon and keeps details expandable", async () => {
|
||||
const config = createTuiResolvedConfig({ animations: false })
|
||||
const anchors = createTimelineAnchors()
|
||||
const [expanded, setExpanded] = createStore<Record<string, boolean>>({})
|
||||
const message: SessionMessageAssistant = {
|
||||
id: "a",
|
||||
type: "assistant",
|
||||
agent: "build",
|
||||
model: { providerID: "fixture", id: "fixture" },
|
||||
time: { created: 0, completed: 2 },
|
||||
content: [
|
||||
{
|
||||
type: "tool",
|
||||
id: "failed-shell",
|
||||
name: "shell",
|
||||
time: { created: 0, completed: 2 },
|
||||
state: { status: "error", input: {}, error: { type: "Fixture", message: "command failed" } },
|
||||
},
|
||||
],
|
||||
}
|
||||
const row: SessionGroup = {
|
||||
type: "group",
|
||||
kind: "activity",
|
||||
size: 1,
|
||||
completed: true,
|
||||
pending: [],
|
||||
children: [{ type: "entry", size: 1, entry: { type: "part", ref: { messageID: "a", partID: "failed-shell" } } }],
|
||||
}
|
||||
const app = await mount({
|
||||
row,
|
||||
anchors,
|
||||
config,
|
||||
expanded: (id) => expanded[id],
|
||||
setExpanded: (id, value) => setExpanded(id, value),
|
||||
message: () => message,
|
||||
entry: () => <text>command failed</text>,
|
||||
})
|
||||
try {
|
||||
app.renderer.start()
|
||||
await app.waitForFrame((frame) => frame.includes("1 command"))
|
||||
expect(app.captureCharFrame()).toContain("+ 1 command")
|
||||
expect(app.captureCharFrame()).not.toContain("command failed")
|
||||
await app.mockMouse.click(4, anchors.get({ type: "group", groupID: groupID(row, 0)! })?.node.y ?? -1)
|
||||
await app.renderOnce()
|
||||
expect(app.captureCharFrame()).toContain("− 1 command")
|
||||
expect(app.captureCharFrame()).toContain("command failed")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
function mount(input: {
|
||||
row: SessionGroup
|
||||
anchors: ReturnType<typeof createTimelineAnchors>
|
||||
|
||||
@@ -180,11 +180,10 @@ test("activity summary counts finished work by category", () => {
|
||||
expect(activitySummary(items, 2)).toEqual({
|
||||
label: "2 commands, 2 edits, 1 thought, 1 read, 1 tool, 2 instructions",
|
||||
active: false,
|
||||
failed: false,
|
||||
})
|
||||
})
|
||||
|
||||
test("running work is active but not counted, and failures are reported", () => {
|
||||
test("running work is active but not counted, and failed tools are counted", () => {
|
||||
const open = { ...assistant("a", []), time: { created: 1 } }
|
||||
const summary = activitySummary(
|
||||
[
|
||||
@@ -195,7 +194,7 @@ test("running work is active but not counted, and failures are reported", () =>
|
||||
],
|
||||
0,
|
||||
)
|
||||
expect(summary).toEqual({ label: "1 read, 1 tool", active: true, failed: true })
|
||||
expect(summary).toEqual({ label: "1 read, 1 tool", active: true })
|
||||
})
|
||||
|
||||
test("execute counts its finished nested calls instead of itself", () => {
|
||||
@@ -219,7 +218,6 @@ test("execute counts its finished nested calls instead of itself", () => {
|
||||
expect(activitySummary([{ message, part: execute }], 0)).toEqual({
|
||||
label: "2 reads, 1 tool",
|
||||
active: false,
|
||||
failed: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -257,8 +255,8 @@ test("a thought still streaming counts as finished once a later row closes its g
|
||||
{ message: open, part: { type: "reasoning" as const, text: "Planning", time: { created: 1 } } },
|
||||
{ message: open, part: tool("r1", "read") },
|
||||
]
|
||||
expect(activitySummary(items, 0)).toEqual({ label: "1 read", active: true, failed: false })
|
||||
expect(activitySummary(items, 0, true)).toEqual({ label: "1 thought, 1 read", active: false, failed: false })
|
||||
expect(activitySummary(items, 0)).toEqual({ label: "1 read", active: true })
|
||||
expect(activitySummary(items, 0, true)).toEqual({ label: "1 thought, 1 read", active: false })
|
||||
})
|
||||
|
||||
test("until something finishes, the label is the first running item's status", () => {
|
||||
@@ -271,7 +269,6 @@ test("until something finishes, the label is the first running item's status", (
|
||||
expect(summarizeActivity(activity, () => open, [], false)).toEqual({
|
||||
label: "Running edit…",
|
||||
active: true,
|
||||
failed: false,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user