declare const require: (name: string) => any const { describe, test, expect, beforeEach, afterEach, mock } = require("bun:test") import type { ConcurrencyManager } from "../background-agent/concurrency" type TaskToastManagerClass = typeof import("./manager").TaskToastManager describe("TaskToastManager", () => { let TaskToastManager: TaskToastManagerClass let mockClient: { tui: { showToast: ReturnType } } let toastManager: InstanceType let mockConcurrencyManager: ConcurrencyManager beforeEach(async () => { mockClient = { tui: { showToast: mock(() => Promise.resolve()), }, } mockConcurrencyManager = { getConcurrencyLimit: mock(() => 5), } as unknown as ConcurrencyManager const mod = await import("./manager") TaskToastManager = mod.TaskToastManager // eslint-disable-next-line @typescript-eslint/no-explicit-any toastManager = new TaskToastManager(mockClient as any, mockConcurrencyManager) }) afterEach(() => { mock.restore() }) describe("skills in toast message", () => { test("should display skills when provided", () => { // given - a task with skills const task = { id: "task_1", description: "Test task", agent: "sisyphus-junior", isBackground: true, skills: ["playwright", "git-master"], } // when - addTask is called toastManager.addTask(task) // then - toast message should include skills expect(mockClient.tui.showToast).toHaveBeenCalled() const call = mockClient.tui.showToast.mock.calls[0][0] expect(call.body.message).toContain("playwright") expect(call.body.message).toContain("git-master") }) test("should not display skills section when no skills provided", () => { // given - a task without skills const task = { id: "task_2", description: "Test task without skills", agent: "explore", isBackground: true, } // when - addTask is called toastManager.addTask(task) // then - toast message should not include skills prefix expect(mockClient.tui.showToast).toHaveBeenCalled() const call = mockClient.tui.showToast.mock.calls[0][0] expect(call.body.message).not.toContain("Skills:") }) }) describe("concurrency info in toast message", () => { test("should display concurrency status in toast", () => { // given - multiple running tasks toastManager.addTask({ id: "task_1", description: "First task", agent: "explore", isBackground: true, }) toastManager.addTask({ id: "task_2", description: "Second task", agent: "librarian", isBackground: true, }) // when - third task is added toastManager.addTask({ id: "task_3", description: "Third task", agent: "explore", isBackground: true, }) // then - toast should show concurrency info expect(mockClient.tui.showToast).toHaveBeenCalledTimes(3) const lastCall = mockClient.tui.showToast.mock.calls[2][0] // Should show "Running (3):" header expect(lastCall.body.message).toContain("Running (3):") }) test("should display concurrency limit info when available", () => { // given - a concurrency manager with known limit const mockConcurrencyWithCounts = { getConcurrencyLimit: mock(() => 5), getRunningCount: mock(() => 2), getQueuedCount: mock(() => 1), } as unknown as ConcurrencyManager // eslint-disable-next-line @typescript-eslint/no-explicit-any const managerWithConcurrency = new TaskToastManager(mockClient as any, mockConcurrencyWithCounts) // when - a task is added managerWithConcurrency.addTask({ id: "task_1", description: "Test task", agent: "explore", isBackground: true, }) // then - toast should show concurrency status like "2/5 slots" expect(mockClient.tui.showToast).toHaveBeenCalled() const call = mockClient.tui.showToast.mock.calls[0][0] expect(call.body.message).toMatch(/\d+\/\d+/) }) }) describe("combined skills and concurrency display", () => { test("should display both skills and concurrency info together", () => { // given - a task with skills and concurrency manager const task = { id: "task_1", description: "Full info task", agent: "sisyphus-junior", isBackground: true, skills: ["frontend-ui-ux"], } // when - addTask is called toastManager.addTask(task) // then - toast should include both skills and task count expect(mockClient.tui.showToast).toHaveBeenCalled() const call = mockClient.tui.showToast.mock.calls[0][0] expect(call.body.message).toContain("frontend-ui-ux") expect(call.body.message).toContain("Running (1):") }) }) describe("model fallback info in toast message", () => { test("should NOT display warning when model is category-default (normal behavior)", () => { // given - category-default is the intended behavior, not a fallback const task = { id: "task_1", description: "Task with category default model", agent: "sisyphus-junior", isBackground: false, modelInfo: { model: "google/gemini-3-pro", type: "category-default" as const }, } // when - addTask is called toastManager.addTask(task) // 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("[FALLBACK]") expect(call.body.message).not.toContain("(category default)") }) test("should display warning when model falls back to system-default", () => { // given - system-default is a fallback (no category default, no user config) const task = { id: "task_1b", description: "Task with system default model", agent: "sisyphus-junior", isBackground: false, modelInfo: { model: "anthropic/claude-sonnet-4-6", type: "system-default" as const }, } // when - addTask is called toastManager.addTask(task) // 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("[FALLBACK]") expect(call.body.message).toContain("anthropic/claude-sonnet-4-6") expect(call.body.message).toContain("(system default fallback)") }) test("should display warning when model is inherited from parent", () => { // given - inherited is a fallback (custom category without model definition) const task = { id: "task_2", description: "Task with inherited model", agent: "sisyphus-junior", isBackground: false, modelInfo: { model: "cliproxy/claude-opus-4-6", type: "inherited" as const }, } // when - addTask is called toastManager.addTask(task) // 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("[FALLBACK]") expect(call.body.message).toContain("cliproxy/claude-opus-4-6") expect(call.body.message).toContain("(inherited from parent)") }) test("should display warning when model is runtime fallback", () => { // given - runtime-fallback indicates a model swap mid-run const task = { id: "task_runtime", description: "Task with runtime fallback model", agent: "explore", isBackground: false, modelInfo: { model: "anthropic/oswe-vscode-prime", type: "runtime-fallback" as const }, } // when - addTask is called toastManager.addTask(task) // 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("[FALLBACK]") expect(call.body.message).toContain("anthropic/oswe-vscode-prime") expect(call.body.message).toContain("(runtime fallback)") }) test("should not display model info when user-defined", () => { // given - a task with user-defined model const task = { id: "task_3", description: "Task with user model", agent: "sisyphus-junior", isBackground: false, modelInfo: { model: "my-provider/my-model", type: "user-defined" as const }, } // when - addTask is called toastManager.addTask(task) // 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("[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)") }) test("should not display model info when not provided", () => { // given - a task without model info const task = { id: "task_4", description: "Task without model info", agent: "explore", isBackground: true, } // when - addTask is called toastManager.addTask(task) // 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("[FALLBACK] Model:") }) }) describe("updateTaskModelBySession", () => { test("updates task model info and shows fallback toast", () => { // given - task without model info const task = { id: "task_update", sessionID: "ses_update_1", description: "Task that will fallback", agent: "explore", isBackground: false, } toastManager.addTask(task) mockClient.tui.showToast.mockClear() // when - runtime fallback applied by session toastManager.updateTaskModelBySession("ses_update_1", { model: "nvidia/stepfun-ai/step-3.5-flash", type: "runtime-fallback", }) // then - new toast shows fallback model expect(mockClient.tui.showToast).toHaveBeenCalled() const call = mockClient.tui.showToast.mock.calls[0][0] expect(call.body.message).toContain("[FALLBACK]") expect(call.body.message).toContain("nvidia/stepfun-ai/step-3.5-flash") expect(call.body.message).toContain("(runtime fallback)") }) }) })