Official
chat
Built-in chat UI (JavaScript UI plugin).
The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/chat@0.2.0
Permissions in 0.2.0
Files
subagents.test.ts5.5 KB
// Regression coverage for subagents using the real chat modules.
// Fixtures exercise the same behavior and assertions as the JavaScript tests.
import { item as fixtureItem, fixtureTask, taskItemAt, entryItems } from "./ui-for-tests.test.ts";
import assert from "node:assert/strict";
import test from "node:test";
import {
applyTask,
findTask,
withoutSpawnRows,
groupTasks,
allTasks,
counts,
countsLabel,
groupLabel,
elapsed,
tokens,
metaLine,
resultOf,
isLive,
} from "./subagents.ts";
const task = fixtureTask;
test("a subagent goes inside the one that started it, at any depth", () => {
const items: import("convergence").TranscriptItem[] = [];
applyTask(items, task("a"));
applyTask(items, task("b", { parentTaskId: "a" }));
applyTask(items, task("c", { parentTaskId: "b" }));
assert.equal(items.length, 1);
assert.equal(findTask(items, "c")?.task.id, "c");
assert.equal(findTask(items, "b")?.items?.[0].id, "task-c");
});
test("a later snapshot updates the task in place and keeps its items", () => {
const items: import("convergence").TranscriptItem[] = [];
applyTask(items, task("a"));
findTask(items, "a")?.items?.push(fixtureItem({ id: "m", role: "assistant", text: "hi" }));
applyTask(items, task("a", { status: "completed", summary: "done" }));
assert.equal(items.length, 1);
assert.equal(taskItemAt(items, 0).task.status, "completed");
assert.equal(taskItemAt(items, 0).items?.length, 1);
});
test("the tool row that started a subagent is hidden, other tools stay", () => {
const items = [
fixtureItem({ id: "t1", role: "tool", call: { id: "t1" } }),
fixtureItem({ id: "t2", role: "tool", call: { id: "t2" } }),
fixtureItem({ id: "task-x", role: "task", task: task("x", { toolCallId: "t1" }), items: [] }),
];
assert.deepEqual(
withoutSpawnRows(items).map((i) => i.id),
["t2", "task-x"],
);
});
test("two or more subagents in a row form a group; one alone does not", () => {
const t = (id: string) => ({
kind: "item" as const,
item: fixtureItem({ id, role: "task", task: task(id), items: [] }),
});
const text = { kind: "item" as const, item: fixtureItem({ id: "m", role: "assistant" }) };
const out = groupTasks([t("a"), t("b"), text, t("c")]);
assert.equal(out.length, 3);
assert.equal(out[0].kind, "tasks");
assert.deepEqual(
entryItems(out[0]).map((i) => i.id),
["a", "b"],
);
assert.equal(out[2].kind, "item");
});
test("a workflow keeps its own card: it never joins a group of subagents", () => {
const t = (id: string, extra: Parameters<typeof fixtureTask>[1] = {}) => ({
kind: "item" as const,
item: fixtureItem({ id, role: "task", task: task(id, extra), items: [] }),
});
const out = groupTasks([t("a"), t("wf", { workflow: { phases: [], logs: [] } }), t("b"), t("c")]);
assert.deepEqual(
out.map((entry) => entry.kind),
["item", "item", "tasks"],
);
assert.equal(out[1].kind === "item" ? out[1].item.id : "", "wf");
});
test("a queued agent is work in hand, counted apart from the running ones", () => {
const tasks = [task("a", { status: "queued" }), task("b"), task("c", { status: "queued" })];
assert.ok(isLive(tasks[0]));
assert.equal(countsLabel(counts(tasks)), "1 working · 2 queued");
});
test("the roster lists parents before their children with depth", () => {
const items: import("convergence").TranscriptItem[] = [];
applyTask(items, task("a"));
applyTask(items, task("b", { parentTaskId: "a" }));
applyTask(items, task("c"));
assert.deepEqual(
allTasks(items).map((r) => [r.task.id, r.depth]),
[
["a", 0],
["b", 1],
["c", 0],
],
);
});
test("counts and labels read like the t3code roster", () => {
const tasks = [
task("a"),
task("b", { status: "waiting" }),
task("c", { status: "completed" }),
task("d", { status: "failed" }),
];
assert.equal(countsLabel(counts(tasks)), "2 working · 1 completed · 1 failed");
assert.equal(groupLabel(tasks), "Kicked off 4 subagents");
assert.equal(groupLabel([task("c", { status: "completed" })]), "Ran 1 subagent");
});
test("elapsed time, tokens and the meta line", () => {
const start = "2026-09-23T10:00:00Z";
const now = Date.parse("2026-09-23T10:01:05Z");
assert.equal(elapsed(task("a", { startedAt: start }), now), "1m 05s");
assert.equal(elapsed(task("a", { startedAt: start, endedAt: "2026-09-23T10:00:42Z" }), now), "42s");
assert.equal(elapsed(task("a"), now), null);
assert.equal(tokens(850), "850 tok");
assert.equal(tokens(12_345), "12k tok");
assert.equal(tokens(1_500), "1.5k tok");
const meta = metaLine(
task("a", { model: "sonnet", effort: "high", startedAt: start, usage: { usedTokens: 1500 }, toolUses: 1 }),
now,
);
assert.equal(meta, "sonnet · high · 1m 05s · 1.5k tok · 1 tool");
});
test("a finished subagent shows what it handed back, once", () => {
const spawn = fixtureItem({
id: "c1",
role: "tool",
call: { id: "c1", content: [{ type: "text", text: "Found 3 files." }] },
});
const item = fixtureItem({
id: "task-x",
role: "task",
task: task("x", { status: "completed", toolCallId: "c1" }),
items: [],
});
assert.equal(resultOf(item, [spawn, item]), "Found 3 files.");
item.task.summary = "The summary";
assert.equal(resultOf(item, [spawn, item]), "The summary");
item.items?.push(fixtureItem({ id: "m", role: "assistant", text: "The summary" }));
assert.equal(resultOf(item, [spawn, item]), null, "the last message already says it");
item.task.status = "running";
assert.equal(resultOf({ ...item, items: [] }, [spawn, item]), null, "nothing while it runs");
});Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 105.1 KB | 5 permissions | Listed |
No comments yet.