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

views/title.ts3.4 KB
// Title rendering preserves the chat's retained tree and interaction keys.
import * as Effect from "effect/Effect";
import { renderSwitcher } from "./panes.ts";
import { workspacePicker } from "./composer.ts";
import { html } from "convergence";
import { allTasks, isLive } from "../subagents.ts";
import type { Element, Chat } from "convergence";
import type { ChatView } from "../controller.ts";
import { row, fill, titleChipAnchor } from "./styles.ts";
export function agentsButton(v: ChatView, chat: Chat): Element | null {
  const t = v.controller.state.transcripts[chat.id];
  const tasks = t ? allTasks(t.items).map((r) => r.task) : [];
  if (!tasks.length) return null;
  const working = tasks.filter(isLive).length;
  return html`
      <button id=${`agents-${chat.id}`} icon="network" label=${working ? `${working} working` : `${tasks.length}`} variant="ghost"
        tooltip="Subagents of this chat" onClick=${() => v.ctx.run(v.controller.openAgents(chat))} />`;
}

export function renderTitle(v: ChatView): Element | null {
  // The cycle still draws off the chat route: committing to a chat
  // brings the route back with it, and an open cycle must never be
  // left with no way to close it.
  if (v.ctx.app.route !== "chat") return v.controller.state.switcher ? renderSwitcher(v) : null;
  const chat = v.ctx.app.chatId ? v.controller.state.chats[v.ctx.app.chatId] : undefined;
  const agent = chat ? v.controller.state.agents[chat.agentId] : null;
  const can = agent?.capabilities;
  const compact = () => {
    if (chat)
      v.ctx.run(
        v.controller.host
          .call("host/chats.compact", { chatId: chat.id })
          .pipe(Effect.catch((error) => v.controller.notify.error(`Compact failed: ${error.message}`))),
      );
  };
  const fork = () => {
    if (chat)
      v.ctx.run(
        v.controller.host.call("host/chats.fork", { chatId: chat.id }).pipe(
          Effect.flatMap((copy) => v.controller.app.select(copy.id, copy.workspaceId)),
          Effect.catch((error) => v.controller.notify.error(`Fork failed: ${error.message}`)),
        ),
      );
  };
  // The switcher goes over the window, not in the strip: the overlay
  // takes no space here.
  return html`
      <div ...${row} ...${fill} gap="small" padding="small">
        <button id="title-new-chat" icon="plus" variant="ghost" tooltip="New chat (⌘N)" onClick=${() => v.ctx.run(v.controller.kernel.call("run_command", { id: "sidebar.newChat" }))} />
        <text style="heading" truncate text=${chat ? v.controller.titleOf(chat) : "Chat"} />
        ${chat && v.controller.isRunning(chat) ? html`<text style="body" size="sm" tone="muted">Running…</text>` : null}
        ${chat?.status === "failed" ? html`<text style="body" size="sm" tone="danger">Failed</text>` : null}
        <spacer />
        ${chat ? agentsButton(v, chat) : null}
        ${
          chat && can?.compact
            ? html`<button id=${`compact-${chat.id}`} icon="fold-vertical" variant="ghost" tooltip="Summarize the conversation to free context" onClick=${compact} />`
            : null
        }
        ${
          chat && can?.fork
            ? html`<button id=${`fork-${chat.id}`} icon="git-branch" variant="ghost" tooltip="Copy this chat and continue separately" onClick=${fork} />`
            : null
        }
        ${chat ? workspacePicker(v, chat, "title", titleChipAnchor) : null}
        ${renderSwitcher(v)}
      </div>`;
}

Versions

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

Reviews and comments

0 threads · 0 reviews

No comments yet.