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

map.test.ts26.6 KB
// Ported from the Rust plugin's map.rs tests.
import * as wire from "./wire.ts";
import type * as z from "zod";
import assert from "node:assert/strict";
import { eventAs, contentAs, required, test } from "./testing.ts";
import type { Emission } from "./types.ts";
import { Mapper, failureMessage, usage } from "./map.ts";

const stream = (event: wire.StreamEvent) => ({ type: "stream_event", parent_tool_use_id: null, event });
const kinds = (events: Emission[]) => events.map((event) => event.kind);
const texts = (events: Emission[]) =>
  events
    .map((event) => event.kind)
    .filter((kind) => kind.event === "text_delta")
    .map((kind) => [kind.itemId, kind.text]);
const tasks = (events: Emission[]) => events.map((event) => event.kind).filter((kind) => kind.event === "task");

function agentCall(id: string, parent: string | null, input: wire.Input) {
  return {
    type: "assistant",
    parent_tool_use_id: parent,
    message: { id: `msg_${id}`, content: [{ type: "tool_use", id, name: "Agent", input }] },
  };
}

test("text deltas accumulate under one item id", () => {
  const mapper = new Mapper();
  mapper.handle(stream({ type: "message_start", message: { id: "msg_1" } }));
  assert.deepEqual(
    mapper.handle(stream({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } })),
    [],
  );
  const events = [];
  for (const chunk of ["Hello", " ", "world"]) {
    events.push(
      ...mapper.handle(stream({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: chunk } })),
    );
  }
  assert.deepEqual(texts(events), [
    ["msg_1:0", "Hello"],
    ["msg_1:0", " "],
    ["msg_1:0", "world"],
  ]);
  // The complete message must not repeat what the stream delivered.
  const repeat = mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: { id: "msg_1", content: [{ type: "text", text: "Hello world" }] },
  });
  assert.deepEqual(repeat, []);
});

test("reasoning streams as its own item", () => {
  const mapper = new Mapper();
  mapper.handle(stream({ type: "message_start", message: { id: "msg_2" } }));
  mapper.handle(stream({ type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }));
  const events = mapper.handle(
    stream({ type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "  step\n" } }),
  );
  assert.deepEqual(kinds(events), [{ event: "reasoning_delta", itemId: "msg_2:0", text: "  step\n", mode: "append" }]);
});

/// A subagent has no stream: each of its blocks arrives as a message of
/// its own, with the same id and the block at position 0.
test("a subagent message split into blocks keeps every block", () => {
  const mapper = new Mapper();
  mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: {
      id: "msg_p",
      content: [
        {
          type: "tool_use",
          id: "toolu_agent",
          name: "Agent",
          input: { description: "Count lines", prompt: "Count the lines of a.txt", subagent_type: "general-purpose" },
        },
      ],
    },
  });
  const child = (content: z.infer<typeof wire.block>) => ({
    type: "assistant",
    parent_tool_use_id: "toolu_agent",
    message: { id: "msg_c", model: "claude-haiku-4-5", content: [content] },
  });
  const events = [
    ...mapper.handle(child({ type: "thinking", thinking: "" })),
    ...mapper.handle(child({ type: "tool_use", id: "toolu_wc", name: "Bash", input: { command: "wc -l a.txt" } })),
    ...mapper.handle(child({ type: "text", text: "2" })),
  ];
  const tagged = events.filter((event) => event.task === "toolu_agent").map((event) => event.kind);
  assert.ok(tagged.some((kind) => kind.event === "tool_call_started" && kind.id === "toolu_wc"));
  assert.ok(tagged.some((kind) => kind.event === "text_delta" && kind.text === "2"));
  // The same message again (an older CLI repeats it whole) adds nothing.
  const again = mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_agent",
    message: {
      id: "msg_c",
      content: [
        { type: "thinking", thinking: "" },
        { type: "tool_use", id: "toolu_wc", name: "Bash", input: {} },
        { type: "text", text: "2" },
      ],
    },
  });
  assert.ok(!again.some((event) => ["tool_call_started", "text_delta"].includes(event.kind.event)));
});

