import { afterAll, beforeAll, describe, expect, it } from "bun:test"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import plugin from "../../.opencode/plugins/tjwater-tools.js"; const originalConnectionPath = process.env.TJWATER_AGENT_MCP_CONNECTION_PATH; const connectionRoot = mkdtempSync(join(tmpdir(), "call-id-plugin-")); const originalInternalToken = process.env.AGENT_INTERNAL_TOKEN; beforeAll(() => { process.env.TJWATER_AGENT_MCP_CONNECTION_PATH = join(connectionRoot, "absent.json"); process.env.AGENT_INTERNAL_TOKEN = "test-internal-token"; }); afterAll(() => { if (originalConnectionPath === undefined) delete process.env.TJWATER_AGENT_MCP_CONNECTION_PATH; else process.env.TJWATER_AGENT_MCP_CONNECTION_PATH = originalConnectionPath; rmSync(connectionRoot, { recursive: true, force: true }); if (originalInternalToken === undefined) delete process.env.AGENT_INTERNAL_TOKEN; else process.env.AGENT_INTERNAL_TOKEN = originalInternalToken; }); describe("OpenCode 2 business tool registration", () => { it("registers business tools for direct dispatch, forwards the native call ID and validates inputs", async () => { const tools = new Map(); const storage = new Map(); let rpcList: (() => Promise) | undefined; let readTodos: ((input: { session_id: string }) => Promise) | undefined; await plugin.setup({ tool: { transform: async (fn: (editor: unknown) => void) => fn({ list: () => Array.from(tools, ([id, tool]) => ({ ...tool, id })), add: (tool: any) => tools.set(tool.name, tool), remove: (name: string) => tools.delete(name), update: () => {} }), list: async () => Array.from(tools, ([id, tool]) => ({ ...tool, id })) }, mcp: { transform: async (fn: (editor: unknown) => void) => fn({ list: () => [], set: () => {} }), list: async () => ({ data: [] }), }, shell: { hook: async () => {} }, permission: { hook: async () => {} }, session: { hook: async () => {} }, rpc: { register: async (_definition: unknown, handlers: { list: () => Promise; read_todos: (input: { session_id: string }) => Promise }) => { rpcList = handlers.list; readTodos = handlers.read_todos; } }, storage: { set: async (key: string, value: unknown) => storage.set(key, value), get: async (key: string) => storage.get(key) }, } as never); expect(tools.get("present_view").options).toEqual({ codemode: false, permission: "present_view" }); for (const name of ["publish_task_progress", "publish_task_presentation"]) { expect(tools.get(name).options).toEqual({ codemode: false, permission: name }); } const render = tools.get("present_view"); await expect(render.execute({ reason: "test", command: { action: "render_scada_analysis", items: [{ device_id: "x", level: "wrong" }] } }, { sessionID: "s", id: "call-native" })).rejects.toThrow(); const todo = tools.get("todowrite"); const todos = [{ content: "check", status: "pending" }]; await todo.execute({ todos }, { sessionID: "s" }); expect(tools.has("todoread")).toBeFalse(); expect(await readTodos!({ session_id: "s" })).toEqual(todos); expect(await readTodos!({ session_id: "another" })).toEqual([]); const discovery = await rpcList!(); expect(JSON.parse(JSON.stringify(discovery))).toEqual(discovery); }); it("exports a single native plugin entrypoint", async () => { const module = await import("../../.opencode/plugins/tjwater-tools.js"); expect(Object.keys(module)).toEqual(["default"]); expect(module.default.id).toBe("tjwater.tools"); }); });