Files
Agent-System-Backend/pkg/extension/extension.go
T
weichen 182d8b5143 Add the EXT-11 scoped-host surface with a red factory test
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.
2026-10-08 17:00:53 +00:00

145 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)
}