Add global skill system for self-accumulated knowledge

Introduce a cross-group skill system that lets the bot distill reusable
knowledge into markdown docs and load them on demand, keeping day-to-day
context pollution low.

- SkillStore manages data/skills/*.md files with name/description
  frontmatter and an in-memory index cache (rebuilt on init/reload)
- Only the skill index (name + one-line description) is injected via the
  new {skills} system-prompt placeholder; bodies load on demand
- New tools: loadSkill / saveSkill (upsert, iterate = load+overwrite) /
  deleteSkill, gated by PluginConfig.skillsEnabled
- Skill names validated against ^[A-Za-z0-9_-]+$ to prevent traversal
- Wire SkillStore.init into onEnable and refresh on /jgpt reload; add
  /jgpt skills listing command
- Bump version to 1.12.0; update README

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 23:37:44 +08:00
parent 538fe563a0
commit 4307019ee8
9 changed files with 416 additions and 8 deletions

View File

@@ -0,0 +1,62 @@
package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool
import com.aallam.openai.api.core.Parameters
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.add
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import kotlinx.serialization.json.putJsonObject
import net.mamoe.mirai.event.events.MessageEvent
import top.jie65535.mirai.JChatGPT
import top.jie65535.mirai.PluginConfig
import top.jie65535.mirai.SkillStore
/**
* 新增或整篇覆盖一个技能(全局,跨群共享)。
* 用于把群里学到/被纠正的知识沉淀下来;迭代时先用 loadSkill 读全文,改好后整篇写回。
*/
class SaveSkill : BaseAgent(
tool = Tool.function(
name = "saveSkill",
description = "沉淀或更新一个技能(知识文档),全局跨群共享。新增直接写;迭代时先 loadSkill 读全文,修改后整篇写回。技能名相同则覆盖。",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("name") {
put("type", "string")
put("description", "技能名kebab-case只能含字母/数字/下划线/连字符,如 kubejs-basics。相同则覆盖")
}
putJsonObject("description") {
put("type", "string")
put("description", "一句话简介,会常驻技能索引,决定你以后何时加载它")
}
putJsonObject("content") {
put("type", "string")
put("description", "技能正文markdown沉淀的知识、经验或提示词。整篇内容会覆盖旧版本")
}
}
putJsonArray("required") {
add("name")
add("description")
add("content")
}
}
)
) {
override val isEnabled: Boolean
get() = PluginConfig.skillsEnabled
override val loadingMessage: String = "记下来了..."
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
requireNotNull(args)
val name = args.getValue("name").jsonPrimitive.content
val description = args.getValue("description").jsonPrimitive.content
val content = args.getValue("content").jsonPrimitive.content
JChatGPT.logger.info("Save skill: \"$name\" - \"$description\"")
val error = SkillStore.save(name, description, content)
return error ?: "OK技能 \"$name\" 已保存。"
}
}