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

  • Control chats chats.controlHighCreates chats, sends prompts and cancels runs.Send prompts, answer agent questions, and create, change, fork, revert, stop and group chats
  • Run other plugins' commands commands.runHighRuns commands of other plugins.Run the sidebar's New chat command from the title bar
  • Read chats chats.readMediumReads your transcripts and chat lists.Show workspaces, tasks, chats and transcripts, and follow them as agents work
  • Read files fs.readMediumReads files in the listed places.Suggest workspace files for @ mentionsPlaces: the open workspace
  • Show panels ui.slotsLowShows views in the listed parts of the window.Draw the chat grid and the title barSlots: centertitle

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

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <3105.1 KB5 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.