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
models.test.ts5.3 KB
// Regression coverage for models using the real chat modules.
// Fixtures exercise the same behavior and assertions as the JavaScript tests.
import assert from "node:assert/strict";
import test from "node:test";
import * as Effect from "effect/Effect";
import type { Chat, ConfigOption } from "convergence";
import { sections, createModelPicker } from "./models.ts";
import { harness, tree as inspect, nodes, chat as makeChat } from "./ui-for-tests.test.ts";
assert.ok(typeof sections === "function");
const choices = [
{ value: "a/fast", name: "Fast", group: "Provider A" },
{ value: "a/deep", name: "Deep", group: "Provider A" },
{ value: "b/deep", name: "Deep", group: "Provider B" },
{ value: "local", name: "Local" },
];
test("favorites are unique, provider-qualified, and leave catalog order intact", () => {
const result = sections(choices, ["b/deep", "removed"], "");
assert.deepEqual(
result.map((group) => [group.name, group.choices.map((choice) => choice.value)]),
[
["Favorites", ["b/deep"]],
["Provider A", ["a/fast", "a/deep"]],
["", ["local"]],
],
);
});
test("search matches names, ids, and providers together, ignoring case and whitespace", () => {
assert.deepEqual(sections(choices, [], " DEEP provider B ")[0].choices, [choices[2]]);
assert.deepEqual(sections(choices, [], "a/fast")[0].choices, [choices[0]]);
assert.deepEqual(sections(choices, [], "missing"), []);
assert.equal(sections(choices, [], "Local")[0].name, "");
});
test("ungrouped models stay flat, with only favorites getting a heading", () => {
const flat = [
{ value: "default", name: "Default", group: undefined },
{ value: "fast", name: "Fast" },
];
assert.deepEqual(sections(flat, [], ""), [{ name: "", choices: flat }]);
assert.deepEqual(sections(flat, ["fast"], ""), [
{ name: "Favorites", favorite: true, choices: [flat[1]] },
{ name: "", choices: [flat[0]] },
]);
});
test("the settings menu lists each setting with its value, the models grouped with favorites first", async () => {
const h = harness(),
selections: string[][] = [];
h.controller.setOption = (...args) =>
Effect.sync(() => {
selections.push(args);
});
const chat = makeChat({ id: "chat", agentId: "one" });
const model: ConfigOption = { id: "model", name: "Model", kind: "select", category: "model", value: "a/fast", choices };
const effort: ConfigOption = {
id: "effort",
name: "Effort",
kind: "select",
category: "reasoning",
value: "high",
choices: [
{ value: "low", name: "Low", description: "Quick answers" },
{ value: "high", name: "High" },
],
};
const speed: ConfigOption = {
id: "speed",
name: "Speed",
kind: "select",
category: "other",
value: "standard",
choices: [{ value: "standard", name: "Standard" }],
};
const open = async () => {
const picker = createModelPicker(h.controller);
await Effect.runPromise(picker.load);
return (chat: Chat) => inspect(picker.render(h.v, chat, { ...model, choices: model.choices?.map((c) => ({ ...c, description: "About it" })) }, [effort, speed]));
};
type Menu = {
type: string;
label: string;
detail: string;
options: unknown;
onStar: (e: { value: string; option?: string }) => void;
onChange: (e: { value: string; option?: string }) => void;
};
let menu = (await open())(chat) as unknown as Menu;
assert.equal(menu.type, "combobox");
// The trigger names the model and, fainter, its effort only.
assert.equal(menu.label, "Fast");
assert.equal(menu.detail, "High");
const rows = menu.options as { value: string; label: string; detail: string; submenu: { value: string; options: { value: string; group?: string; starred?: boolean; description?: string }[] } }[];
assert.deepEqual(
rows.map((row) => [row.label, row.detail, row.submenu.value]),
[
["Model", "Fast", "a/fast"],
["Effort", "High", "high"],
["Speed", "Standard", "standard"],
],
);
assert.deepEqual(
rows[0].submenu.options.map((o) => [o.value, o.group, o.starred]),
[
["a/fast", "Provider A", false],
["a/deep", "Provider A", false],
["b/deep", "Provider B", false],
["local", undefined, false],
],
);
// Choices are names only: their descriptions only took room.
assert.ok(rows.every((row) => row.submenu.options.every((o) => o.description === undefined)));
// A star saves the favorite without choosing the model; the starred one
// moves to its own group on top.
menu.onStar({ value: "b/deep", option: "model" });
await new Promise(setImmediate);
assert.equal(selections.length, 0);
assert.deepEqual(h.stored.modelFavorites, { one: ["b/deep"] });
menu = (await open())(chat) as unknown as Menu;
const models = (menu.options as typeof rows)[0].submenu.options;
assert.deepEqual([models[0].value, models[0].group, models[0].starred], ["b/deep", "Favorites", true]);
// Favorites are the agent's own.
const other = (await open())({ ...chat, agentId: "two" });
assert.ok((other.options as typeof rows)[0].submenu.options.every((o) => !o.starred));
// A choice in a submenu sets that setting and gives the prompt the keyboard.
menu.onChange({ value: "low", option: "effort" });
await h.settle();
assert.deepEqual(selections, [["chat", "effort", "low"]]);
assert.ok(h.focused.at(-1)?.startsWith("prompt-"));
});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.