Reuse OpenCode 2 HTTP/stdio clients with per-session read-only grants, native approval and sanitized owner-scoped MCP status. Narrow the environment inherited by trusted local MCP processes. Validated in an isolated checkout with bun run check and 20 focused tests.
135 lines
5.0 KiB
TypeScript
135 lines
5.0 KiB
TypeScript
import query_project_data from "../tools/query_project_data.js";
|
|
import web_search from "../tools/web_search.js";
|
|
import geocode from "../tools/geocode.js";
|
|
import final_answer from "../tools/final_answer.js";
|
|
import locate_features from "../tools/locate_features.js";
|
|
import zoom_to_map from "../tools/zoom_to_map.js";
|
|
import show_chart from "../tools/show_chart.js";
|
|
import view_history from "../tools/view_history.js";
|
|
import { Plugin, Rpc } from "@opencode/plugin";
|
|
import { z } from "zod";
|
|
import { resolve } from "node:path";
|
|
import { registerSessionSandbox } from "../session-sandbox.js";
|
|
import { registerMcpPolicy } from "../mcp-policy.js";
|
|
|
|
import add_task_human_evidence from "../tools/add_task_human_evidence.js";
|
|
import clear_scada_analysis from "../tools/clear_scada_analysis.js";
|
|
import get_operational_task_context from "../tools/get_operational_task_context.js";
|
|
import memory_manager from "../tools/memory_manager.js";
|
|
import propose_task_action from "../tools/propose_task_action.js";
|
|
import publish_execution_plan from "../tools/publish_execution_plan.js";
|
|
import publish_task_report from "../tools/publish_task_report.js";
|
|
import query_operational_task_data from "../tools/query_operational_task_data.js";
|
|
import render_scada_analysis from "../tools/render_scada_analysis.js";
|
|
import request_task_data_access from "../tools/request_task_data_access.js";
|
|
import session_search from "../tools/session_search.js";
|
|
import skill_manager from "../tools/skill_manager.js";
|
|
|
|
const businessTools = {
|
|
web_search,
|
|
geocode,
|
|
query_project_data,
|
|
final_answer,
|
|
locate_features,
|
|
zoom_to_map,
|
|
show_chart,
|
|
view_history,
|
|
add_task_human_evidence,
|
|
clear_scada_analysis,
|
|
get_operational_task_context,
|
|
memory_manager,
|
|
propose_task_action,
|
|
publish_execution_plan,
|
|
publish_task_report,
|
|
query_operational_task_data,
|
|
render_scada_analysis,
|
|
request_task_data_access,
|
|
session_search,
|
|
skill_manager,
|
|
};
|
|
|
|
export default Plugin.define({
|
|
id: "tjwater.tools",
|
|
async setup(ctx) {
|
|
await registerSessionSandbox(ctx);
|
|
await ctx.tool.transform((editor) => {
|
|
for (const [name, definition] of Object.entries(businessTools)) {
|
|
const input = z.object(definition.args);
|
|
editor.add({
|
|
name,
|
|
description: definition.description,
|
|
input,
|
|
options: { codemode: false, permission: name },
|
|
async execute(args, context) {
|
|
// Validate again at the execution boundary, retaining domain refinements.
|
|
const parsed = input.parse(args);
|
|
const content = await definition.execute(parsed as never, {
|
|
sessionID: context.sessionID,
|
|
callID: context.id,
|
|
signal: context.signal,
|
|
});
|
|
return { content };
|
|
},
|
|
});
|
|
}
|
|
});
|
|
const todoItem = z.object({
|
|
id: z.string().optional(),
|
|
content: z.string(),
|
|
status: z.enum(["pending", "in_progress", "completed", "cancelled"]),
|
|
priority: z.enum(["low", "medium", "high"]).optional(),
|
|
});
|
|
await ctx.tool.transform((editor) => {
|
|
editor.add({
|
|
name: "todowrite",
|
|
description: "Update the current session's task checklist.",
|
|
input: z.object({ todos: z.array(todoItem) }),
|
|
options: { codemode: false, permission: "todowrite" },
|
|
async execute({ todos }, context) {
|
|
await ctx.storage.set(`todos/${context.sessionID}`, todos);
|
|
return { content: JSON.stringify(todos) };
|
|
},
|
|
});
|
|
editor.add({
|
|
name: "todoread",
|
|
description: "Read the current session's task checklist.",
|
|
input: z.object({}),
|
|
options: { codemode: false, permission: "todoread" },
|
|
async execute(_input, context) {
|
|
return { content: JSON.stringify(await ctx.storage.get(`todos/${context.sessionID}`) ?? []) };
|
|
},
|
|
});
|
|
});
|
|
const mcpReport = await registerMcpPolicy(ctx);
|
|
await ctx.rpc.register(Rpc.define({
|
|
id: "tjwater.tools",
|
|
methods: {
|
|
list: {
|
|
input: z.object({}),
|
|
output: z.array(z.object({ id: z.string(), direct: z.boolean(), sandboxed: z.boolean(), workspaceRoot: z.string().optional() })),
|
|
},
|
|
mcp: {
|
|
input: z.object({}),
|
|
output: z.object({
|
|
configuration_errors: z.array(z.string()),
|
|
servers: z.array(z.object({
|
|
name: z.string(), type: z.enum(["local", "remote"]),
|
|
status: z.enum(["connected", "pending", "disabled", "failed", "needs_auth"]),
|
|
configured_protocol: z.enum(["legacy", "auto", "2026-07-28"]), tools: z.array(z.string()),
|
|
})),
|
|
}),
|
|
},
|
|
},
|
|
events: {},
|
|
}), {
|
|
mcp: mcpReport,
|
|
list: async () => (await ctx.tool.list()).map((tool) => ({
|
|
id: tool.id,
|
|
direct: tool.options?.codemode === false,
|
|
sandboxed: tool.id === "shell" && tool.description.includes("Landlock + seccomp"),
|
|
...(tool.id === "shell" ? { workspaceRoot: resolve(process.env.TJWATER_AGENT_WORKSPACE_ROOT ?? ".agent-workspaces") } : {}),
|
|
})),
|
|
});
|
|
},
|
|
});
|