Files
next-tjwater-agent/.opencode/plugins/tjwater-tools.ts
T
jiang 6a49d2209e feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context,
while retaining memory, skills, actions and publication plugins. Add nearby-model
queries and explicit chat/task/learning model defaults.

Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped
session ownership, pending input edits and investigation communication.

Disable local-provider discovery: Server /health allowed native vLLM to probe
/v1/models on port 8000. A native before/after regression prevents recurrence.

Validation: 170 focused Bun tests; service/plugin typechecks; native discovery,
provider-discovery and input-inbox smoke checks; no paid model inference.
2026-10-19 18:46:58 +08:00

146 lines
6.2 KiB
TypeScript

import final_answer from "../tools/final_answer.js";
import present_answer from "../tools/present_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 { registerTaskAgentPolicy } from "../task-agent-policy.js";
import { AGENT_MESSAGE_TOOLS, registerAgentMessagePolicy } from "../agent-message-policy.js";
import { checkOpencodeModels } from "../model-check.js";
import { DEFAULT_SESSION_WORKSPACE_ROOT } from "../../src/runtime/conversationWorkspace.js";
import add_task_human_evidence from "../tools/add_task_human_evidence.js";
import clear_scada_analysis from "../tools/clear_scada_analysis.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 publish_task_progress from "../tools/publish_task_progress.js";
import publish_task_presentation from "../tools/publish_task_presentation.js";
import render_scada_analysis from "../tools/render_scada_analysis.js";
import request_task_data_access from "../tools/request_task_data_access.js";
import skill_manager from "../tools/skill_manager.js";
import send_task_message from "../tools/send_task_message.js";
import reply_agent_message from "../tools/reply_agent_message.js";
const businessTools = {
final_answer,
present_answer,
locate_features,
zoom_to_map,
show_chart,
view_history,
add_task_human_evidence,
clear_scada_analysis,
memory_manager,
propose_task_action,
publish_execution_plan,
publish_task_report,
publish_task_progress,
publish_task_presentation,
render_scada_analysis,
request_task_data_access,
skill_manager,
send_task_message,
reply_agent_message,
};
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 object = z.object(definition.args);
const input = (AGENT_MESSAGE_TOOLS as readonly string[]).includes(name) ? object.strict() : object;
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.remove("todoread");
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) };
},
});
});
const mcpReport = await registerMcpPolicy(ctx);
await registerTaskAgentPolicy(ctx);
await registerAgentMessagePolicy(ctx);
await ctx.rpc.register(Rpc.define({
id: "tjwater.tools",
methods: {
read_todos: {
input: z.object({ session_id: z.string().min(1).max(128) }).strict(),
output: z.array(todoItem),
},
check_models: {
input: z.object({ models: z.array(z.object({ id: z.string(), provider_id: z.string(), model_id: z.string(), wire_id: z.string(), requires_tools: z.boolean() }).strict()).min(1).max(1000) }).strict(),
output: z.object({
models: z.array(z.object({ id: z.string(), model_id: z.string(), status: z.enum(["valid", "not_registered", "not_enabled", "no_tools", "wire_id_mismatch", "not_advertised", "unchecked"]) })),
providers: z.array(z.object({ id: z.string(), status: z.enum(["checked", "not_registered", "unsupported_catalog", "catalog_unavailable", "catalog_invalid", "credentials_unavailable"]), http_status: z.number().int().optional() })),
}),
},
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: {},
}), {
read_todos: async (input) => z.array(todoItem).parse(await ctx.storage.get(`todos/${input.session_id}`) ?? []),
check_models: (input, context) => checkOpencodeModels(ctx, input, context.signal),
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 ?? DEFAULT_SESSION_WORKSPACE_ROOT) } : {}),
})),
});
},
});