test("the assistant message fills in blocks the stream missed", () => {
  const mapper = new Mapper();
  const events = mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: { id: "msg_3", model: "claude-opus-5", content: [{ type: "text", text: "PONG" }] },
  });
  assert.deepEqual(kinds(events), [{ event: "text_delta", itemId: "msg_3:0", text: "PONG", mode: "replace" }]);
  // The message named the model, which picks the context window.
  const result = mapper.handle({
    type: "result",
    usage: { input_tokens: 1 },
    modelUsage: { "claude-haiku-4-5": { contextWindow: 200000 }, "claude-opus-5": { contextWindow: 1000000 } },
  });
  assert.equal(eventAs(result[0].kind, "usage").contextWindow, 1000000);
});

test("a command tool call reports output and exit code", () => {
  const mapper = new Mapper();
  mapper.handle(stream({ type: "message_start", message: { id: "msg_4" } }));
  mapper.handle(
    stream({
      type: "content_block_start",
      index: 0,
      content_block: { type: "tool_use", id: "toolu_1", name: "Bash", input: {} },
    }),
  );
  for (const chunk of ['{"command":', '"ls -l"}']) {
    mapper.handle(
      stream({ type: "content_block_delta", index: 0, delta: { type: "input_json_delta", partial_json: chunk } }),
    );
  }
  const [started] = mapper.handle(stream({ type: "content_block_stop", index: 0 }));
  assert.equal(started.kind.event, "tool_call_started");
  assert.equal(started.kind.id, "toolu_1");
  assert.equal(started.kind.kind, "execute");
  assert.equal(started.kind.title, "ls -l");
  assert.equal(started.kind.status, "running");
  // The command is on show from the start.
  assert.deepEqual(started.kind.content, [{ type: "terminal", command: "ls -l", output: "" }]);
  const [finished] = mapper.handle({
    type: "user",
    tool_use_result: { exitCode: 0 },
    message: { content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "total 0\n" }] },
  });
  assert.equal(eventAs(finished.kind, "tool_call_updated").status, "completed");
  assert.deepEqual(
    eventAs(finished.kind, "tool_call_updated").output,
    { exitCode: 0 },
    "the structured result reaches the renderer",
  );
  assert.deepEqual(eventAs(finished.kind, "tool_call_updated").content, [
    { type: "terminal", command: "ls -l", output: "total 0\n", exitCode: 0 },
  ]);
});

test("a file edit carries a diff on start and on completion", () => {
  const mapper = new Mapper();
  const [started] = mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: {
      id: "msg_5",
      content: [
        {
          type: "tool_use",
          id: "toolu_2",
          name: "Edit",
          input: { file_path: "/w/a.rs", old_string: "let a = 1;\n", new_string: "let a = 2;\n" },
        },
      ],
    },
  });
  assert.equal(eventAs(started.kind, "tool_call_started").kind, "edit");
  assert.deepEqual(eventAs(started.kind, "tool_call_started").locations, [{ path: "/w/a.rs" }]);
  assert.match(
    required(contentAs(required(eventAs(started.kind, "tool_call_started").content)[0], "diff").diff),
    /\+let a = 2;/,
  );
  const [finished] = mapper.handle({
    type: "user",
    message: { content: [{ type: "tool_result", tool_use_id: "toolu_2", content: "ok" }] },
  });
  assert.equal(eventAs(finished.kind, "tool_call_updated").status, "completed");
  assert.equal(required(eventAs(finished.kind, "tool_call_updated").content)[0].type, "diff");
});

