Official
acp
ACP agents from the ACP registry (Gemini, Cursor, Droid, Kilo, pi, ...), Oh My Pi, and your own entries (custom.json in the plugin's data folder). Agents are discovered at runtime.
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/acp@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.
Files
throttle.test.ts3.5 KB
// Ported from the Rust plugin's throttle.rs tests.
import { test } from "node:test";
import assert from "node:assert/strict";
import type { ToolCallUpdate, ToolContent } from "convergence/protocol";
import { MAX_CHARS, ToolStream, bound } from "./throttle.ts";
const textUpdate = (text: string): ToolCallUpdate => ({
id: "t1",
status: "running",
content: [{ type: "text", text }],
});
test("long output keeps its newest part", () => {
const content = [{ type: "text", text: `${"x".repeat(MAX_CHARS * 2)}TAIL` }] satisfies ToolContent[];
bound(content);
assert.ok(content[0].text.endsWith("TAIL"), "the newest output was dropped");
assert.ok(content[0].text.startsWith("[Earlier output truncated]"));
assert.ok([...content[0].text].length <= MAX_CHARS + 64);
});
test("terminal output is capped, a diff is not", () => {
const long = "y".repeat(MAX_CHARS + 10);
const content = [
{ type: "terminal", command: "ls", output: long },
{ type: "diff", path: "/a", newText: long },
] satisfies ToolContent[];
const [terminal, diff] = content;
assert.ok(terminal);
assert.ok(diff);
bound(content);
assert.ok(terminal.output);
assert.ok(terminal.output.length < long.length + 40 && terminal.output.startsWith("[Earlier"));
assert.equal(diff.newText, long);
});
test("short output is untouched", () => {
const content = [{ type: "text", text: "hello" }] satisfies ToolContent[];
bound(content);
assert.deepEqual(content, [{ type: "text", text: "hello" }]);
});
// A tool that resends its whole output on every tick must not produce one
// event per tick.
test("small repeated updates are coalesced", () => {
const stream = new ToolStream();
assert.ok(stream.push(textUpdate("start")), "the first update always goes out");
let emitted = 0;
for (let index = 0; index < 9; index += 1) if (stream.push(textUpdate(`start${"y".repeat(index)}`))) emitted += 1;
assert.equal(emitted, 0, "small growth should have been held back");
// The tenth withheld update is released even without growth.
assert.ok(stream.push(textUpdate("start-again")));
});
test("real growth goes out at once", () => {
const stream = new ToolStream();
assert.ok(stream.push(textUpdate("start")));
assert.ok(stream.push(textUpdate("z".repeat(600))), "a big chunk must not wait");
});
test("a finished call is always reported", () => {
const stream = new ToolStream();
assert.ok(stream.push(textUpdate("start")));
assert.ok(stream.push({ id: "t1", status: "completed" }));
});
// Whatever is withheld must survive: the merged patch carries the fields of
// every update it stands for.
test("withheld updates are merged, not lost", () => {
const stream = new ToolStream();
assert.ok(stream.push(textUpdate("start")));
stream.push({ id: "t1", outputDelta: "a" });
stream.push({ id: "t1", outputDelta: "b" });
const flushed = stream.flush();
assert.ok(flushed);
// Both deltas survive the wait, in order.
assert.equal(flushed.outputDelta, "ab");
assert.equal(stream.flush(), null);
});
// A field set by a withheld update must reach the host in the patch that
// finally goes out.
test("a withheld field reaches the emitted patch", () => {
const stream = new ToolStream();
assert.ok(stream.push(textUpdate("start")));
// Held back: too little growth to go out on its own.
assert.equal(stream.push({ id: "t1", locations: [{ path: "/w/a.rs", line: 3 }] }), null);
const done = stream.push({ id: "t1", status: "completed" });
assert.ok(done?.locations);
assert.equal(done.status, "completed");
assert.equal(done.locations.length, 1, "the held location was lost");
});Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 94.9 KB | 6 permissions | Listed |
No comments yet.