From 4c9743ae4257088a9c5b4cc2a5cbfde89b83503b Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 14 Oct 2026 15:15:16 +0000 Subject: [PATCH] Let compiled plugins prepare startup after host defaults merge Why: RDM's sandbox policy must run after HostOptions are merged and be able to refuse startup when bwrap is missing. Evidence: go test ./pkg/extension/ ./cmd/agent-system/ -count=1 passed on this tree (SELF). Impact: InProc.Prepare and PrepareCompiled; serve applies plugin rewrites before app.Run. Refs: feature/svc-plugin-host-options Co-authored-by: Cursor --- cmd/agent-system/main.go | 16 +++++++++++++--- pkg/extension/extension.go | 18 ++++++++++++++++++ pkg/extension/hostoptions_test.go | 26 ++++++++++++++++++++++++++ pkg/extension/surface.txt | 1 + 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/cmd/agent-system/main.go b/cmd/agent-system/main.go index 13461aa..5b37990 100644 --- a/cmd/agent-system/main.go +++ b/cmd/agent-system/main.go @@ -120,13 +120,23 @@ func main() { if !cliSet["execBackend"] && execBackendResolved == "" { execBackendResolved = pluginDefaults.ExecBackend } + extOpt := extension.Options{ + Addr: resolved.Addr, DataDir: resolved.DataDir, PluginsDir: resolved.PluginsDir, + DisableUploadIndexing: pluginDefaults.DisableUploadIndexing, + DisableConvCardMirror: pluginDefaults.DisableConvCardMirror, + DisableExecTools: disableExec, ExecBackend: execBackendResolved, + } + if err := extension.PrepareCompiled(&extOpt); err != nil { + fmt.Fprintln(os.Stderr, "agent-system:", err) + os.Exit(1) + } if err := app.Run(app.Options{ Addr: resolved.Addr, DataDir: resolved.DataDir, PluginsDir: resolved.PluginsDir, ForceMock: resolved.Mock, StorageDSN: resolved.StorageDSN, ArtifactsDir: resolved.ArtifactsDir, AuthMode: resolved.AuthMode, InstanceID: resolved.Instance, InsecureListen: resolved.InsecureListen, AllowedHosts: resolved.AllowedHosts(), - DisableUploadIndexing: pluginDefaults.DisableUploadIndexing, - DisableConvCardMirror: pluginDefaults.DisableConvCardMirror, - DisableExecTools: disableExec, ExecBackend: execBackendResolved, ExecDepsDir: resolved.ExecDepsDir, + DisableUploadIndexing: extOpt.DisableUploadIndexing, + DisableConvCardMirror: extOpt.DisableConvCardMirror, + DisableExecTools: extOpt.DisableExecTools, ExecBackend: extOpt.ExecBackend, ExecDepsDir: resolved.ExecDepsDir, ExecWorkspace: resolved.ExecWorkspace, Tier: resolved.Tier, PGPool: store.PoolOptions{ diff --git a/pkg/extension/extension.go b/pkg/extension/extension.go index cde0025..018ed09 100644 --- a/pkg/extension/extension.go +++ b/pkg/extension/extension.go @@ -184,6 +184,8 @@ type InProc struct { HostOptions func() Options // CLI 可选。组合二进制在基座 serve 旗标上挂接插件专属旗标(如 bootstrap-admin)。 CLI *CLIHook + // Prepare 可选。基座合并 HostOptions 之后、进入服务之前调用,可改写 opt 或拒绝启动。 + Prepare func(opt *Options) error } // Options 是下游自有二进制的启动参数,字段与 agent-system serve 对齐。 @@ -277,6 +279,22 @@ func RunCLIHooks(opt Options) (bool, error) { return false, nil } +// PrepareCompiled 在 HostOptions 合并进 opt 之后调用各插件 Prepare。任一错误拒绝启动。 +func PrepareCompiled(opt *Options) error { + if opt == nil { + return errors.New("extension: nil options") + } + for _, e := range Compiled() { + if e.Prepare == nil { + continue + } + if err := e.Prepare(opt); err != nil { + return fmt.Errorf("extension: %s: %w", e.ID, err) + } + } + return nil +} + // RegisterCompiled 在 init 中登记进程内扩展。 func RegisterCompiled(e InProc) { mu.Lock() diff --git a/pkg/extension/hostoptions_test.go b/pkg/extension/hostoptions_test.go index bb60284..cabbd02 100644 --- a/pkg/extension/hostoptions_test.go +++ b/pkg/extension/hostoptions_test.go @@ -40,6 +40,32 @@ func TestMergeHostOptionsORAndExecConflict(t *testing.T) { } } +func TestPrepareCompiledCanRejectStartup(t *testing.T) { + ResetCompiled() + t.Cleanup(ResetCompiled) + RegisterCompiled(InProc{ + ID: "rdm", + Prepare: func(opt *Options) error { + if opt.ExecBackend == "sandbox" { + return errors.New("no bwrap") + } + opt.DisableExecTools = true + return nil + }, + }) + opt := Options{ExecBackend: "sandbox"} + if err := PrepareCompiled(&opt); err == nil { + t.Fatal("expected prepare error") + } + opt.ExecBackend = "direct" + if err := PrepareCompiled(&opt); err != nil { + t.Fatal(err) + } + if !opt.DisableExecTools { + t.Fatal("prepare should be able to rewrite options") + } +} + func TestCLIHookHandlesBeforeServe(t *testing.T) { ResetCompiled() t.Cleanup(ResetCompiled) diff --git a/pkg/extension/surface.txt b/pkg/extension/surface.txt index 731bc58..39d6061 100644 --- a/pkg/extension/surface.txt +++ b/pkg/extension/surface.txt @@ -34,6 +34,7 @@ MergeHostOptions NewHostClient NewScopedHost Options +PrepareCompiled ProjectHost ProjectHost.EnsureProject ProjectHost.OwnedProject