test("a failed tool result marks the call failed", () => {
  const mapper = new Mapper();
  mapper.rememberTool("toolu_3", "Read", { file_path: "/w/missing" });
  const [event] = mapper.handle({
    type: "user",
    message: {
      content: [
        {
          type: "tool_result",
          tool_use_id: "toolu_3",
          is_error: true,
          content: [{ type: "text", text: "no such file" }],
        },
      ],
    },
  });
  assert.equal(eventAs(event.kind, "tool_call_updated").status, "failed");
  assert.deepEqual(eventAs(event.kind, "tool_call_updated").content, [{ type: "text", text: "no such file" }]);
});

test("TodoWrite also emits a plan", () => {
  const mapper = new Mapper();
  const events = mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: {
      id: "msg_6",
      content: [
        {
          type: "tool_use",
          id: "toolu_4",
          name: "TodoWrite",
          input: {
            todos: [
              { content: "one", status: "in_progress" },
              { content: "two", status: "pending" },
            ],
          },
        },
      ],
    },
  });
  assert.equal(events[1].kind.event, "plan");
  assert.equal(required(eventAs(required(events[1]).kind, "plan").entries).length, 2);
  assert.equal(required(eventAs(required(events[1]).kind, "plan").entries)[0].status, "in_progress");
});

/// The exact frame claude 2.1.280 sends for an ordinary foreground Bash
/// call. Read as a subagent, it hid the command row behind a task row.
test("a foreground command is not a subagent", () => {
  const mapper = new Mapper();
  const [started] = mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: {
      id: "msg_b",
      content: [
        { type: "tool_use", id: "toolu_bash", name: "Bash", input: { command: "ls -la", description: "List files" } },
      ],
    },
  });
  assert.equal(eventAs(started.kind, "tool_call_started").kind, "execute");
  assert.deepEqual(
    mapper.handle({
      type: "system",
      subtype: "task_started",
      task_id: "b38o239g0",
      task_type: "local_bash",
      tool_use_id: "toolu_bash",
      description: "ls -la",
      is_backgrounded: false,
    }),
    [],
  );
  const finished = mapper.handle({
    type: "user",
    parent_tool_use_id: null,
    tool_use_result: { stdout: "a\n", exitCode: 0 },
    message: { content: [{ type: "tool_result", tool_use_id: "toolu_bash", content: "a\n" }] },
  });
  assert.equal(finished.length, 1);
  assert.equal(finished[0].task, null);
  assert.equal(eventAs(finished[0].kind, "tool_call_updated").id, "toolu_bash");
  assert.deepEqual(
    mapper.handle({
      type: "system",
      subtype: "task_notification",
      task_id: "b38o239g0",
      tool_use_id: "toolu_bash",
      status: "completed",
      summary: "ls -la",
    }),
    [],
  );
  assert.equal(mapper.taskOfAgent("b38o239g0", "toolu_bash"), null);
  assert.equal(mapper.cliTaskId("toolu_bash"), null);
});

