Official

settings

Built-in settings 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/settings@0.2.0

Permissions in 0.2.0

  • Manage plugins plugins.manageOfficial onlyReloads and enables plugins. Only official plugins can have it.List, enable, disable and reload plugins, install updates, and quit to finish an update
  • Change app settings settings.writeHighChanges the app settings.Change the settings and the theme
  • Manage agents agents.manageHighEnables, disables and signs in agents, and changes their settings.Turn agents on and off, sign in to them and update them
  • Control chats chats.controlHighCreates chats, sends prompts and cancels runs.Import an agent's own sessions as chats
  • Read chats chats.readMediumReads your transcripts and chat lists.List the workspaces an agent's sessions can be imported into
  • Read app settings settings.readLowReads the app settings.Show the settings, and the tools and rules of every plugin
  • Show panels ui.slotsLowShows views in the listed parts of the window.Draw the settings pageSlots: center

Files

views/tools.ts4 KB
// The Tools section: the plugin tools every agent can be given, the mode
// they are shown in, and the rules plugins add to every session.
// HTM whitespace is significant; keep these templates out of Prettier's HTML formatter.
import { html as htm } from "convergence";
import type { EffectViewContext } from "convergence/effect";
import type { ToolsOverview } from "convergence";
import type { SettingsPage } from "../controller.ts";
import { card, listRow, pluginChip, section, segmented, setting, column, fill, row } from "./layout.ts";

function toggled(list: readonly string[], key: string, on: boolean) {
  return on ? list.filter((k) => k !== key) : [...list, key];
}

export function renderTools(ctx: EffectViewContext<object>, actions: SettingsPage["Service"]) {
  const state = actions.state;
  const t = state.tools;
  if (!t) return htm`<empty>This build of Divergence has no plugin tools.</empty>`;
  const settings = state.settings?.tools;
  const toolRow = (tool: ToolsOverview["tools"][number]) =>
    listRow(
      htm`<switch id=${`tool-${tool.plugin}-${tool.name}`} label="" checked=${tool.enabled}
        onChange=${(e: { value: boolean }) => ctx.run(actions.updateTools({ disabled: toggled(settings?.disabled ?? [], `${tool.plugin}/${tool.name}`, e.value) }))} />`,
      htm`
        <div ...${column} ...${fill} gap="small" padding="small">
          <div ...${row} gap="small" padding="small">
            <text style="mono" size="sm">${tool.name}</text>
            ${tool.exposedAs !== tool.name ? htm`<text style="body" size="xs" tone="faint">${`as ${tool.exposedAs}`}</text>` : null}
            <spacer />
            ${pluginChip(tool.plugin)}
          </div>
          ${tool.description ? htm`<text style="body" size="xs" tone="muted">${tool.description}</text>` : null}
        </div>`,
    );
  const ruleRow = (rule: ToolsOverview["rules"][number]) =>
    listRow(
      htm`<switch id=${`rules-${rule.plugin}-${rule.file}`} label="" checked=${rule.enabled}
        onChange=${(e: { value: boolean }) => ctx.run(actions.updateTools({ disabledRules: toggled(settings?.disabledRules ?? [], rule.plugin, e.value) }))} />`,
      htm`
        <div ...${column} ...${fill} gap="small" padding="small">
          <div ...${row} gap="small" padding="small">
            <text style="mono" size="sm">${rule.file}</text>
            <spacer />
            ${pluginChip(rule.plugin)}
          </div>
          <text style="body" size="xs" tone="muted" truncate>${rule.text.trim().split("\n")[0] ?? ""}</text>
        </div>`,
    );
  return htm`
      <div ...${column} gap="large" padding="small">
        ${section(
          "Plugin tools",
          "Tools that plugins give every agent. A chat gets the tools that are on when its session starts.",
          card([
            setting({
              title: "Show tools to agents as",
              description:
                t.mode === "code"
                  ? "One execute tool. The agent writes a short JavaScript program that calls the tools, so it can chain and filter their results."
                  : "Each plugin tool is a tool of its own.",
              control: segmented(
                "tool-mode",
                t.mode,
                [
                  { value: "code", label: "Code mode" },
                  { value: "direct", label: "Separate tools" },
                ],
                (value) => ctx.run(actions.updateTools({ mode: value })),
              ),
            }),
          ]),
        )}
        ${
          t.tools.length
            ? card(t.tools.map(toolRow))
            : htm`<text style="body" size="xs" tone="muted">No plugin gives tools yet. A plugin registers them with api.tools.register; see plugins/docs/tools.md.</text>`
        }
        ${section(
          "Rules",
          "Instructions plugins add to every agent session.",
          t.rules.length
            ? card(t.rules.map(ruleRow))
            : htm`<text style="body" size="xs" tone="muted">No plugin has rules. A plugin lists Markdown files in contributes.rules.</text>`,
        )}
      </div>`;
}

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <324.7 KB7 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.