fix(tui): tighten subagent background shortcut
This commit is contained in:
parent
3005e8bb98
commit
3d0ff1c705
5 changed files with 66 additions and 20 deletions
|
|
@ -316,7 +316,7 @@ describe("SubagentTool", () => {
|
|||
}).pipe(Effect.forkScoped)
|
||||
|
||||
const backgrounded = yield* Effect.gen(function* () {
|
||||
for (const _ of Array.from({ length: 20 })) {
|
||||
for (const _ of Array.from({ length: 100 })) {
|
||||
const result = yield* jobs.backgroundAll({ sessionID: parent.id, type: SubagentTool.name })
|
||||
if (result.length > 0) return result
|
||||
yield* Effect.sleep("10 millis")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useTheme, selectedForeground } from "../../../context/theme"
|
|||
import { Locale } from "../../../util/locale"
|
||||
import { useBindings, useCommandShortcut } from "../../../keymap"
|
||||
import { useComposerTab } from "./index"
|
||||
import { useTuiConfig } from "../../../config"
|
||||
|
||||
interface SubagentEntry {
|
||||
sessionID: string
|
||||
|
|
@ -23,6 +24,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
const { theme } = useTheme()
|
||||
const fg = selectedForeground(theme)
|
||||
const composer = useComposerTab()
|
||||
const tuiConfig = useTuiConfig()
|
||||
const interruptHint = useCommandShortcut("composer.subagent.interrupt")
|
||||
|
||||
const session = createMemo(() => data.session.get(props.sessionID))
|
||||
|
|
@ -175,6 +177,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
{ key: "down", desc: "Next subagent", group: "Subagents", cmd: "composer.subagent.down" },
|
||||
{ key: "return", desc: "Navigate to subagent", group: "Subagents", cmd: "composer.subagent.select" },
|
||||
{ key: "ctrl+d", desc: "Interrupt subagent", group: "Subagents", cmd: "composer.subagent.interrupt" },
|
||||
...tuiConfig.keybinds.gather("session.background", ["session.background"] as const),
|
||||
],
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,13 @@ export function Session() {
|
|||
}),
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(backgroundSessionID, (sessionID) => {
|
||||
if (sessionID === route.sessionID) return
|
||||
void data.session.message.refresh(sessionID)
|
||||
}),
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(descendantSessionIDs, (sessionIDs) => {
|
||||
void Promise.all(sessionIDs.map((sessionID) => data.session.permission.refresh(sessionID)))
|
||||
|
|
@ -907,12 +914,6 @@ export function Session() {
|
|||
bindings: tuiConfig.keybinds.gather("session.background", sessionBackgroundBindingCommands),
|
||||
}))
|
||||
|
||||
useBindings(() => ({
|
||||
mode: "composer",
|
||||
enabled: () => foregroundSubagents() > 0 && composer.open,
|
||||
bindings: tuiConfig.keybinds.gather("session.background", sessionBackgroundBindingCommands),
|
||||
}))
|
||||
|
||||
// snap to bottom when session changes
|
||||
createEffect(on(() => route.sessionID, toBottom))
|
||||
createEffect(
|
||||
|
|
|
|||
|
|
@ -65,11 +65,12 @@ export function foregroundSubagentCount(input: {
|
|||
.map((session) => session.id),
|
||||
)
|
||||
|
||||
const runningSessionTitles = new Set(
|
||||
input.sessions
|
||||
.filter((session) => runningSessionIDs.has(session.id) && session.title)
|
||||
.map((session) => session.title),
|
||||
)
|
||||
const runningSessionTitleCounts = input.sessions
|
||||
.filter((session) => runningSessionIDs.has(session.id) && session.title)
|
||||
.reduce((counts, session) => {
|
||||
if (!session.title) return counts
|
||||
return counts.set(session.title, (counts.get(session.title) ?? 0) + 1)
|
||||
}, new Map<string, number>())
|
||||
|
||||
const runningRows = input.messages.flatMap((message) =>
|
||||
message.type === "assistant"
|
||||
|
|
@ -91,16 +92,20 @@ export function foregroundSubagentCount(input: {
|
|||
}),
|
||||
)
|
||||
|
||||
const anonymousRunningRows = runningRows.filter((part) => {
|
||||
if (subagentSessionID(part.state?.structured ?? {})) return false
|
||||
const anonymousRunningRows = runningRows.filter((part) => !subagentSessionID(part.state?.structured ?? {}))
|
||||
const matchedAnonymousRows = anonymousRunningRows.filter((part) => {
|
||||
const input = part.state?.input
|
||||
if (typeof input !== "object" || input === null || Array.isArray(input) || !("description" in input)) return true
|
||||
return typeof input.description === "string" ? !runningSessionTitles.has(input.description) : true
|
||||
if (typeof input !== "object" || input === null || Array.isArray(input) || !("description" in input)) return false
|
||||
if (typeof input.description !== "string") return false
|
||||
const remaining = runningSessionTitleCounts.get(input.description) ?? 0
|
||||
if (remaining <= 0) return false
|
||||
runningSessionTitleCounts.set(input.description, remaining - 1)
|
||||
return true
|
||||
}).length
|
||||
|
||||
const runningSessions = [...runningSessionIDs].filter((session) => !runningRowSessionIDs.has(session)).length
|
||||
|
||||
return runningSessions + runningRowSessionIDs.size + anonymousRunningRows
|
||||
return runningSessions + runningRowSessionIDs.size + anonymousRunningRows.length - matchedAnonymousRows
|
||||
}
|
||||
|
||||
export function subagentDisplayState(input: SubagentDisplayStateInput) {
|
||||
|
|
@ -111,7 +116,7 @@ export function subagentDisplayState(input: SubagentDisplayStateInput) {
|
|||
return {
|
||||
background,
|
||||
running,
|
||||
icon: running ? "│" : "✓",
|
||||
icon: running ? "│" : input.toolStatus === "completed" ? "✓" : "│",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,33 @@ describe("foregroundSubagentCount", () => {
|
|||
).toBe(2)
|
||||
})
|
||||
|
||||
test("does not undercount duplicate descriptions when only one child session is synced", () => {
|
||||
expect(
|
||||
foregroundSubagentCount({
|
||||
sessionID: "parent",
|
||||
sessions: [{ id: "child-1", parentID: "parent", title: "Same task" }],
|
||||
messages: [
|
||||
{
|
||||
type: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool",
|
||||
name: "subagent",
|
||||
state: { status: "running", input: { description: "Same task" }, structured: {} },
|
||||
},
|
||||
{
|
||||
type: "tool",
|
||||
name: "subagent",
|
||||
state: { status: "running", input: { description: "Same task" }, structured: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
status: () => "running",
|
||||
}),
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
test("counts running child sessions", () => {
|
||||
expect(
|
||||
foregroundSubagentCount({
|
||||
|
|
@ -138,13 +165,23 @@ describe("subagentDisplayState", () => {
|
|||
).toEqual({ background: true, running: false, icon: "✓" })
|
||||
})
|
||||
|
||||
test("does not spin forever from stale background metadata when the child session is unknown", () => {
|
||||
test("does not spin forever from stale background metadata without a child session ID", () => {
|
||||
expect(
|
||||
subagentDisplayState({
|
||||
toolStatus: "completed",
|
||||
metadata: { sessionID: "child", status: "running", background: true },
|
||||
metadata: { status: "running", background: true },
|
||||
sessionStatus: () => "idle",
|
||||
}),
|
||||
).toEqual({ background: true, running: false, icon: "✓" })
|
||||
})
|
||||
|
||||
test("does not show a checkmark for errored subagent rows", () => {
|
||||
expect(
|
||||
subagentDisplayState({
|
||||
toolStatus: "error",
|
||||
metadata: { background: true },
|
||||
sessionStatus: () => "idle",
|
||||
}),
|
||||
).toEqual({ background: true, running: false, icon: "│" })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue