Files
SivanCola 073079d675 feat(composition): load preset resources on demand
Problem: selecting a custom preset captures unused skill templates and
extension packages and waits for optional MCP startup.

Root cause: resource snapshot creation also served as the runtime permission
model, making selection cost scale with installed resource payload.

Fix: add live capability-grant bindings while preserving legacy snapshots.
Resolve current skill/MCP/extension/hook/command authority at use, defer
optional startup, retain stable provider contracts, and fence new event
and archive formats from older readers. Use lightweight status summaries
and per-instance extension call leases for safe concurrent replacement.

Verification: root and Desktop suites, full boot/builtin suites, affected
storage/controller/frontend tests, lifecycle race checks, frontend interaction
tests and typecheck, lint/vet/check, actionlint, and Windows cross-compilation.
Twenty-sample real switch measurements include build, persist and publication;
64 MiB unused templates remain outside the binding and stay below the local
500 ms P95 target. Native Windows execution is delegated to hosted CI.
2026-10-20 18:49:01 +08:00

66 lines
1.6 KiB
Go

package usecap
import (
"sync"
"reasonix/internal/contract/agentcomposition"
"reasonix/internal/contract/config"
"reasonix/internal/ext/plugin"
)
type InventorySource func() ([]config.PluginEntry, []plugin.Spec, map[string]bool, error)
type inventorySource struct {
mu sync.Mutex
load InventorySource
fingerprint string
specs []plugin.Spec
}
func (r *MCPCapabilityRuntime) ConfigureInventorySource(load InventorySource) {
r.source = &inventorySource{load: load}
}
func (r *MCPCapabilityRuntime) refreshInventory() error {
if r == nil || r.source == nil {
return nil
}
source := r.source
source.mu.Lock()
defer source.mu.Unlock()
entries, specs, enabled, err := source.load()
if err != nil {
return err
}
keys := make([]string, 0, len(specs))
for _, spec := range specs {
keys = append(keys, plugin.SchemaCacheKey(spec)+":"+spec.Package)
}
fingerprint := agentcomposition.Digest(struct {
Entries []config.PluginEntry
Keys []string
Enabled map[string]bool
}{entries, keys, enabled})
if source.fingerprint != fingerprint || !sameInventorySpecs(source.specs, specs) {
r.ConfigureServers(entries, specs, enabled)
source.fingerprint = fingerprint
source.specs = make([]plugin.Spec, len(specs))
for i, spec := range specs {
source.specs[i] = cloneMCPSpec(spec)
}
}
return nil
}
func sameInventorySpecs(previous, current []plugin.Spec) bool {
if len(previous) != len(current) {
return false
}
for i, spec := range current {
if previous[i].Authorized != spec.Authorized || !plugin.MCPRuntimeSpecMatches(previous[i], spec) {
return false
}
}
return true
}