test("a subagent becomes a task that owns its messages", () => {
  const mapper = new Mapper();
  mapper.handle(stream({ type: "message_start", message: { id: "msg_1", model: "claude-opus-5" } }));
  const [call] = mapper.handle(
    agentCall("toolu_a", null, {
      description: "Search the tests",
      prompt: "Find every test.\nThen stop.",
      subagent_type: "Explore",
    }),
  );
  assert.equal(eventAs(call.kind, "tool_call_started").kind, "task");

  const started = mapper.handle({
    type: "system",
    subtype: "task_started",
    task_id: "a1b2",
    tool_use_id: "toolu_a",
    description: "Search the tests",
    subagent_type: "Explore",
    task_type: "local_agent",
    is_backgrounded: false,
    spawn_depth: 1,
    prompt: "Find every test.\nThen stop.",
  });
  let info = tasks(started)[0];
  assert.equal(started[0].task, null, "the declaration belongs to the conversation");
  assert.equal(info.id, "toolu_a", "published under the spawning call, which every child message names");
  assert.equal(info.toolCallId, "toolu_a");
  assert.equal(info.title, "Search the tests");
  assert.equal(info.name, "Explore");
  assert.equal(info.prompt, "Find every test.\nThen stop.");
  assert.equal(info.status, "running");
  assert.ok(info.startedAt);
  assert.equal(info.parentTaskId, undefined);
  assert.equal(mapper.cliTaskId("toolu_a"), "a1b2");

  // The child's messages are tagged with the task, and its model is learnt
  // from the first one without becoming the session's model.
  const child = mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_a",
    message: {
      id: "msg_c1",
      model: "claude-haiku-4-5",
      content: [
        { type: "text", text: "looking" },
        { type: "tool_use", id: "toolu_grep", name: "Grep", input: { pattern: "fn test" } },
      ],
    },
  });
  assert.equal(tasks(child)[0].model, "claude-haiku-4-5");
  const tagged = child.filter((event) => event.kind.event !== "task");
  assert.equal(tagged.length, 2);
  assert.ok(tagged.every((event) => event.task === "toolu_a"));
  const result = mapper.handle({
    type: "user",
    parent_tool_use_id: "toolu_a",
    message: { content: [{ type: "tool_result", tool_use_id: "toolu_grep", content: "a.rs" }] },
  });
  assert.equal(result[0].task, "toolu_a");
  const usageEvents = mapper.handle({
    type: "result",
    usage: { input_tokens: 1 },
    modelUsage: { "claude-haiku-4-5": { contextWindow: 200000 }, "claude-opus-5": { contextWindow: 1000000 } },
  });
  assert.equal(eventAs(usageEvents[0].kind, "usage").contextWindow, 1000000);

  // Progress moves the activity line and the counters, never the title.
  const progress = mapper.handle({
    type: "system",
    subtype: "task_progress",
    task_id: "a1b2",
    tool_use_id: "toolu_a",
    description: "Running Grep",
    last_tool_name: "Grep",
    summary: "reading map.rs",
    usage: { total_tokens: 1200, tool_uses: 3, duration_ms: 400 },
  });
  assert.equal(progress.length, 1);
  info = tasks(progress)[0];
  assert.equal(info.title, "Search the tests");
  assert.equal(info.activity, "Grep");
  assert.equal(info.summary, "reading map.rs");
  assert.equal(required(info.usage).usedTokens, 1200);
  assert.equal(info.toolUses, 3);
  const paused = mapper.handle({
    type: "system",
    subtype: "task_updated",
    task_id: "a1b2",
    patch: { status: "paused", description: "Other" },
  });
  assert.equal(tasks(paused)[0].status, "waiting");
  assert.equal(tasks(paused)[0].title, "Search the tests");
  mapper.handle({ type: "system", subtype: "task_updated", task_id: "a1b2", patch: { status: "running" } });

  mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_a",
    message: { id: "msg_c2", content: [{ type: "text", text: "Found 3 tests." }] },
  });
  const done = mapper.handle({
    type: "system",
    subtype: "task_notification",
    task_id: "a1b2",
    tool_use_id: "toolu_a",
    status: "completed",
    summary: 'Agent "Search the tests" completed',
    usage: { total_tokens: 1500, tool_uses: 4, duration_ms: 900 },
  });
  info = tasks(done)[0];
  assert.equal(info.status, "completed");
  assert.equal(info.activity, undefined);
  assert.ok(info.endedAt);
  assert.equal(info.summary, "Found 3 tests.");
  assert.equal(required(info.usage).usedTokens, 1500);

  // The spawning call's result is the whole answer, and it stays.
  const answer = mapper.handle({
    type: "user",
    parent_tool_use_id: null,
    tool_use_result: {
      status: "completed",
      agentId: "a1b2",
      content: [{ type: "text", text: "Three tests: a, b, c." }],
      totalTokens: 1600,
      totalToolUseCount: 4,
    },
    message: {
      content: [
        {
          type: "tool_result",
          tool_use_id: "toolu_a",
          content: [
            { type: "text", text: "Three tests: a, b, c." },
            { type: "text", text: "agentId: a1b2" },
          ],
        },
      ],
    },
  });
  assert.equal(answer[0].kind.event, "tool_call_updated");
  assert.equal(answer[0].kind.id, "toolu_a");
  assert.equal(answer[0].task, null);
  info = tasks(answer)[0];
  assert.equal(info.summary, "Three tests: a, b, c.");
  assert.equal(required(info.usage).usedTokens, 1600);
  const later = mapper.handle({
    type: "system",
    subtype: "task_updated",
    task_id: "a1b2",
    patch: { status: "completed" },
  });
  assert.ok(tasks(later).every((info) => info.summary === "Three tests: a, b, c."));

  // A message after the end still lands in the task.
  const late = mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_a",
    message: { id: "msg_late", content: [{ type: "text", text: "leftover" }] },
  });
  assert.equal(late.length, 1);
  assert.equal(late[0].task, "toolu_a");
});

