Official

claude

Claude Code agent provider: runs the Claude Code CLI headless for each account.

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/claude@0.2.0

Permissions in 0.2.0

  • Provide agents agents.provideMediumAdds agents to the app.Provide the Claude Code agent and pass its tool calls to plugin tools
  • Run named programs processMediumStarts the listed programs.Run the Claude Code CLI (sessions, sign-in, `claude update`), and ask the npm installation that owns it about a newer versionPrograms: claudenpm
  • Read files fs.readMediumReads files in the listed places.Read Claude Code's sessions, settings and skills (in ~/.claude, other accounts' ~/.claude-* folders, the shared skills and the folder you choose in Settings), the project's .claude folder, the administrator's skill policy, and once, what the previous provider keptPlaces: ~/.claude/**~/.claude*/**~/.agents/skills/**the open workspaceits own data folder/Library/Application Support/ClaudeCode/managed-settings.json/etc/claude-code/managed-settings.json${settings.configDir}
  • Environment variables envMediumReads the listed environment variables.Find Claude Code's configuration directory, and the launch settings you set in CLAUDE_* variables (extra arguments, MCP servers, setting sources, appended system prompt, extra folders)Variables: HOMECLAUDE_CONFIG_DIRCLAUDE_EXTRA_ARGSCLAUDE_MCP_CONFIGCLAUDE_STRICT_MCP_CONFIGCLAUDE_SETTING_SOURCESCLAUDE_APPEND_SYSTEM_PROMPTCLAUDE_ADD_DIRS

Files

records.ts5.2 KB
// Stored transcript records (`<session>.jsonl` and every subagent's file)
// mapped to transcript items (the record half of the Rust `history.rs`).
import * as wire from "./wire.ts";
import type { TranscriptItem } from "./types.ts";
import type { TaskInfo } from "convergence/protocol";
import { planFromTodos, resultContent, resultText, toolCall } from "./tools.ts";

/// A record's time as an ISO string, or `null`.
export function timestamp(record: wire.Record | undefined) {
  if (typeof record?.timestamp !== "string") return null;
  const time = Date.parse(record.timestamp);
  return Number.isNaN(time) ? null : new Date(time).toISOString();
}

/// The first text of a message's content: the string itself, or the first
/// text block.
export function firstText(content: unknown): string | null {
  if (typeof content === "string") return content;
  if (Array.isArray(content)) {
    const block = content.map(wire.objectOf).find((block) => block?.type === "text" && typeof block.text === "string");
    return block && typeof block.text === "string" ? block.text : null;
  }
  return null;
}

/// The marker the CLI records when the user stops a turn.
export function isInterruption(content: unknown) {
  let text = null;
  if (typeof content === "string") text = content;
  else if (Array.isArray(content) && content.length === 1 && typeof wire.objectOf(content[0]).text === "string")
    text = String(wire.objectOf(content[0]).text);
  return text !== null && text.startsWith("[Request interrupted by user");
}

type Body<T> = T extends { id: string } ? Omit<T, "id"> : never;
function item(id: string, createdAt: string | null, body: Body<TranscriptItem>) {
  const out: { id: string; createdAt?: string } = { id };
  if (createdAt) out.createdAt = createdAt;
  return Object.assign(out, body);
}

/// Maps transcript records to items. The main conversation and every
/// subagent's own file share this.
export function recordsToItems(records: wire.Record[]): TranscriptItem[] {
  const items: TranscriptItem[] = [];
  // Tool calls by id, so a `tool_result` updates the call it answers.
  const calls = new Map<string, number>();
  for (const record of records) {
    const createdAt = timestamp(record);
    const uuid = typeof record?.uuid === "string" ? record.uuid : "";
    const content = record?.message?.content;
    if (content === undefined) continue;
    if (record.type === "user") {
      // The CLI writes the end of a background task into the conversation
      // as a user message. Nobody typed it; the task item shows it.
      if (record.origin?.kind === "task-notification") continue;
      // Stop, as the CLI records it: shown the way a live stop is.
      if (isInterruption(content)) {
        items.push(item(uuid, createdAt, { role: "notice", text: "Stopped." }));
        continue;
      }
      const blocks: import("convergence/protocol").ContentBlock[] = [];
      for (const block of Array.isArray(content) ? content : []) {
        if (block?.type === "text") {
          blocks.push({ type: "text", text: typeof block.text === "string" ? block.text : "" });
        } else if (block?.type === "tool_result") {
          const at = calls.get(block.tool_use_id ?? "");
          if (at === undefined) continue;
          const entry = items[at];
          if (entry?.role !== "tool") continue;
          const call = entry.call;
          call.status = block.is_error === true ? "failed" : "completed";
          const content = resultContent(call.name, call.input ?? null, resultText(block.content), null);
          call.content = content;
        }
      }
      if (typeof content === "string") blocks.push({ type: "text", text: content });
      if (blocks.length) items.push(item(uuid, createdAt, { role: "user", blocks }));
    } else if (record.type === "assistant") {
      (Array.isArray(content) ? content : []).forEach((block, index) => {
        const id = `${uuid}:${index}`;
        if (block?.type === "text") {
          const text = typeof block.text === "string" ? block.text : "";
          if (text) items.push(item(id, createdAt, { role: "assistant", text }));
        } else if (block?.type === "thinking") {
          const text = typeof block.thinking === "string" ? block.thinking : "";
          if (text) items.push(item(id, createdAt, { role: "reasoning", text }));
        } else if (block?.type === "tool_use") {
          const callId = typeof block.id === "string" ? block.id : "";
          const name = typeof block.name === "string" ? block.name : "";
          const input = block.input ?? null;
          if (name === "TodoWrite") {
            const plan = planFromTodos(input);
            if (plan) items.push(item(`${id}:plan`, createdAt, { role: "plan", plan }));
          }
          calls.set(callId, items.length);
          items.push(item(id, createdAt, { role: "tool", call: toolCall(callId, name, input, "pending") }));
        }
      });
    }
  }
  return items;
}

/// A task item: the subagent's snapshot with its own items inside, under
/// the id the host gives a task it learns about live.
export function taskItem(info: TaskInfo, items: TranscriptItem[]): TranscriptItem {
  return item(`task-${info.id}`, info.startedAt ?? null, { role: "task", task: info, items });
}

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <3128.7 KB4 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.