import { describe, expect, it } from "vitest"; import type { AgentConfigOption, AgentEntry, AgentStatus, AgentTask } from "@/bindings"; import { applyCompletion, atEdgeLine, canSend, completionAt, isPathQuery, resolveTyped, imageFromDataUrl, mainOptions, matchCommands, mentionOf, modeIcon, nextValue, recall, toPrompt, retryParts, usageLabel, workTime, } from "./composerModel"; import { age, attentionChange, branchLabel, entriesKey, findSubagent, groupTasks, subagentTitle, turnNumbers, upsertEntry, type SubagentEntry, } from "./model"; import { markdownHtml } from "./Markdown"; function task(id: string, status: AgentStatus, updatedMs: number): AgentTask { return { id, title: id, provider: "claude", model: null, status, isolation: "worktree", cwd: "/w", branch: `agent/${id}`, base: "main", baseCommit: "abc", error: null, createdMs: 0, updatedMs, config: [], commands: [], usage: null, queue: [], }; } const option = (category: string, values: string[], current: string | null): AgentConfigOption => ({ id: category, name: category, description: null, category, current, values: values.map((value) => ({ value, name: value, description: null })), via: "option", }); describe("agent composer", () => { it("completes @files anywhere and /commands as the first word", () => { expect(completionAt("fix @src/le", 11)).toEqual({ kind: "file", query: "src/le", from: 4, to: 11 }); expect(completionAt("/rev", 4)).toEqual({ kind: "command", query: "rev", from: 0, to: 4 }); expect(completionAt("run /rev", 8)).toBeNull(); expect(completionAt("a@b", 3)).toBeNull(); const c = completionAt("see @le and", 7)!; expect(applyCompletion("see @le and", c, "@src/lexer.rs")).toEqual({ text: "see @src/lexer.rs and", caret: 18 }); const cmds = [ { name: "init", description: "", hint: null }, { name: "review", description: "", hint: null }, { name: "pr-review", description: "", hint: null }, ]; expect(matchCommands(cmds, "rev").map((c) => c.name)).toEqual(["review", "pr-review"]); }); it("matches commands by prefix, plugin name, substring, then letters in order", () => { const cmds = ["triage", "verify", "issue-fix", "brooks-lint:brooks-review", "pr-review", "init"].map((name) => ({ name, description: "", hint: null, })); const names = (q: string) => matchCommands(cmds, q).map((c) => c.name); expect(names("trig")).toEqual(["triage"]); expect(names("brooks-r")).toEqual(["brooks-lint:brooks-review"]); expect(names("rev")).toEqual(["brooks-lint:brooks-review", "pr-review"]); expect(names("if")).toEqual(["verify", "issue-fix"]); expect(names("xyz")).toEqual([]); expect(names("")).toHaveLength(cmds.length); }); it("sends only the files still mentioned", () => { const prompt = toPrompt({ text: " look at @src/a.rs:3-5 and @b.rs ", files: [ { path: "/w/src/a.rs", rel: "src/a.rs" }, { path: "/w/b.rs", rel: "b.rs" }, { path: "/w/b.rs", rel: "b.rs" }, { path: "/w/gone.rs", rel: "gone.rs" }, ], images: [], }); expect(prompt).toEqual({ text: "look at @src/a.rs:3-5 and @b.rs", files: ["/w/src/a.rs", "/w/b.rs"], images: [] }); expect(mentionOf("a.rs", { from: 3, to: 3 })).toBe("@a.rs:3"); expect(canSend({ text: " ", files: [], images: [] })).toBe(false); }); it("recalls history from the edge lines only", () => { const h = ["newest", "older"]; expect(recall(h, -1, -1)).toBe(0); expect(recall(h, 0, -1)).toBe(1); expect(recall(h, 1, -1)).toBeNull(); expect(recall(h, 0, 1)).toBe(-1); expect(recall(h, -1, 1)).toBeNull(); expect(atEdgeLine("a\nb", 1, -1)).toBe(true); expect(atEdgeLine("a\nb", 3, -1)).toBe(false); expect(atEdgeLine("a\nb", 1, 1)).toBe(false); }); it("picks the main settings and cycles the mode", () => { const config = [option("fast", ["on"], null), option("mode", ["ask", "code"], "code"), option("model", ["m"], "m")]; expect(mainOptions(config).map((o) => o.id)).toEqual(["model", "mode"]); expect(nextValue(config[1]!)).toBe("ask"); const approvals = { ...option("openidea.cursor.approval", ["ask", "bypass"], "ask"), category: "approval" }; expect(mainOptions([...config, approvals]).map((o) => o.id)).toEqual(["model", "mode", approvals.id]); expect( ["default", "acceptEdits", "plan", "auto", "bypassPermissions", "auto_edit", "read-only", "x"].map(modeIcon), ).toEqual(["lock", "edit", "todo", "run", "unlock", "edit", "eye", "lock"]); expect(usageLabel({ used: 24_000, size: 200_000 })).toEqual({ short: "12%", detail: "24k / 200k tokens", }); expect(imageFromDataUrl("data:image/png;base64,AAA", "a.png")).toEqual({ mimeType: "image/png", data: "AAA", name: "a.png", }); expect(imageFromDataUrl("data:text/plain;base64,AAA")).toBeNull(); }); }); describe("usage meters", () => { it("formats token counts", () => { expect(usageLabel({ used: 83_100, size: 1_000_000 }).detail).toBe("83.1k / 1M tokens"); }); it("adds up the time spent on turns", () => { const user = (sentMs: number, doneMs: number | null): AgentEntry => ({ type: "user", text: "", attachments: [], sentMs, doneMs, }); const entries = [user(0, 5_000), { type: "agent", text: "" } as AgentEntry, user(10_000, null)]; expect(workTime(entries, 12_000)).toBe(7_000); expect(workTime(entries, null)).toBe(5_000); }); }); describe("agents model", () => { it("groups tasks by what they need, newest first", () => { const groups = groupTasks([ task("a", "ready", 1), task("b", "needsInput", 2), task("c", "running", 3), task("d", "starting", 4), task("e", "ready", 5), ]); expect(groups.map((g) => [g.status, g.tasks.map((t) => t.id)])).toEqual([ ["needsInput", ["b"]], ["running", ["d", "c"]], ["ready", ["e", "a"]], ]); }); it("upserts entries in place and refuses gaps", () => { const a: AgentEntry = { type: "agent", text: "a" }; const b: AgentEntry = { type: "agent", text: "b" }; expect(upsertEntry([a], 0, b)).toEqual([b]); expect(upsertEntry([a], 1, b)).toEqual([a, b]); expect(upsertEntry([a], 3, b)).toBeNull(); }); it("notifies on finish, failure and questions only", () => { expect(attentionChange("running", "ready")).toBe("finished"); expect(attentionChange("running", "failed")).toBe("failed"); expect(attentionChange("running", "limited")).toBe("failed"); expect(attentionChange("running", "needsInput")).toBe("input"); expect(attentionChange("needsInput", "running")).toBeNull(); expect(attentionChange("ready", "archived")).toBeNull(); expect(attentionChange(undefined, "ready")).toBeNull(); }); it("finds subagent cards and keys their conversations apart", () => { const card: SubagentEntry = { type: "subagent", id: "c1", title: null, description: "Find the lexer", status: "running", error: null, prompt: null, reply: null, tools: 0, activity: null, startedMs: 0, endedMs: null, cancellable: false, }; expect(findSubagent([{ type: "agent", text: "a" }, card], "c1")).toBe(card); expect(findSubagent(undefined, "c1")).toBeUndefined(); expect(subagentTitle(card)).toBe("Find the lexer"); expect(subagentTitle({ ...card, title: "Explore" })).toBe("Explore"); expect(entriesKey("t", null)).toBe("t"); expect(entriesKey("t", "c1")).not.toBe(entriesKey("t", null)); }); it("formats labels", () => { expect(branchLabel("agent/fix-lexer")).toBe("fix-lexer"); expect(branchLabel(null)).toBe(""); expect(age(0, 30_000)).toBe("now"); expect(age(0, 5 * 60_000)).toBe("5m"); expect(age(0, 3 * 3_600_000)).toBe("3h"); }); it("renders agent markdown without scripts", () => { const html = markdownHtml("**bold** "); expect(html).toContain("bold"); expect(html).not.toContain("onerror"); expect(html).not.toContain(" { const html = markdownHtml("- **parent** text:\n - child\n- plain"); expect(html).toContain("
  • parent text: