Official

terminal

Built-in integrated terminal (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/terminal@0.2.0

Permissions in 0.2.0

Take care. This plugin asks for permissions that can do anything your account can. The app asks you to hold Enable for two seconds or to type the plugin's name before it turns on.
  • Terminals terminalDangerousCreates and controls terminals, which run a shell.Run shells in the integrated terminal
  • Read chats chats.readMediumReads your transcripts and chat lists.Start shells in the current workspace
  • Show panels ui.slotsLowShows views in the listed parts of the window.Show the terminal button and its windowSlots: railright

Files

state.test.ts2.5 KB
// Test workspace isolation, saved session selection, and terminal pane identity.
// Session removal must choose a neighbour in the same workspace, even with interleaved sessions.
import assert from "node:assert/strict";
import test from "node:test";

import type { Terminal, Workspace } from "convergence";
import { activeTerminal, forWorkspace, nextTerminal, pathsByWorkspace, terminalInPane } from "./state.ts";

const terminal = (id: string, workspaceId: string | null = "w1"): Terminal => ({
  id,
  title: id,
  userTitle: null,
  cwd: "/",
  workspaceId,
  running: true,
  exitCode: null,
});

test("terminals stay scoped to their workspace", () => {
  assert.deepEqual(
    forWorkspace([terminal("a"), terminal("b", "w2"), terminal("c")], "w1").map(({ id }) => id),
    ["a", "c"],
  );
});

test("active session uses its saved choice, then the last workspace session", () => {
  const terminals = [terminal("a"), terminal("b"), terminal("other", "w2")];
  assert.equal(activeTerminal({ terminals, activeByWorkspace: { w1: "a" } }, "w1")?.id, "a");
  assert.equal(activeTerminal({ terminals, activeByWorkspace: {} }, "w1")?.id, "b");
  assert.equal(activeTerminal({ terminals, activeByWorkspace: {} }, "missing"), null);
  assert.equal(activeTerminal({ terminals, activeByWorkspace: { w1: "other" } }, "w1")?.id, "b");
  assert.equal(activeTerminal({ terminals, activeByWorkspace: { w1: "deleted" } }, "w1")?.id, "b");
  assert.equal(activeTerminal({ terminals, activeByWorkspace: { w1: null } }, "w1")?.id, "b");
});

test("pane session and workspace paths are derived from host state", () => {
  assert.equal(terminalInPane({ kind: "terminal", path: "term-1" }), "term-1");
  assert.equal(terminalInPane({ kind: "file", path: "a.ts" }), null);
  assert.equal(terminalInPane(null), null);
  assert.equal(terminalInPane("nope"), null);
  const workspaces: Workspace[] = [{ id: "w1", name: "one", path: "/one", position: 0 }];
  assert.deepEqual(pathsByWorkspace(workspaces), { w1: "/one" });
});

test("closing chooses the next workspace session, then its previous one", () => {
  const first = terminal("first");
  const middle = terminal("middle");
  const last = terminal("last");
  const terminals = [first, terminal("other", "w2"), middle, last];
  assert.equal(nextTerminal(terminals, first)?.id, "middle");
  assert.equal(nextTerminal(terminals, middle)?.id, "last");
  assert.equal(nextTerminal(terminals, last)?.id, "middle");
  assert.equal(nextTerminal([first, terminal("other", "w2")], first), null);
});

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <38.7 KB3 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.