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
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
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 24.7 KB | 7 permissions | Listed |
No comments yet.