test("a nested subagent lives inside its parent", () => {
  const mapper = new Mapper();
  mapper.handle(agentCall("toolu_outer", null, { description: "Outer", prompt: "go" }));
  mapper.handle({
    type: "system",
    subtype: "task_started",
    task_id: "outer1",
    tool_use_id: "toolu_outer",
    description: "Outer",
    task_type: "local_agent",
    spawn_depth: 1,
  });
  // The inner call is made by the outer agent.
  const call = mapper.handle(agentCall("toolu_inner", "toolu_outer", { prompt: "\nInspect the parser\nand report" }));
  assert.equal(call[0].task, "toolu_outer");
  const started = mapper.handle({
    type: "system",
    subtype: "task_started",
    task_id: "inner1",
    tool_use_id: "toolu_inner",
    task_type: "local_agent",
    subagent_type: "general-purpose",
    spawn_depth: 2,
  });
  const info = tasks(started)[0];
  assert.equal(info.id, "toolu_inner");
  assert.equal(info.parentTaskId, "toolu_outer");
  assert.equal(info.title, "Inspect the parser", "the prompt's first line when there is no description");
  const text = mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_inner",
    message: { id: "msg_i", content: [{ type: "text", text: "deep" }] },
  });
  assert.equal(text[0].task, "toolu_inner");
  assert.equal(mapper.taskOfAgent("inner1", null), "toolu_inner");
});

/// A child can speak before `task_started`; it declares its task from the
/// call rather than landing in the main conversation.
test("a subagent that speaks first still gets a task", () => {
  const mapper = new Mapper();
  mapper.handle({
    type: "assistant",
    parent_tool_use_id: null,
    message: {
      id: "msg_1",
      content: [
        {
          type: "tool_use",
          id: "toolu_p",
          name: "Task",
          input: { description: "Search the tests", prompt: "find them", run_in_background: true },
        },
      ],
    },
  });
  const early = mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_p",
    message: { id: "msg_c", content: [{ type: "text", text: "looking" }] },
  });
  assert.equal(early[0].kind.event, "task");
  assert.equal(early[0].kind.id, "toolu_p");
  assert.equal(early[0].kind.title, "Search the tests");
  assert.equal(early[0].kind.background, true);
  assert.equal(early[0].task, null);
  assert.ok(early.slice(1).every((event) => event.task === "toolu_p"));
  const started = mapper.handle({
    type: "system",
    subtype: "task_started",
    task_id: "task_9",
    tool_use_id: "toolu_p",
    description: "Search the tests",
    subagent_type: "Explore",
    task_type: "local_agent",
    is_backgrounded: true,
  });
  assert.equal(eventAs(started[0].kind, "task").id, "toolu_p");
  assert.equal(eventAs(started[0].kind, "task").name, "Explore");
  // A background agent's result is what it said last; the frame's own
  // summary is a status line.
  mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_p",
    message: { id: "msg_d", content: [{ type: "text", text: "All found." }] },
  });
  const done = tasks(
    mapper.handle({
      type: "system",
      subtype: "task_notification",
      task_id: "task_9",
      status: "completed",
      summary: "Agent finished",
    }),
  )[0];
  assert.equal(done.status, "completed");
  assert.equal(done.summary, "All found.");
});

