扩展 HotPlex

May 14, 2026 · View on GitHub

阅读本文后,你将掌握为 HotPlex 添加新组件的完整流程:Worker 适配器、消息平台、AEP 事件类型和 CLI 子命令。

概述

HotPlex 采用插件式架构,通过 interface + registry 模式实现组件解耦。新增组件只需实现对应接口、完成注册,无需修改核心逻辑。本文覆盖四种最常见的扩展场景。

前提条件

场景一:添加新 Worker 适配器

Worker 适配器负责与 AI Agent 运行时通信。所有 Worker 实现 worker.Worker 接口并嵌入 base.BaseWorker

文件结构

internal/worker/<name>/
├── worker.go          # 主适配器实现
├── worker_test.go     # 单元测试
└── ...

实现步骤

1. 创建适配器结构体

package myworker

import (
    "context"
    "sync"

    "github.com/hrygo/hotplex/internal/worker"
    "github.com/hrygo/hotplex/internal/worker/base"
)

// 编译时接口检查
var _ worker.Worker = (*Worker)(nil)

// Worker 实现 myworker 适配器
type Worker struct {
    *base.BaseWorker  // 共享生命周期:Terminate/Kill/Wait/Health/LastIO/Conn
    mu sync.Mutex
}

2. 实现 Worker 接口

// Type 返回 Worker 类型标识
func (w *Worker) Type() worker.WorkerType {
    return worker.WorkerType("myworker")
}

// SupportsResume 标识是否支持恢复会话
func (w *Worker) SupportsResume() bool { return true }

// SupportsStreaming 标识是否支持流式输出
func (w *Worker) SupportsStreaming() bool { return true }

// SupportsTools 标识是否支持工具调用
func (w *Worker) SupportsTools() bool { return true }

// Start 启动 Worker 运行时
func (w *Worker) Start(ctx context.Context, session worker.SessionInfo) error {
    w.mu.Lock()
    defer w.mu.Unlock()

    // 1. 构建启动命令
    // 2. 启动子进程(使用 proc 包确保跨平台隔离)
    // 3. 建立 SessionConn
    return nil
}

// Input 投递用户消息
func (w *Worker) Input(ctx context.Context, content string, metadata map[string]any) error {
    // 通过 SessionConn 发送消息
    return nil
}

// Resume 恢复已有会话
func (w *Worker) Resume(ctx context.Context, session worker.SessionInfo) error {
    // 重连到已有会话
    return nil
}

3. 注册 Worker

使用 init() + worker.Register() 模式:

func New(log *slog.Logger, cfg *config.Config) *Worker {
    return &Worker{BaseWorker: base.NewBaseWorker(log, cfg)}
}

func init() {
    worker.Register(worker.WorkerType("myworker"), func() (worker.Worker, error) {
        return New(slog.Default(), nil), nil
    })
}

4. 确保 blank import

cmd/hotplex/main.go 中添加 blank import:

import (
    _ "github.com/hrygo/hotplex/internal/worker/myworker"
)

5. 编写测试

// worker_test.go
package myworker

import (
    "testing"
    "github.com/stretchr/testify/require"
)

func TestWorker_Type(t *testing.T) {
    t.Parallel()
    w := New(slog.Default(), nil)
    require.Equal(t, worker.WorkerType("myworker"), w.Type())
}

完整的分步实现指南(包含 Start/Input/Resume 的详细实现、输出读取循环、进程隔离等)请参考 添加 Worker 适配器

关键注意事项

  • 进程隔离:使用 internal/worker/proc 包启动子进程,确保 PGID (POSIX) 或 Job Object (Windows) 隔离
  • 分层终止:遵循 SIGTERM → 等待 5s → SIGKILL 的终止顺序
  • 跨平台:使用 filepath.Join()os.TempDir(),不硬编码路径分隔符
  • Mutex:显式 mu 字段,不嵌入 sync.Mutex

场景二:添加新消息平台适配器

消息平台适配器负责与 Slack、飞书等平台通信。所有适配器实现 PlatformAdapterInterface 接口并嵌入 PlatformAdapter

文件结构

internal/messaging/<name>/
├── adapter.go         # 主适配器实现
├── adapter_test.go    # 单元测试
└── ...

实现步骤

1. 创建适配器结构体

package myplatform

import (
    "context"
    "github.com/hrygo/hotplex/internal/messaging"
)

type Adapter struct {
    messaging.BaseAdapter[*MyPlatformConn]  // 嵌入获取连接池
    // 平台特有字段
}

2. 实现 PlatformAdapterInterface

实现 PlatformAdapterInterface 的 6 个方法:Platform()Start()HandleTextMessage()Close()ConfigureWith(AdapterConfig)GetBotID()

3. 2 步初始化

cmd/hotplex/messaging_init.go 中注册初始化流程:

