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

workflows.test.ts5.3 KB
// Regression coverage for workflows using the real chat modules.
// Fixtures exercise the same behavior and assertions as the JavaScript tests.
import { item as fixtureItem, fixtureTask } from "./ui-for-tests.test.ts";
import assert from "node:assert/strict";
import test from "node:test";

import {
  isWorkflow,
  agentsOf,
  phasesOf,
  currentPhase,
  displayStatus,
  stripOf,
  progressLabel,
  phaseDetail,
  phaseOpen,
  resultView,
  modelName,
  keywordsIn,
} from "./workflows.ts";

const agent = (
  index: number,
  phase: string,
  status: import("convergence").TaskStatus,
  extra: Parameters<typeof fixtureTask>[1] = {},
) =>
  fixtureItem({
    id: `task-wf/${index}`,
    role: "task",
    task: { id: `wf/${index}`, title: `agent ${index}`, status, phase, parentTaskId: "wf", ...extra },
    items: [],
  });

const run = (
  status: import("convergence").TaskStatus,
  agents: import("./grouping.ts").TaskItem[],
  phases = ["Find", "Verify"],
) =>
  fixtureItem({
    id: "task-wf",
    role: "task",
    task: {
      id: "wf",
      title: "Review the branch",
      status,
      workflow: { logs: [], phases: phases.map((title) => ({ title })) },
    },
    items: [fixtureItem({ id: "a", role: "assistant", text: "not an agent" }), ...agents],
  });

test("a workflow's agents sit in the phases the script declared, in order", () => {
  const item = run("running", [
    agent(1, "Find", "completed"),
    agent(2, "Find", "running"),
    agent(3, "Extra", "queued"),
  ]);
  assert.ok(isWorkflow(item.task));
  assert.equal(agentsOf(item).length, 3);
  const phases = phasesOf(item);
  assert.deepEqual(
    phases.map((phase) => phase.title),
    ["Find", "Verify", "Extra"],
    "an undeclared phase goes last",
  );
  assert.deepEqual(
    phases.map((phase) => phase.state),
    ["running", "pending", "queued"],
  );
  assert.equal(phases[0].tally.done, 1);
  assert.equal(currentPhase(phases)?.title, "Find");
});

test("the phase a run has reached is the last one an agent started in", () => {
  const item = run("running", [agent(1, "Find", "completed"), agent(2, "Verify", "running")]);
  const phases = phasesOf(item);
  assert.equal(currentPhase(phases)?.title, "Verify");
  assert.equal(progressLabel(item, phases), "Phase 2 of 2 · Verify · 1/2 agents");
  const done = run("completed", [agent(1, "Find", "completed"), agent(2, "Verify", "failed")]);
  assert.equal(
    progressLabel(done, phasesOf(done)),
    "Phase 2 of 2 · 1/2 agents · 1 failed",
    "no phase name once it ended",
  );
});

test("an agent that needs the user makes the whole run need the user", () => {
  assert.equal(displayStatus(run("running", [agent(1, "Find", "waiting")])), "waiting");
  assert.equal(displayStatus(run("running", [agent(1, "Find", "running")])), "running");
  assert.equal(
    displayStatus(run("cancelled", [agent(1, "Find", "waiting")])),
    "cancelled",
    "an ended run says how it ended",
  );
});

test("the strip has a segment per agent and a placeholder per phase not reached", () => {
  const strip = stripOf(phasesOf(run("running", [agent(1, "Find", "completed"), agent(2, "Find", "running")])));
  assert.equal(strip.length, 2);
  assert.deepEqual(
    strip[0].segments.map((segment) => segment.tone),
    ["success", "primary"],
  );
  assert.equal(strip[1].segments.length, 1);
  assert.equal(strip[1].segments[0].surface, "muted");
});

test("phase rows say what they wait for and open while something happens", () => {
  const phases = phasesOf(
    run("running", [
      agent(1, "Find", "completed", { usage: { usedTokens: 12_000 } }),
      agent(2, "Find", "waiting"),
      agent(3, "Find", "queued"),
    ]),
  );
  assert.equal(phaseDetail(phases[0]), "1 needs you · 1 queued · 1/3 · 12k tok");
  assert.equal(phaseDetail(phases[1]), "Not started");
  assert.equal(phaseOpen(phases[0]), true);
  assert.equal(phaseOpen(phases[1]), false);
});

test("a result is text when the script returned text, and JSON otherwise", () => {
  assert.deepEqual(resultView("All good."), { kind: "text", text: "All good." });
  assert.deepEqual(resultView({ a: 1 }), { kind: "json", text: '{\n  "a": 1\n}' });
  assert.equal(resultView(null), null);
  assert.equal(resultView("  "), null);
});

test("a model id reads as the agent's name for it", () => {
  const option = {
    choices: [
      { value: "claude-haiku-4-5", name: "Haiku 4.5", aliases: ["haiku"] },
      { value: "claude-opus-5-5[1m]", name: "Opus 5.5" },
    ],
  };
  assert.equal(modelName(option, "claude-haiku-4-5-20251001"), "Haiku 4.5");
  assert.equal(modelName(option, "haiku"), "Haiku 4.5");
  assert.equal(modelName(option, "claude-opus-5-5"), "Opus 5.5");
  assert.equal(modelName(option, "gpt-9"), "gpt-9");
  assert.equal(modelName(undefined, null), null);
});

test("a keyword counts only as a word of its own", () => {
  const keywords = [{ word: "ultracode", label: "Ultracode" }];
  assert.equal(keywordsIn("ultracode: review the branch", keywords).length, 1);
  assert.equal(keywordsIn("Please ULTRACODE this", keywords).length, 1);
  assert.equal(keywordsIn("(ultracode)", keywords).length, 1);
  for (const text of ["/ultracode", "--ultracode", "ultracode.ts", "ultracoder", "src/ultracode/x", "my-ultracode"]) {
    assert.equal(keywordsIn(text, keywords).length, 0, text);
  }
  assert.deepEqual(keywordsIn("", keywords), []);
  assert.deepEqual(keywordsIn("ultracode", []), []);
});

Versions

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

Reviews and comments

0 threads · 0 reviews

No comments yet.