test("a stopped or killed task is cancelled", () => {
  for (const frame of [
    { type: "system", subtype: "task_updated", task_id: "t", patch: { status: "killed" } },
    { type: "system", subtype: "task_notification", task_id: "t", status: "stopped" },
  ]) {
    const mapper = new Mapper();
    mapper.handle({
      type: "system",
      subtype: "task_started",
      task_id: "t",
      tool_use_id: "tu",
      description: "d",
      task_type: "local_agent",
    });
    const info = tasks(mapper.handle(frame))[0];
    assert.equal(info.status, "cancelled");
    assert.equal(info.title, "d");
    // The Agent call then ends in an error; the subagent was still
    // stopped, not failed.
    const after = mapper.handle({
      type: "user",
      parent_tool_use_id: null,
      message: { content: [{ type: "tool_result", tool_use_id: "tu", is_error: true, content: "interrupted" }] },
    });
    for (const info of tasks(after)) assert.equal(info.status, "cancelled");
  }
});

test("housekeeping tasks are not announced", () => {
  const mapper = new Mapper();
  assert.deepEqual(
    mapper.handle({
      type: "system",
      subtype: "task_started",
      task_id: "t",
      description: "watch",
      skip_transcript: true,
      task_type: "local_agent",
    }),
    [],
  );
});

test("a subagent request is attributed to its task", () => {
  const mapper = new Mapper();
  mapper.handle(agentCall("toolu_a", null, { description: "Edit" }));
  mapper.handle({
    type: "system",
    subtype: "task_started",
    task_id: "agent7",
    tool_use_id: "toolu_a",
    task_type: "local_agent",
  });
  mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_a",
    message: { id: "m", content: [{ type: "tool_use", id: "toolu_w", name: "Write", input: { file_path: "/w/a" } }] },
  });
  assert.equal(mapper.taskOfAgent("agent7", "toolu_x"), "toolu_a");
  assert.equal(mapper.taskOfAgent(null, "toolu_w"), "toolu_a");
  assert.equal(mapper.taskOfAgent(null, "toolu_main"), null);
  assert.equal(tasks(mapper.ask("toolu_a"))[0].status, "waiting");
  assert.equal(tasks(mapper.answered("toolu_a"))[0].status, "running");
  const denied = mapper.handle({
    type: "system",
    subtype: "permission_denied",
    tool_name: "Write",
    agent_id: "agent7",
    tool_use_id: "toolu_w",
  });
  assert.equal(denied[0].task, "toolu_a");
  assert.deepEqual(denied[0].kind, { event: "notice", level: "warning", message: "Permission denied for Write." });
});

test("a status line for a tool becomes that tool's output", () => {
  const mapper = new Mapper();
  const events = mapper.handle({
    type: "system",
    subtype: "informational",
    level: "info",
    content: "Running 3 of 40 tests",
    tool_use_id: "toolu_9",
  });
  assert.deepEqual(events[0].kind, {
    event: "tool_call_updated",
    id: "toolu_9",
    outputDelta: "Running 3 of 40 tests\n",
  });
  // A repeated line would fill the panel with the same sentence.
  assert.deepEqual(
    mapper.handle({
      type: "system",
      subtype: "informational",
      content: "Running 3 of 40 tests",
      tool_use_id: "toolu_9",
    }),
    [],
  );
  // A line with no tool is a notice, its text under `content`.
  const notice = mapper.handle({
    type: "system",
    subtype: "informational",
    level: "warning",
    content: "A hook blocked the prompt",
  });
  assert.deepEqual(kinds(notice), [{ event: "notice", level: "warning", message: "A hook blocked the prompt" }]);
});

