Skip to content

扩展开发

ArkPilot 提供多种扩展方式,方便第三方插件或扩展包添加自定义工具、技能和上下文。

扩展方式概览

方式适用场景复杂度
脚本工具(tools/)简单工具,快速开发
JAR 扩展包(expansions/)多工具打包,功能丰富⭐⭐
插件 API 注册其他 Bukkit 插件集成⭐⭐⭐

方式一:脚本工具

plugins/ArkPilot/tools/ 目录下创建脚本工具,零编译、热加载。

目录结构

tools/
└── give-item/
    ├── manifest.yml    # 工具定义(必需)
    └── main.js         # 执行脚本(必需)

manifest.yml

yaml
# 工具名称(AI 调用时使用的函数名)
toolName: give_item
# 工具描述(告诉 AI 这个工具做什么)
description: "给予玩家指定物品和数量"
# 工具 key(可选,默认取目录名)
# key: give-item
# 参数定义(JSON Schema 格式)
parameters:
  type: object
  properties:
    item:
      type: string
      description: "物品 ID,如 diamond、iron_ingot"
    amount:
      type: integer
      description: "数量,默认 1"
      default: 1
  required: [item]

# 执行器配置
executor:
  # 入口文件
  entry: main.js
  # 执行类型(可选,默认按后缀推断)
  # javascript / python / process
  type: javascript
  # 超时(毫秒,可选)
  timeout: 5000

# 权限要求(可选)
# permission: "arkpilot.tool.give-item"
# 是否需要玩家确认(可选)
# requireConfirmation: false

脚本类型

类型语言机制特点
javascriptJSGraalJS 内嵌执行可直接访问 Bukkit Java 对象
pythonPythonGraalPy 内嵌执行同上
process任意语言子进程 stdin/stdout JSON 通信语言无限制

使用脚本工具

  1. 创建 tools/my-tool/manifest.yml 和脚本文件
  2. 执行 /ark reload
  3. 在助手配置的 tools.load 中添加工具 key:
yaml
tools:
  load:
    - execute-command
    - my-tool         # 新增

方式二:JAR 扩展包

plugins/ArkPilot/expansions/ 目录下放置 JAR 扩展包。一个 JAR 可注册多个工具、技能和上下文提供者。

核心接口

kotlin
// 扩展包基类
abstract class ArkPilotExpansion {
    abstract val id: String
    abstract val name: String
    open val requiredPlugins: List<String> = emptyList() // 可选:声明依赖的 Bukkit 插件名

    // 启用时调用,在此注册工具/技能
    abstract fun onEnable(context: ExpansionContext)

    // 禁用时调用
    open fun onDisable() {}
}

// 工具定义
abstract class ExpansionTool {
    abstract val key: String
    abstract val toolName: String
    abstract val description: String
    abstract val parameters: String  // JSON Schema

    // 执行工具
    abstract fun execute(player: Player, params: Map<String, Any?>): String
}

// 上下文提供者(使用 AgentScope,支持玩家和非玩家场景)
fun interface ExpansionContextProvider {
    fun provide(assistant: AssistantConfig, scope: AgentScope): String
}

// 注册上下文
interface ExpansionContext {
    val dataFolder: File
    fun registerTool(tool: ExpansionTool)
    fun registerSkill(key: String, markdown: String)
    fun registerContextProvider(id: String, provider: ExpansionContextProvider)
    fun registerCommand(command: ExpansionCommand)
}

requiredPlugins 用于声明前置插件依赖。加载器会在调用 onEnable 前检查这些插件是否已安装且已启用;若缺失,将跳过该扩展并输出日志。

注册命令

扩展包可以通过 ExpansionCommand 注册独立的顶级命令(如 /blueprint),与主插件 /ark 完全解耦:

kotlin
abstract class ExpansionCommand {
    /** 命令名称,注册为 /<name> 顶级命令 */
    abstract val name: String
    /** 命令别名 */
    open val aliases: List<String> = emptyList()
    /** 权限节点 */
    open val permission: String = ""
    /** 命令描述 */
    open val description: String = ""
    /** 是否仅玩家可执行 */
    open val playerOnly: Boolean = true
    /** 执行命令 */
    abstract fun execute(sender: CommandSender, args: List<String>)
    /** Tab 补全 */
    open fun tabComplete(sender: CommandSender, args: List<String>): List<String> = emptyList()
}

onEnable 中注册:

kotlin
override fun onEnable(context: ExpansionContext) {
    context.registerCommand(MyCommand())
}

命令通过 Bukkit CommandMap 动态注册,/ark reload 时自动注销并重新注册。

工具确认回调

需要玩家确认的工具可以通过 ToolConfirmCallback 完全控制确认消息的发送方式:

kotlin
class MyTool : ExpansionTool() {
    override val requireConfirmation = true
    override val confirmNoExpire = true  // 确认请求不超时

