Official
sidebar
Built-in sidebar 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/sidebar@0.2.0
Permissions in 0.2.0
Files
main.ts2.6 KB
// Register the sidebar synchronously, its commands, and the event
// subscriptions that keep its state current; the initial refresh runs in
// the plugin scope.
import * as Effect from "effect/Effect";
import * as Scope from "effect/Scope";
import * as Stream from "effect/Stream";
import { Commands, Events, Views, runPlugin } from "convergence/effect";
import { Sidebar } from "./controller.ts";
import { renderSidebar } from "./views.ts";
const program = Effect.gen(function* () {
const sidebar = yield* Sidebar;
const views = yield* Views;
// Views register synchronously at the start of the program so reload
// keeps the view's identity (same slot, same order).
sidebar.views.push(
yield* views.slot("left", (ctx) => renderSidebar(ctx, sidebar), {
on: ["workspaces_changed", "chat_changed", "chat_removed", "agents_changed", "app_state"],
}),
);
const commands = yield* Commands;
yield* commands.register("sidebar.newChat", sidebar.newChatCommand());
yield* commands.register("sidebar.toggleChatCompleted", sidebar.toggleCompletedCommand());
yield* commands.register("sidebar.addWorkspace", sidebar.addWorkspace());
for (let n = 1; n <= 9; n++) yield* commands.register(`sidebar.openChat${n}`, sidebar.selectListedCommand(n - 1));
const events = yield* Events;
// Event followers are fire-and-forget like the original's async handlers:
// a failed refresh leaves the state for the next event, and the rest of
// the plugin keeps working.
const follow = <A, E>(
stream: Stream.Stream<A, E>,
handle: (event: A) => Effect.Effect<void, unknown>,
): Effect.Effect<void, never, Scope.Scope> =>
stream.pipe(
Stream.mapEffect((event) => handle(event).pipe(Effect.ignore), { concurrency: "unbounded" }),
Stream.runDrain,
Effect.forkScoped,
);
yield* follow(events.stream("workspaces_changed"), () => sidebar.refresh());
yield* follow(events.stream("agents_changed"), () => sidebar.refresh());
yield* follow(events.stream("chat_changed"), (event) => sidebar.chatChanged(event));
yield* follow(events.stream("chat_removed"), (event) => sidebar.chatRemoved(event));
yield* follow(events.stream("transcript_reset"), (event) => sidebar.transcriptReset(event));
yield* follow(events.stream("agent"), (event) => sidebar.agentEvent(event));
// The first load runs in the program fiber, like the original's refresh()
// at the end of activate: its list calls leave before activate returns.
// A failed load keeps the previous state, and the next event retries.
yield* sidebar.refresh().pipe(Effect.ignore);
});
export const activate = runPlugin(Sidebar.layer, program);Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 20.2 KB | 3 permissions | Listed |
No comments yet.