155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
import { tool } from "@opencode-ai/plugin";
|
|
|
|
import { SkillStore } from "../../src/skills/store.js";
|
|
import {
|
|
getRuntimeSessionContext,
|
|
type RuntimeSessionContext,
|
|
} from "../../src/runtime/sessionContext.js";
|
|
|
|
type ToolContextReader = {
|
|
read(sessionId: string): RuntimeSessionContext | null;
|
|
};
|
|
|
|
const runtimeContextReader: ToolContextReader = {
|
|
read: getRuntimeSessionContext,
|
|
};
|
|
|
|
export const createSkillManagerTool = (
|
|
skillStore = new SkillStore(),
|
|
toolContextStore: ToolContextReader = runtimeContextReader,
|
|
initializePromise: Promise<unknown> = Promise.resolve(),
|
|
) =>
|
|
tool({
|
|
description:
|
|
"维护已验证、可复用、非敏感的 workflow 或方法模式。支持 list、write_skill、remove_skill、append_pattern、remove_pattern、write_reference、remove_reference、write_script、remove_script。",
|
|
args: {
|
|
action: tool.schema
|
|
.enum([
|
|
"list",
|
|
"write_skill",
|
|
"remove_skill",
|
|
"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. Use 'workflow' for the workflow index, or '__root__' for the root skills index.",
|
|
),
|
|
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(
|
|
"Content used by write_skill, write_reference, or write_script.",
|
|
),
|
|
},
|
|
async execute(args, context) {
|
|
await initializePromise;
|
|
const sessionContext = 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 === "write_skill"
|
|
? await skillStore.writeSkill(args.skill_path, args.content ?? "")
|
|
: args.action === "remove_skill"
|
|
? await skillStore.removeSkill(args.skill_path)
|
|
: 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,
|
|
});
|
|
},
|
|
});
|
|
|
|
export default createSkillManagerTool();
|