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
grouping.test.ts3.3 KB
// Regression coverage for grouping using the real chat modules.
// Fixtures exercise the same behavior and assertions as the JavaScript tests.
import { item as fixtureItem, entryItems } from "./ui-for-tests.test.ts";
import assert from "node:assert/strict";
import test from "node:test";
import { foldItems, groupTitle, editStats } from "./grouping.ts";
const tool = (id: string, kind: string, content: import("convergence").ToolContent[] = []) =>
fixtureItem({ id, role: "tool", call: { id, kind, content } });
const flatten = (groups: import("./grouping.ts").FoldEntry[]) =>
groups.flatMap((entry) => (entry.kind === "item" ? [entry.item] : entry.items));
test("all tool types and leading or intervening reasoning form one consecutive group", () => {
const items = [
fixtureItem({ id: "thought", role: "reasoning" }),
...[
"read",
"edit",
"search",
"execute",
"delete",
"move",
"fetch",
"think",
"task",
"switch_mode",
"other",
"future_kind",
].flatMap((kind) => [tool(kind, kind), fixtureItem({ id: `thought-${kind}`, role: "reasoning" })]),
];
assert.deepEqual(foldItems(items), [{ kind: "tools", id: "tools-thought", items }]);
});
test("user and assistant messages each split groups and preserve chronological order", () => {
const items = [
tool("one", "read"),
fixtureItem({ id: "assistant", role: "assistant" }),
tool("two", "edit"),
fixtureItem({ id: "user", role: "user" }),
fixtureItem({ id: "thought", role: "reasoning" }),
tool("three", "execute"),
];
const groups = foldItems(items);
assert.deepEqual(
groups.map((entry) => (entry.kind === "item" ? entry.item.id : entry.id)),
["tools-one", "assistant", "tools-two", "user", "tools-thought"],
);
assert.deepEqual(flatten(groups), items);
assert.deepEqual(entryItems(groups[4]), items.slice(4));
});
test("compaction and other standalone entries are boundaries, not group contents", () => {
for (const role of ["compaction", "plan", "task", "notice"] as const) {
const items = [tool("one", "edit"), fixtureItem({ id: "boundary", role }), tool("two", "edit")];
const groups = foldItems(items);
assert.deepEqual(
groups.map((entry) => (entry.kind === "item" ? entry.item.id : entry.id)),
["tools-one", "boundary", "tools-two"],
);
assert.deepEqual(flatten(groups), items);
}
assert.deepEqual(foldItems([]), []);
const messages = [fixtureItem({ id: "u", role: "user" }), fixtureItem({ id: "a", role: "assistant" })];
assert.deepEqual(
foldItems(messages),
messages.map((item) => ({ kind: "item", item })),
);
});
test("streaming keeps group identity stable from reasoning through mixed tool calls", () => {
const items: import("convergence").TranscriptItem[] = [fixtureItem({ id: "reason", role: "reasoning" })];
const first = foldItems(items)[0];
assert.ok(first.kind === "tools");
const id = first.id;
assert.equal(groupTitle(items), "Reasoning");
items.push(
tool("first", "execute"),
tool("second", "edit", [{ type: "diff", path: "file", diff: "+new\n-old\n" }]),
tool("third", "edit"),
);
const group = foldItems(items)[0];
assert.equal(group.kind === "tools" ? group.id : "", id);
assert.equal(groupTitle(entryItems(group)), "1 Run, 2 Edits");
assert.deepEqual(editStats(entryItems(group)), { added: 1, removed: 1 });
});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.