    override val confirmCallback = ToolConfirmCallback { scope, displayName, confirmId, params ->
        // 自定义确认消息发送逻辑(如添加预览按钮等)
        val component = ComponentBuilder("确认执行 $displayName? ")
            .append("[✓ 确认]").event(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ark confirm $confirmId"))
            .append(" [✗ 取消]").event(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ark cancel $confirmId"))
            .create()
        scope.sendMessage(*component)
    }
}

如果不设置 confirmCallback,将使用默认的确认消息模板。

内置示例:活动追踪扩展包

项目 expansions/ 模块提供了一个完整的扩展包示例——活动追踪器(Activity Tracker),演示如何:

  • 注册多个工具(query_player_activityquery_server_activity
  • 注册上下文提供者(向玩家对话注入近 30 分钟活动摘要)
  • 监听 Bukkit 事件(方块破坏/放置、聊天、死亡、成就、击杀等)
  • 支持全局工具调用(supportsGlobal = true),心跳助手和对话助手均可使用

编译后的 JAR 放入 plugins/ArkPilot/expansions/ 即可使用。配合内置的 event-host 心跳助手,可实现自动观察服务器动态并发起活动。

自定义消息投递(DeliveryProvider)

扩展包可以注册 DeliveryProvider,接管 AI 回复的投递方式。例如 chemdah 扩展包将 AI 回复写入 Chemdah Session 而不是直接发送聊天消息。

kotlin
import me.kzheart.arkpilot.core.agent.DeliveryFactory
import me.kzheart.arkpilot.core.agent.DeliveryProvider

// 在 onEnable 中注册
val provider = DeliveryProvider { assistant, scope, liveStreaming, agentStartTimeMs ->
    // 返回 null 表示不接管,交给下一个 Provider 或默认 PlayerDelivery
    if (assistant.id != "my-assistant") return@DeliveryProvider null
    MyCustomDelivery(assistant, scope, agentStartTimeMs)
}
DeliveryFactory.registerProvider(provider)

// 在 onDisable 中注销
DeliveryFactory.unregisterProvider(provider)

自定义 Delivery 需继承 AbstractPlayerDelivery,实现 onTextDeltafinishcancel 等方法。

构建注意事项

由于 TabooLib 会将 kotlin. 包重定向为 kotlin1822.,JAR 扩展包必须做相同的 relocate,否则运行时会出现 ClassNotFoundException。推荐使用 Shadow 插件:

kotlin
// build.gradle.kts
plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
}

tasks.shadowJar {
    archiveBaseName.set("arkpilot-my-expansion")
    archiveClassifier.set("")
    relocate("kotlin.", "kotlin1822.")
    // 不打包任何依赖,只做 relocate
    dependencies { exclude(dependency(".*:.*:.*")) }
}

tasks.jar { enabled = false }
tasks.build { dependsOn(tasks.shadowJar) }

使用扩展包

  1. 将 JAR 放入 expansions/ 目录
  2. 重启服务器或执行 /ark reload
  3. 在助手配置中启用扩展包注册的工具/技能

方式三:插件 API 注册

其他 Bukkit 插件可通过 ArkPilotApi 注册工具和技能。

注册工具

kotlin
import me.kzheart.arkpilot.api.ArkPilotApi

ArkPilotApi.registerTool("economy-balance", myAgentTool)

注册后在助手 tools.load 中添加 key 启用。

注册技能

kotlin
ArkPilotApi.registerSkill("economy-guide", """
---
name: economy-guide
description: 经济系统使用指南
---

# 经济系统

## 查看余额
使用 /money 查看当前余额。
...
""".trimIndent())

注册上下文提供者

向 Prompt Layer 4 注入场景上下文:

kotlin
ArkPilotApi.registerContextProvider("dungeon") { assistant, scope ->
    // scope 是 AgentScope,可通过 is PlayerBound 判断是否有玩家
    val playerScope = scope as? PlayerBound ?: return@registerContextProvider ""
    val player = playerScope.player.cast<Player>()
    val dungeon = DungeonPlugin.getCurrentDungeon(player)
    if (dungeon != null) {
        "Player is in dungeon: ${dungeon.name}, floor ${dungeon.floor}"
    } else {
        ""  // 返回空字符串不注入
    }
}

监听事件

所有事件继承自 ArkPilotEvent,基类携带 scope: AgentScope 属性,可通过 is PlayerBound 判断是否来自玩家。

kotlin
// TabooLib 注解方式
@SubscribeEvent
fun onConversationEnd(event: ConversationEndEvent) {
    val playerScope = event.scope as? PlayerBound
    // playerScope?.playerName, playerScope?.player 等
}

// 回调注册方式(回调参数为 AgentScope)
ArkPilotApi.onConversationEnd { scope, assistant, usage ->
    // scope 是 AgentScope
}

ArkPilotApi.onToolExecution { scope, toolName, params ->
    // scope 是 AgentScope
}

可用事件

事件说明
ConversationStartEvent对话开始
ConversationEndEvent对话结束(含 usage、success、error)
ToolExecutionStartEvent工具执行开始(含 toolName、params)
ToolExecutionEndEvent工具执行结束(含 success、durationMs)
ToolConfirmRequestEvent工具确认请求发出
ToolConfirmResponseEvent玩家确认/取消工具执行
MemoryChangeEvent记忆变更(ADD/REMOVE)
SessionResetEvent会话重置(MANUAL/IDLE_EXPIRE/DAILY_RESET)
RouteResolvedEvent路由解析完成

动态修改 Prompt

kotlin
ArkPilotApi.addBootstrapHook { assistant, scope, files ->
    // scope 是 AgentScope
    files["SOUL"] = files["SOUL"].orEmpty() + "\n\n额外的灵魂指引..."
}

工具来源与优先级

所有方式注册的工具完全等同,用同样的 key 引用:

BUILTIN    → 代码内置工具
PLUGIN     → 其他插件通过 API 注册
EXTERNAL   → tools/ 脚本工具
EXPANSION  → expansions/ JAR 扩展包

安全注意事项

WARNING

  • 脚本工具和扩展包拥有与插件相同的权限级别
  • 服主应审查所有第三方脚本和扩展包的安全性
  • 子进程模式的工具有超时强制终止和输出大小限制

内置扩展包参考

想了解内置扩展包的使用方式和配置?请查看扩展模块文档:

ArkPilot — Minecraft AI Assistant Plugin