refactor(task-toast): improve task toast manager types and logic

Add new toast types and improve state management for background task notifications. Update tests to cover new scenarios.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
justsisyphus 2026-01-22 22:46:36 +09:00
parent aa2b052d28
commit 72098213ee
3 changed files with 21 additions and 13 deletions

View File

@ -160,7 +160,7 @@ describe("TaskToastManager", () => {
// #then - toast should NOT show warning - category default is expected
expect(mockClient.tui.showToast).toHaveBeenCalled()
const call = mockClient.tui.showToast.mock.calls[0][0]
expect(call.body.message).not.toContain("⚠️")
expect(call.body.message).not.toContain("[FALLBACK]")
expect(call.body.message).not.toContain("(category default)")
})
@ -180,7 +180,7 @@ describe("TaskToastManager", () => {
// #then - toast should show fallback warning
expect(mockClient.tui.showToast).toHaveBeenCalled()
const call = mockClient.tui.showToast.mock.calls[0][0]
expect(call.body.message).toContain("⚠️")
expect(call.body.message).toContain("[FALLBACK]")
expect(call.body.message).toContain("anthropic/claude-sonnet-4-5")
expect(call.body.message).toContain("(system default fallback)")
})
@ -201,7 +201,7 @@ describe("TaskToastManager", () => {
// #then - toast should show fallback warning
expect(mockClient.tui.showToast).toHaveBeenCalled()
const call = mockClient.tui.showToast.mock.calls[0][0]
expect(call.body.message).toContain("⚠️")
expect(call.body.message).toContain("[FALLBACK]")
expect(call.body.message).toContain("cliproxy/claude-opus-4-5")
expect(call.body.message).toContain("(inherited from parent)")
})
@ -222,7 +222,7 @@ describe("TaskToastManager", () => {
// #then - toast should NOT show model warning
expect(mockClient.tui.showToast).toHaveBeenCalled()
const call = mockClient.tui.showToast.mock.calls[0][0]
expect(call.body.message).not.toContain("⚠️ Model:")
expect(call.body.message).not.toContain("[FALLBACK] Model:")
expect(call.body.message).not.toContain("(inherited)")
expect(call.body.message).not.toContain("(category default)")
expect(call.body.message).not.toContain("(system default)")
@ -243,7 +243,7 @@ describe("TaskToastManager", () => {
// #then - toast should NOT show model warning
expect(mockClient.tui.showToast).toHaveBeenCalled()
const call = mockClient.tui.showToast.mock.calls[0][0]
expect(call.body.message).not.toContain("⚠️ Model:")
expect(call.body.message).not.toContain("[FALLBACK] Model:")
})
})
})

View File

@ -24,6 +24,7 @@ export class TaskToastManager {
agent: string
isBackground: boolean
status?: TaskStatus
category?: string
skills?: string[]
modelInfo?: ModelFallbackInfo
}): void {
@ -34,6 +35,7 @@ export class TaskToastManager {
status: task.status ?? "running",
startedAt: new Date(),
isBackground: task.isBackground,
category: task.category,
skills: task.skills,
modelInfo: task.modelInfo,
}
@ -116,7 +118,7 @@ export class TaskToastManager {
"system-default": " (system default fallback)",
}
const suffix = suffixMap[newTask.modelInfo!.type as "inherited" | "system-default"]
lines.push(`⚠️ Model fallback: ${newTask.modelInfo!.model}${suffix}`)
lines.push(`[FALLBACK] Model: ${newTask.modelInfo!.model}${suffix}`)
lines.push("")
}
@ -124,10 +126,11 @@ export class TaskToastManager {
lines.push(`Running (${running.length}):${concurrencyInfo}`)
for (const task of running) {
const duration = this.formatDuration(task.startedAt)
const bgIcon = task.isBackground ? "⚡" : "🔄"
const bgIcon = task.isBackground ? "[BG]" : "[RUN]"
const isNew = task.id === newTask.id ? " ← NEW" : ""
const categoryInfo = task.category ? `/${task.category}` : ""
const skillsInfo = task.skills?.length ? ` [${task.skills.join(", ")}]` : ""
lines.push(`${bgIcon} ${task.description} (${task.agent})${skillsInfo} - ${duration}${isNew}`)
lines.push(`${bgIcon} ${task.description} (${task.agent}${categoryInfo})${skillsInfo} - ${duration}${isNew}`)
}
}
@ -135,10 +138,11 @@ export class TaskToastManager {
if (lines.length > 0) lines.push("")
lines.push(`Queued (${queued.length}):`)
for (const task of queued) {
const bgIcon = task.isBackground ? "⏳" : "⏸️"
const bgIcon = task.isBackground ? "[Q]" : "[W]"
const categoryInfo = task.category ? `/${task.category}` : ""
const skillsInfo = task.skills?.length ? ` [${task.skills.join(", ")}]` : ""
const isNew = task.id === newTask.id ? " ← NEW" : ""
lines.push(`${bgIcon} ${task.description} (${task.agent})${skillsInfo} - Queued${isNew}`)
lines.push(`${bgIcon} ${task.description} (${task.agent}${categoryInfo})${skillsInfo} - Queued${isNew}`)
}
}
@ -158,8 +162,8 @@ export class TaskToastManager {
const queued = this.getQueuedTasks()
const title = newTask.isBackground
? `New Background Task`
: `🔄 New Task Executed`
? `New Background Task`
: `New Task Executed`
tuiClient.tui.showToast({
body: {
@ -184,7 +188,7 @@ export class TaskToastManager {
const remaining = this.getRunningTasks()
const queued = this.getQueuedTasks()
let message = `"${task.description}" finished in ${task.duration}`
let message = `"${task.description}" finished in ${task.duration}`
if (remaining.length > 0 || queued.length > 0) {
message += `\n\nStill running: ${remaining.length} | Queued: ${queued.length}`
}

View File

@ -1,8 +1,11 @@
import type { ModelSource } from "../../shared/model-resolver"
export type TaskStatus = "running" | "queued" | "completed" | "error"
export interface ModelFallbackInfo {
model: string
type: "user-defined" | "inherited" | "category-default" | "system-default"
source?: ModelSource
}
export interface TrackedTask {
@ -12,6 +15,7 @@ export interface TrackedTask {
status: TaskStatus
startedAt: Date
isBackground: boolean
category?: string
skills?: string[]
modelInfo?: ModelFallbackInfo
}