ci / vet-and-test (pull_request) Successful in 41s
Why: 进程内插件命令此前只登记规格不绑处理器,分发即 "no host handler bound", 且插件无 owner 凭证(H4);EXT-1 最小集补上处理器绑定与 owner 作用域宿主句柄。 Evidence: pkg/extension 新增 Request/Handler/Host + Registry.AddCommandHandler(surface.txt 12→15); cmdbus.RegisterPluginCommand 三例测试(committed/未绑 rejected/高危确认卡先拦); rag.IndexChunks 抽出并两例直测;internal/app hostcaps+inprocHost 复用 hostapi owner 作用域实现, applyCompiled 携 caps,inproc_host_test 双 owner 隔离 + provider=hash 向量写入均通过;go test ./... 29 包全绿。 Impact: 公开面依赖从零外部模块变为 +agent-system-core(声明式 go.mod 需按 compat-policy §4 加 core require+replace); 既有路径语义不变,SurfaceVersion 保持 2.0.0-experimental。 Refs: documents 提案 2026-10-05-ext-1-inproc-command-handler.md;V1.2 方案批注 1。
109 lines
2.7 KiB
Go
109 lines
2.7 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
agentcommand "agent-command"
|
||
"agent-system-backend/internal/cmdbus"
|
||
"agent-system-backend/pkg/extension"
|
||
core "agent-system-core"
|
||
)
|
||
|
||
type compiledRegistry struct {
|
||
skillsRoot string
|
||
bus *cmdbus.Bus
|
||
}
|
||
|
||
// pluginRegistry 携带插件身份与宿主能力,per-InProc 构造:
|
||
// AddCommandHandler 绑定的处理器在分发时按请求 Owner 构造 inprocHost(EXT-1)。
|
||
type pluginRegistry struct {
|
||
compiledRegistry
|
||
pluginID string
|
||
caps hostcaps
|
||
}
|
||
|
||
func (r pluginRegistry) AddCommandHandler(spec json.RawMessage, h extension.Handler) error {
|
||
if r.bus == nil {
|
||
return nil
|
||
}
|
||
if h == nil {
|
||
return fmt.Errorf("extension: %s: nil command handler", r.pluginID)
|
||
}
|
||
var s agentcommand.CommandSpec
|
||
if err := json.Unmarshal(spec, &s); err != nil {
|
||
return fmt.Errorf("extension: command spec: %w", err)
|
||
}
|
||
return r.bus.RegisterPluginCommand(s, func(ctx context.Context, req cmdbus.Request) (any, error) {
|
||
host := inprocHost{caps: r.caps, pluginID: r.pluginID, owner: req.Owner}
|
||
return h(ctx, host, extension.Request{
|
||
ID: req.ID, Params: req.Params, Entrance: req.Entrance,
|
||
Actor: req.Actor, Owner: req.Owner, ConversationID: req.ConversationID,
|
||
})
|
||
})
|
||
}
|
||
|
||
func (r compiledRegistry) AddSkill(name, markdown string) error {
|
||
if r.skillsRoot == "" || name == "" {
|
||
return nil
|
||
}
|
||
dest := filepath.Join(r.skillsRoot, "compiled", name)
|
||
if err := os.MkdirAll(dest, 0o755); err != nil {
|
||
return err
|
||
}
|
||
return os.WriteFile(filepath.Join(dest, "SKILL.md"), []byte(markdown), 0o644)
|
||
}
|
||
|
||
func (r compiledRegistry) AddCommand(spec json.RawMessage) error {
|
||
if r.bus == nil {
|
||
return nil
|
||
}
|
||
return r.bus.RegisterPluginCommands([]json.RawMessage{spec})
|
||
}
|
||
|
||
func (r compiledRegistry) Note(kind, name string, payload json.RawMessage) error {
|
||
log.Printf("extension: note %s %s (%d bytes)", kind, name, len(payload))
|
||
return nil
|
||
}
|
||
|
||
func applyCompiled(skillsRoot string, bus *cmdbus.Bus, caps hostcaps) {
|
||
base := compiledRegistry{skillsRoot: skillsRoot, bus: bus}
|
||
for _, e := range extension.Compiled() {
|
||
if e.Register == nil {
|
||
continue
|
||
}
|
||
reg := pluginRegistry{compiledRegistry: base, pluginID: e.ID, caps: caps}
|
||
if err := e.Register(reg); err != nil {
|
||
log.Printf("extension: %s: %v", e.ID, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func mergeMCPServers(raw string, extra []core.PluginMCPServer) string {
|
||
var base []json.RawMessage
|
||
if raw != "" {
|
||
if err := json.Unmarshal([]byte(raw), &base); err != nil {
|
||
return raw
|
||
}
|
||
}
|
||
for _, s := range extra {
|
||
b, err := json.Marshal(s)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
base = append(base, b)
|
||
}
|
||
if len(base) == 0 {
|
||
return ""
|
||
}
|
||
out, err := json.Marshal(base)
|
||
if err != nil {
|
||
return raw
|
||
}
|
||
return string(out)
|
||
}
|