import { tool } from "@opencode-ai/plugin"; import { SkillStore } from "../../src/skills/store.js"; import { ToolSessionContextStore } from "../../src/session/toolContextStore.js"; const toolContextStore = new ToolSessionContextStore(); const initializePromise = toolContextStore.initialize(); const skillStore = new SkillStore(); export default tool({ description: "维护已验证、可复用、非敏感的 workflow 或方法模式。支持 list、append_pattern、remove_pattern、write_reference、remove_reference、write_script、remove_script。", args: { action: tool.schema .enum([ "list", "append_pattern", "remove_pattern", "write_reference", "remove_reference", "write_script", "remove_script", ]) .describe("Skill maintenance operation."), reason: tool.schema .string() .describe( "Why this skill maintenance action is justified for future reuse.", ), skill_path: tool.schema .string() .describe( "Target skill directory path relative to .opencode/skills, for example analytics/simulation-analysis/leakage or platform/governance-observability/meta.", ), pattern: tool.schema .string() .optional() .describe("Pattern text used by append_pattern."), target_id: tool.schema .string() .optional() .describe("Stable learned pattern id used by remove_pattern."), file_path: tool.schema .string() .optional() .describe("Asset file path. For references use references/*.md; for scripts use scripts/*.py."), content: tool.schema .string() .optional() .describe("Asset content used by write_reference or write_script."), }, async execute(args, context) { await initializePromise; const sessionContext = await toolContextStore.read(context.sessionID); if (!sessionContext) { throw new Error(`session context not found for ${context.sessionID}`); } if (sessionContext.allowLearningWrite === false && args.action !== "list") { return JSON.stringify({ ok: true, kind: "skill", decision: "rejected", detail: "skill writes are disabled for this session", }); } if (args.action === "list") { const result = await skillStore.list(args.skill_path); if (!result) { return JSON.stringify({ ok: true, kind: "skill", decision: "rejected", detail: "invalid skill_path; expected a relative path under .opencode/skills", }); } return JSON.stringify({ ok: true, kind: "skill", decision: "accepted", detail: "skill listed", ...result, }); } const result = args.action === "append_pattern" ? await skillStore.appendPattern(args.skill_path, args.pattern ?? "") : args.action === "remove_pattern" ? await skillStore.removePattern(args.skill_path, args.target_id ?? "") : args.action === "write_reference" ? await skillStore.writeReference( args.skill_path, args.file_path ?? "", args.content ?? "", ) : args.action === "remove_reference" ? await skillStore.removeReference(args.skill_path, args.file_path ?? "") : args.action === "write_script" ? await skillStore.writeScript( args.skill_path, args.file_path ?? "", args.content ?? "", ) : await skillStore.removeScript(args.skill_path, args.file_path ?? ""); return JSON.stringify({ ok: true, kind: "skill", decision: result.changed ? "accepted" : "rejected", detail: result.detail, target: result.target, }); }, });