Files
Aravind 4e1f746b22
Some checks failed
CI / test (push) Failing after 4m40s
CI / fixture (push) Failing after 7s
update the archive options
2026-09-10 19:30:46 +05:30

82 lines
2.8 KiB
Go

package definition
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
// CuratedIDs reads the agent specs that ship with the deployment and returns
// the set of definition ids they declare.
//
// # WHY THIS EXISTS
//
// An agent is "built-in" when the product ships its spec. There is no column
// saying so and deliberately none is added here: `importagents` publishes these
// same files into `agent_definitions` as ordinary `organization` rows, so a
// curated agent and a tenant-authored shared agent are indistinguishable in the
// table. The distinguishing fact lives on disk, in the directory the importer
// publishes FROM — which is the same fact the frontend uses, where `isShipped`
// tests membership of the ids bundled from `src/agents/**/*.md`.
//
// Reading the directory rather than listing ids in configuration keeps the
// protected set and the published set the same set by construction. Adding a
// ninth agent protects it; removing one stops protecting it; neither needs a
// code change, and neither can drift.
//
// The ids are parsed out of the frontmatter with the same parser the importer
// uses, NOT taken from the filename. A file named `analytics-agent.md` whose
// frontmatter says `id: analytics` publishes as `analytics`, and protecting the
// filename would protect nothing.
//
// A missing directory returns an empty set and no error: development checkouts
// and test binaries run from working directories that have no `agents/`, and
// this is a protection list rather than something to serve from. A directory
// that exists but holds a spec that will not parse IS an error — that same file
// would fail the importer, and staying quiet about it would leave an agent
// unprotected for a reason nobody could see.
func CuratedIDs(dir string) (map[string]bool, error) {
entries, err := os.ReadDir(dir)
if os.IsNotExist(err) {
return map[string]bool{}, nil
}
if err != nil {
return nil, fmt.Errorf("read curated agents in %s: %w", dir, err)
}
ids := make(map[string]bool, len(entries))
for _, e := range entries {
name := e.Name()
// README.md is documentation, not a spec — skipped by name, the same
// way cmd/importagents skips it, so that a parse failure always means
// something is actually wrong.
if e.IsDir() || !strings.HasSuffix(name, ".md") || name == "README.md" {
continue
}
raw, err := os.ReadFile(filepath.Join(dir, name))
if err != nil {
return nil, fmt.Errorf("read %s: %w", name, err)
}
parsed, err := ParseAgent(string(raw), Options{})
if err != nil {
return nil, fmt.Errorf("%s: %w", name, err)
}
if parsed.ID != "" {
ids[parsed.ID] = true
}
}
return ids, nil
}
// SortedIDs renders a set as a stable list, for logging.
func SortedIDs(set map[string]bool) []string {
out := make([]string, 0, len(set))
for id := range set {
out = append(out, id)
}
sort.Strings(out)
return out
}