Why: EXT-11 is accepted; the surface (SetScopedHostFactory/NewScopedHost, SurfaceVersion 2.4.0-experimental) must land with the behavioral red pinned - wiring absent, NewScopedHost fails closed with ErrNotLinked. Evidence: TestScopedHostFactory red at the wired-factory assertion; row follows on the task card. Impact: pkg/extension additive surface (surface.txt +2 symbols); internal wireScopedHostFactory stub. Behavior unchanged. Refs: EXT-11 proposal; task card.
145 lines
5.0 KiB
Go
145 lines
5.0 KiB
Go
// Package extension 是基座对下游仓的公开扩展面。
|
||
// 下游只能依赖本包(以及 agent-system-core 的类型),不能导入 internal。
|
||
package extension
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"sync"
|
||
|
||
core "agent-system-core"
|
||
)
|
||
|
||
// SurfaceVersion 是本包导出面的实验版本。变更导出符号必须同时更新 surface.txt。
|
||
// 2.1.0-experimental:EXT-8 新增 ResourceAuthorizer 与 Registry.RegisterResourceAuthorizer。
|
||
// 2.2.0-experimental:EXT-9 新增 Host.KnowledgePublish/KnowledgeDelete 与
|
||
// Options.DisableUploadIndexing。
|
||
// 2.3.0-experimental:EXT-12 最小开关 Options.DisableConvCardMirror(会话卡不镜像
|
||
// 进项目公共流)。
|
||
// 2.4.0-experimental:EXT-11 新增 SetScopedHostFactory/NewScopedHost(按 owner+pluginID
|
||
// 重建受限宿主句柄,供下游恢复路径)。
|
||
const SurfaceVersion = "2.4.0-experimental"
|
||
|
||
// ErrNotLinked 表示进程没有空白导入 link 包,Main 无法启动服务。
|
||
var ErrNotLinked = errors.New("extension: runner not linked; blank-import agent-system-backend/pkg/extension/link")
|
||
|
||
// ResourceAuthorizer 是 EXT-8 资源授权适配器:基座在 owner 自有资源判定未命中时,
|
||
// 回调询问「owner 对 projectID 是否有共享读取授权」。只回答布尔,不接触基座内部
|
||
// 状态;返回 error 视为拒绝(fail-closed)。仅作用于读取路径,写路径一律 owner 自有。
|
||
type ResourceAuthorizer func(owner string, projectID core.ID) (bool, error)
|
||
|
||
// Registry 是进程内扩展的注册口。实现在基座内部,对下游只暴露这些方法。
|
||
type Registry interface {
|
||
AddSkill(name, markdown string) error
|
||
AddCommand(spec json.RawMessage) error
|
||
// AddCommandHandler 注册插件命令规格并绑定进程内处理器(EXT-1):
|
||
// 基座在分发时按请求 Owner 构造 Host 句柄调用 h。
|
||
AddCommandHandler(spec json.RawMessage, h Handler) error
|
||
Note(kind, name string, payload json.RawMessage) error
|
||
// RegisterResourceAuthorizer 注册 EXT-8 资源授权适配器(进程内扩展);
|
||
// 首个注册者生效。声明式扩展不支持(回调需宿主进程内代码)。
|
||
RegisterResourceAuthorizer(authz ResourceAuthorizer) error
|
||
}
|
||
|
||
// InProc 是随下游二进制编译进来的扩展。
|
||
type InProc struct {
|
||
ID string
|
||
Register func(reg Registry) error
|
||
}
|
||
|
||
// Options 是下游自有二进制的启动参数,字段与 agent-system serve 对齐。
|
||
type Options struct {
|
||
Addr string
|
||
DataDir string
|
||
PluginsDir string
|
||
Mock bool
|
||
StorageDSN string
|
||
ArtifactsDir string
|
||
AuthMode string
|
||
// DisableUploadIndexing 关闭上传端点的自动检索索引(EXT-9;零值=false=现状:
|
||
// 可解析文本照旧进索引。下游以发布状态机自管索引时置 true)。
|
||
DisableUploadIndexing bool
|
||
// DisableConvCardMirror 关闭会话卡向项目公共流的镜像(EXT-12 最小开关;
|
||
// 零值=false=现状全量镜像。true 时会话卡只进本人会话流,不发布即不持久,
|
||
// 项目流重放不含会话卡;项目级卡不受影响)。
|
||
DisableConvCardMirror bool
|
||
}
|
||
|
||
var (
|
||
mu sync.Mutex
|
||
compiled []InProc
|
||
runner func(Options) error
|
||
)
|
||
|
||
// RegisterCompiled 在 init 中登记进程内扩展。
|
||
func RegisterCompiled(e InProc) {
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
compiled = append(compiled, e)
|
||
}
|
||
|
||
// Compiled 返回已登记扩展的副本。
|
||
func Compiled() []InProc {
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
out := make([]InProc, len(compiled))
|
||
copy(out, compiled)
|
||
return out
|
||
}
|
||
|
||
// ResetCompiled 只给测试清状态。
|
||
func ResetCompiled() {
|
||
mu.Lock()
|
||
compiled = nil
|
||
mu.Unlock()
|
||
}
|
||
|
||
// SetRunner 由 link 包在 init 时注入。下游不要调用。
|
||
func SetRunner(fn func(Options) error) {
|
||
mu.Lock()
|
||
runner = fn
|
||
mu.Unlock()
|
||
}
|
||
|
||
// scopedHostFactory 由组合根在装配 hostcaps 后注入(EXT-11):按 owner+pluginID
|
||
// 构造与分发期同构的受限 Host。下游不要调用 SetScopedHostFactory。
|
||
var scopedHostFactory func(owner, pluginID string) Host
|
||
|
||
// SetScopedHostFactory 组合根注入(EXT-11)。下游不要调用。
|
||
func SetScopedHostFactory(fn func(owner, pluginID string) Host) {
|
||
mu.Lock()
|
||
scopedHostFactory = fn
|
||
mu.Unlock()
|
||
}
|
||
|
||
// NewScopedHost 按 owner+pluginID 重建受限宿主句柄(EXT-11):供下游任务恢复等
|
||
// 非分发期路径获取合法句柄;能力与分发期同构(owner 作用域、来源前缀强制、
|
||
// KV/任务台账隔离)。未 link 或未注入时返回错误(fail-closed,不降级)。
|
||
func NewScopedHost(owner, pluginID string) (Host, error) {
|
||
mu.Lock()
|
||
fn := scopedHostFactory
|
||
mu.Unlock()
|
||
if fn == nil {
|
||
return nil, ErrNotLinked
|
||
}
|
||
if owner == "" || pluginID == "" {
|
||
return nil, errors.New("extension: scoped host requires owner and pluginID")
|
||
}
|
||
h := fn(owner, pluginID)
|
||
if h == nil {
|
||
return nil, errors.New("extension: scoped host factory returned nil")
|
||
}
|
||
return h, nil
|
||
}
|
||
|
||
// Main 启动带有已登记扩展的服务。
|
||
func Main(opt Options) error {
|
||
mu.Lock()
|
||
fn := runner
|
||
mu.Unlock()
|
||
if fn == nil {
|
||
return ErrNotLinked
|
||
}
|
||
return fn(opt)
|
||
}
|