test("compaction reports the summary the CLI wrote", () => {
  const mapper = new Mapper();
  assert.deepEqual(
    mapper.handle({
      type: "user",
      isCompactSummary: true,
      message: { role: "user", content: [{ type: "text", text: "We fixed the parser." }] },
    }),
    [],
  );
  const events = mapper.handle({
    type: "system",
    subtype: "compact_boundary",
    compact_metadata: { trigger: "manual", pre_tokens: 180000, post_tokens: 20000 },
  });
  assert.deepEqual(kinds(events), [{ event: "compacted", summary: "We fixed the parser." }]);
  const failed = mapper.handle({
    type: "system",
    subtype: "status",
    status: "idle",
    compact_result: "failed",
    compact_error: "too large",
  });
  assert.deepEqual(kinds(failed), [{ event: "notice", level: "error", message: "Compaction failed: too large" }]);
});

test("a rate limit event becomes usage limits", () => {
  const mapper = new Mapper();
  const [event] = mapper.handle({
    type: "rate_limit_event",
    rate_limit_info: { status: "allowed", rateLimitType: "five_hour", utilization: 0.5, resetsAt: 1789000000 },
  });
  assert.equal(event.kind.event, "usage_limits");
  assert.equal(required(event.kind.windows)[0].id, "five_hour");
  assert.ok(Math.abs(required(event.kind.windows)[0].usedPercent - 50) < 1e-9);
});

test("the result reports the context of the last request", () => {
  // Numbers from a CLI 2.1 turn with two tool calls.
  const mapper = new Mapper();
  for (const [read, created] of [
    [13673, 9014],
    [22687, 2609],
    [25296, 142],
  ]) {
    mapper.handle({
      type: "assistant",
      parent_tool_use_id: null,
      message: {
        id: `msg_${read}`,
        model: "claude-haiku-4-5",
        content: [],
        usage: {
          input_tokens: 8,
          cache_read_input_tokens: read,
          cache_creation_input_tokens: created,
          output_tokens: 1,
        },
      },
    });
  }
  // A subagent's request is its own context, not the chat's.
  mapper.handle({
    type: "assistant",
    parent_tool_use_id: "toolu_agent",
    message: {
      id: "msg_child",
      model: "claude-haiku-4-5",
      content: [],
      usage: { input_tokens: 90000, output_tokens: 1 },
    },
  });
  const [event] = mapper.handle({
    type: "result",
    subtype: "success",
    usage: { input_tokens: 26, cache_read_input_tokens: 61656, cache_creation_input_tokens: 11765, output_tokens: 431 },
    modelUsage: { "claude-haiku-4-5": { contextWindow: 200000 } },
  });
  assert.equal(eventAs(event.kind, "usage").usedTokens, 8 + 25296 + 142 + 1);
  assert.equal(eventAs(event.kind, "usage").contextWindow, 200000);
});

test("the result reports usage and outcome", () => {
  const success = {
    type: "result",
    subtype: "success",
    is_error: false,
    total_cost_usd: 0.25,
    usage: {
      input_tokens: 2,
      output_tokens: 5,
      cache_read_input_tokens: 11,
      cache_creation_input_tokens: 12,
      output_tokens_details: { thinking_tokens: 3 },
    },
    modelUsage: { "claude-opus-5": { contextWindow: 1000000 }, "claude-haiku-4-5": { contextWindow: 200000 } },
  };
  // The session's own model decides the window.
  assert.deepEqual(usage(success, "claude-haiku-4-5", null), {
    usedTokens: 30,
    contextWindow: 200000,
    costUsd: 0.25,
    inputTokens: 2,
    cachedInputTokens: 23,
    outputTokens: 5,
    reasoningTokens: 3,
  });
  assert.equal(failureMessage(success), null);
  assert.equal(
    failureMessage({ type: "result", subtype: "error_during_execution", is_error: true, errors: ["boom"] }),
    "error_during_execution: boom",
  );
  assert.equal(
    failureMessage({ type: "result", subtype: "success", is_error: true, result: "credit balance too low" }),
    "success: credit balance too low",
  );
});

Versions

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

Reviews and comments

0 threads · 0 reviews

No comments yet.