Skip to content

Commit 5a5d06d

Browse files
committed
refactor(app): derive blocking from shared subagent scan
1 parent eb83856 commit 5a5d06d

2 files changed

Lines changed: 56 additions & 29 deletions

File tree

‎packages/app/src/session/requests/background.test.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,20 @@ describe("subagent background tasks", () => {
102102

103103
expect(background([assistant("m1", [shell], true)]).tasks).toEqual([{ id: "sh-1", type: "shell", label: "ls" }])
104104
})
105+
106+
test("running shell parts block only from the latest message", () => {
107+
const live = tool(
108+
{ status: "running", input: { command: "tail -f x" }, metadata: { shellID: "sh-live" } },
109+
{ id: "live", name: "shell" },
110+
)
111+
112+
const stale = tool(
113+
{ status: "running", input: { command: "sleep 99" }, metadata: { shellID: "sh-old" } },
114+
{ id: "old", name: "shell" },
115+
)
116+
117+
const model = background([assistant("m1", [stale], true), assistant("m2", [live], false)])
118+
119+
expect(model.blocking).toEqual([{ type: "shell", partID: "live", id: "sh-live", label: "tail -f x" }])
120+
})
105121
})

‎packages/app/src/session/requests/background.ts‎

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SessionInfo, SessionMessageAssistant, SessionMessageInfo, ShellInfo } from "@opencode/client/promise"
1+
import type { SessionInfo, SessionMessageInfo, ShellInfo } from "@opencode/client/promise"
22
import { scanSubagentTranscript } from "@opencode/session-ui/subagent-transcript"
33
import { Predicate } from "effect"
44
import { createMemo } from "solid-js"
@@ -46,14 +46,13 @@ export function createSessionBackground(input: {
4646
]
4747
})
4848

49-
const assistant = messages.reduce<SessionMessageAssistant | undefined>((latest, message) => {
50-
if (message.type === "synthetic") return latest
49+
for (const message of messages) {
50+
if (message.type !== "assistant") continue
5151

52-
if (message.type !== "assistant") return latest
53-
message.content.forEach((part) => {
54-
if (part.type !== "tool" || part.name !== "shell") return
52+
for (const part of message.content) {
53+
if (part.type !== "tool" || part.name !== "shell") continue
5554

56-
if (part.state.status !== "completed" || part.state.metadata?.status !== "running") return
55+
if (part.state.status !== "completed" || part.state.metadata?.status !== "running") continue
5756

5857
const shellID = part.state.metadata.shellID
5958
const command = part.state.input.command
@@ -65,36 +64,48 @@ export function createSessionBackground(input: {
6564
label: Predicate.isString(command) ? command : part.id,
6665
},
6766
})
68-
})
67+
}
68+
}
69+
70+
// Live foreground calls, from the shared scan so the header and the panel
71+
// agree on what counts as live. Legacy task parts surface as subagents.
72+
const blocking = [
73+
...scan.calls.flatMap((call) => {
74+
if (!call.live || call.status !== "running") return []
75+
76+
return [
77+
{
78+
type: "subagent" as const,
79+
partID: call.partID,
80+
id: call.childID,
81+
label: call.description,
82+
},
83+
]
84+
}),
85+
...(scan.live?.content.flatMap((part) => {
86+
if (part.type !== "tool" || part.name !== "shell" || part.state.status !== "running") return []
6987

70-
return message.time.completed === undefined ? message : latest
71-
}, undefined)
88+
const shellID = part.state.metadata.shellID
89+
const command = part.state.input.command
90+
91+
return [
92+
{
93+
type: "shell" as const,
94+
partID: part.id,
95+
id: Predicate.isString(shellID) ? shellID : undefined,
96+
label: Predicate.isString(command) ? command : undefined,
97+
},
98+
]
99+
}) ?? []),
100+
]
72101

73102
return {
74103
// Backgrounded calls stay listed until their synthetic completion notice lands (filtered at construction).
75104
subagents,
76105
shells: shells.flatMap((item) =>
77106
!completed.has(item.partID) && !completed.has(item.task.id) ? [item.task] : [],
78107
),
79-
blocking:
80-
assistant?.content.flatMap((part) => {
81-
if (part.type !== "tool" || part.state.status !== "running") return []
82-
83-
if (part.name !== "shell" && part.name !== "subagent") return []
84-
const value = part.name === "shell" ? part.state.metadata.shellID : part.state.metadata.sessionID
85-
const label = part.name === "shell" ? part.state.input.command : part.state.input.description
86-
87-
return [
88-
{
89-
// SAFETY: the guard above leaves only "shell" and "subagent" names.
90-
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- see SAFETY above
91-
type: part.name as "shell" | "subagent",
92-
partID: part.id,
93-
id: Predicate.isString(value) ? value : undefined,
94-
label: Predicate.isString(label) ? label : undefined,
95-
},
96-
]
97-
}) ?? [],
108+
blocking,
98109
}
99110
})
100111

0 commit comments

Comments
 (0)