// 1. 统一注入依赖
adapter.ConfigureWith(messaging.AdapterConfig{
    Hub: hub, SM: sm, Handler: handler, Bridge: bridge, Gate: gate,
    Extras: map[string]any{"token": token},
})
// 2. 启动连接
adapter.Start(ctx)

4. 结果投递

internal/cron/delivery.go 中添加新平台的投递逻辑,将 cron 任务执行结果回传到该平台。

完整的分步实现指南(包含 PlatformConn 实现、消息去重、SDK 集成、测试等)请参考 添加消息平台适配器

场景三:添加新 AEP 事件类型

AEP v1 是 HotPlex 的统一通信协议。添加新事件类型需要修改 pkg/events/events.go

实现步骤

1. 添加 Kind 常量

// pkg/events/events.go

const (
    // ... 已有事件类型
    MyNewEvent Kind = "my_new_event"  // 添加新类型
)

2. 添加 Event Data 结构体(如需要)

// 如果新事件有自定义数据结构
type MyNewEventData struct {
    Field1 string `json:"field1"`
    Field2 int    `json:"field2"`
}

3. 更新编解码验证

pkg/aep/ 中更新编解码逻辑(如需对新类型做特殊验证)。

4. 更新 Handler

internal/gateway/handler.go 中添加对新事件类型的处理逻辑:

func (h *Handler) handle(ctx context.Context, env *events.Envelope) error {
    switch env.Event.Type {
    // ... 已有 case
    case events.MyNewEvent:
        return h.handleMyNewEvent(ctx, env)
    }
}

5. 更新背压策略

internal/gateway/hub.goisDroppable 函数中决定新事件类型是否可丢弃:

func isDroppable(kind events.Kind) bool {
    switch kind {
    case events.MessageDelta, events.Raw:
        return true
    // MyNewEvent 默认不可丢弃(关键事件)
    default:
        return false
    }
}

6. 编写测试

func TestMyNewEvent_Kind(t *testing.T) {
    t.Parallel()
    require.Equal(t, events.Kind("my_new_event"), events.MyNewEvent)
}

关键注意事项

  • Kind 命名使用 snake_case(如 permission_request
  • C→S(Client → Server)事件需在 Handler 中处理
  • S→C(Server → Client)事件需在 Bridge/Hub 中生成
  • 所有事件必须有文档化的语义和时间约束

场景四:添加新 CLI 子命令

HotPlex 使用 Cobra CLI 框架,子命令定义在 cmd/hotplex/ 目录。

文件结构

cmd/hotplex/
├── main.go            # 根命令
├── <name>.go          # 新子命令
└── <name>_test.go     # 测试

实现步骤

1. 创建命令文件

// cmd/hotplex/mycmd.go
package main

import (
    "github.com/spf13/cobra"
)

func newMyCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "mycmd",
        Short: "Short description",
        Long:  "Long description",
        RunE: func(cmd *cobra.Command, args []string) error {
            // 命令逻辑
            return nil
        },
    }

    // 添加 flag
    cmd.Flags().String("name", "", "description")

    // 添加子命令
    cmd.AddCommand(newMySubCmd())

    return cmd
}

2. 注册到根命令

cmd/hotplex/main.goinit() 函数中添加注册:

func init() {
    rootCmd.AddCommand(newMyCmd())
}

3. 编写测试

// cmd/hotplex/mycmd_test.go
package main

import (
    "testing"
    "github.com/stretchr/testify/require"
)

func TestMyCmd_Execute(t *testing.T) {
    t.Parallel()
    cmd := newMyCmd()
    cmd.SetArgs([]string{"--name", "test"})
    require.NoError(t, cmd.Execute())
}

通用注意事项

跨平台兼容

所有新组件必须兼容 Linux、macOS、Windows:

  • 路径:使用 filepath.Join()filepath.Dir()filepath.Base()
  • 临时目录:使用 os.TempDir()
  • 用户主目录:使用 os.UserHomeDir()
  • 进程终止:使用 process.Kill()(通过 proc 包)
  • 平台特有逻辑:使用 *_unix.go / *_windows.go build tags

代码规范

  • Mutex:显式 mu 字段,不嵌入,不传指针
  • 错误Err 前缀(哨兵)、Error 后缀(自定义)、fmt.Errorf("%w") 包装
  • 日志:使用 log/slog JSON handler
  • 测试testify/require、表驱动、t.Parallel()

DI 注入

项目禁止 wire/dig,全部手动构造函数注入。通过 GatewayDeps 结构传递依赖。

验证

添加新组件后,运行完整检查:

# 1. 确保编译通过
make build

# 2. 运行完整测试
make test

# 3. 运行 lint
make lint

# 4. 完整 CI 等价检查
make check

验证清单:

  • 新组件有对应测试
  • 通过 race 检测
  • 跨平台兼容(至少确保无硬编码路径分隔符)
  • 注册到 registry(Worker 适配器)
  • blank import 已添加(Worker 适配器)
  • 遵循项目代码规范

下一步