mirror of
https://github.com/msitarzewski/agency-agents.git
synced 2026-10-01 13:55:54 +03:00
fix(install): accept the file-stem ids runbooks.json uses for --agent
strategy/runbooks.json names agents by file stem, the corpus id the desktop
app maps to catalog entries ("engineering-frontend-developer"). The installer
only accepted the slug built from `name:` ("frontend-developer"), and for
206 of the 279 agents those differ. Of the 36 agents the four runbooks list,
35 could not be selected by the ids the runbooks give them:
$ install.sh --tool claude-code --agent engineering-frontend-developer --dry-run
[ERR] Unknown agent 'engineering-frontend-developer'. Use --list agents ...
So there was no way to install a runbook's team from the CLI without
translating every id by hand.
resolve_agent now takes a slug, a display name (as before, via slugify), or a
file stem, and returns the install slug. Slugs are tried first. No stem
equals a different agent's slug today, and stems are unique, so nothing that
resolved before resolves differently now.
Resolution also stops rescanning the roster per request. agent_slug_exists
ran get_field over all 279 files for each requested agent; roster_index
builds the slug/stem table once in the parent shell. A 36-line agents file
went from ~10,000 get_field calls to 279.
test-agent-selection.sh gains a stem case and feeds each runbook's roster to
--agents-file, checking that it resolves to exactly its own agent count. It
fails on main at the first runbook. The usage text and the README selection
section say which ids are accepted, with an example of installing a
runbook's team.
This commit is contained in:
@@ -102,6 +102,15 @@ Browse the agents below and copy/adapt the ones you need!
|
||||
./scripts/install.sh --tool opencode --division engineering --dry-run
|
||||
```
|
||||
|
||||
`--agent` and `--agents-file` take an agent's slug (as `--list agents` prints it), its display name, or its file name without `.md` — the id the [runbook rosters](strategy/runbooks.json) use — so a runbook's team installs as listed:
|
||||
|
||||
```bash
|
||||
python3 -c 'import json, sys
|
||||
for r in json.load(open("strategy/runbooks.json"))["runbooks"]:
|
||||
if r["slug"] == sys.argv[1]: [print(a) for g in r["roster"] for a in g["agents"]]' startup-mvp > team.txt
|
||||
./scripts/install.sh --tool claude-code --agents-file team.txt
|
||||
```
|
||||
|
||||
> **OpenCode note:** OpenCode's runtime currently registers only ~119 agents and silently drops the rest ([upstream bug](https://github.com/anomalyco/opencode/issues/27988)). Installing a subset with `--division` keeps you under that limit. The installer warns you when a selection would exceed it.
|
||||
|
||||
See the [Multi-Tool Integrations](#-multi-tool-integrations) section below for full details.
|
||||
|
||||
+43
-13
@@ -33,8 +33,9 @@
|
||||
# Selection (compose freely; empty = everything):
|
||||
# --tool <a,b> Only these tools
|
||||
# --division <a,b> Only these teams/divisions (comma-separated)
|
||||
# --agent <slug,slug> Only these specific agents
|
||||
# --agents-file <path> Agents listed in a file (one slug/name per line, # comments ok)
|
||||
# --agent <id,id> Only these specific agents (install slug, display name,
|
||||
# or file stem such as engineering-frontend-developer)
|
||||
# --agents-file <path> Agents listed in a file (one id per line, # comments ok)
|
||||
#
|
||||
# Mode:
|
||||
# --link Symlink instead of copy (updates propagate)
|
||||
@@ -165,6 +166,7 @@ AGENTS_FILE="" # --agents-file
|
||||
DRY_RUN=false # --dry-run
|
||||
SELECTION_ACTIVE=false # true once any agent-level filter is applied
|
||||
_ALLOWED_SLUGS="" # newline-delimited cache of allowed slugs
|
||||
_ROSTER_INDEX="" # "<install slug>\t<file stem>" per agent; see roster_index
|
||||
|
||||
# division_files <division> — agent file paths (frontmatter only) in a division.
|
||||
division_files() {
|
||||
@@ -178,16 +180,43 @@ division_files() {
|
||||
# division_count <division> — number of agents in a division.
|
||||
division_count() { division_files "$1" | grep -c . ; }
|
||||
|
||||
# agent_slug_exists <slug> — verify a requested agent against the source roster.
|
||||
# Selection filters should fail before installation when they name nothing that
|
||||
# can be installed; otherwise dry-run counts and completion messages lie.
|
||||
agent_slug_exists() {
|
||||
local target="$1" div f
|
||||
# roster_index — fill _ROSTER_INDEX with one "<install slug>\t<file stem>" line
|
||||
# per agent, once. Call it in the parent shell before resolve_agent: a $(...)
|
||||
# caller would build its own copy and throw it away.
|
||||
#
|
||||
# Resolving each requested agent used to rescan the roster, running get_field
|
||||
# on all 279 files per request, so a 36-agent runbook roster cost ~10,000
|
||||
# get_field calls before anything installed.
|
||||
roster_index() {
|
||||
[[ -n "$_ROSTER_INDEX" ]] && return 0
|
||||
local div f
|
||||
for div in "${ALL_DIVISIONS[@]}"; do
|
||||
while IFS= read -r f; do
|
||||
[[ "$(agent_slug "$f")" == "$target" ]] && return 0
|
||||
_ROSTER_INDEX+="$(agent_slug "$f")"$'\t'"$(basename "$f" .md)"$'\n'
|
||||
done < <(division_files "$div")
|
||||
done
|
||||
}
|
||||
|
||||
# resolve_agent <requested> — print the install slug for a requested agent,
|
||||
# 1 if nothing matches. Selection filters should fail before installation when
|
||||
# they name nothing that can be installed; otherwise dry-run counts and
|
||||
# completion messages lie.
|
||||
#
|
||||
# Two spellings name an agent. The install slug comes from `name:` and is what
|
||||
# --list agents prints. The file stem is the corpus id strategy/runbooks.json
|
||||
# uses ("engineering-frontend-developer"), and for 206 of 279 agents it is not
|
||||
# the slug, so 35 of the 36 agents the runbooks list could not be selected by
|
||||
# the ids the runbooks give. Slugs are tried first; no stem equals another
|
||||
# agent's slug today, and slug-first keeps it unambiguous if one ever does.
|
||||
resolve_agent() {
|
||||
local target="$1" slug stem
|
||||
[[ -n "$target" ]] || return 1
|
||||
while IFS=$'\t' read -r slug stem; do
|
||||
[[ -n "$slug" && "$slug" == "$target" ]] && { printf '%s' "$slug"; return 0; }
|
||||
done <<< "$_ROSTER_INDEX"
|
||||
while IFS=$'\t' read -r slug stem; do
|
||||
[[ -n "$slug" && "$stem" == "$target" ]] && { printf '%s' "$slug"; return 0; }
|
||||
done <<< "$_ROSTER_INDEX"
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -199,7 +228,8 @@ build_selection() {
|
||||
return
|
||||
fi
|
||||
SELECTION_ACTIVE=true
|
||||
local slugs="" div f s line requested
|
||||
local slugs="" div f s line requested resolved
|
||||
roster_index
|
||||
for div in ${FILTER_DIVISIONS[@]+"${FILTER_DIVISIONS[@]}"}; do
|
||||
while IFS= read -r f; do
|
||||
s="$(agent_slug "$f")"; [[ -n "$s" ]] && slugs+="$s"$'\n'
|
||||
@@ -207,11 +237,11 @@ build_selection() {
|
||||
done
|
||||
for s in ${FILTER_AGENTS[@]+"${FILTER_AGENTS[@]}"}; do
|
||||
requested="$(slugify "$s")"
|
||||
if ! agent_slug_exists "$requested"; then
|
||||
if ! resolved="$(resolve_agent "$requested")"; then
|
||||
err "Unknown agent '$s'. Use --list agents to see the available roster."
|
||||
exit 1
|
||||
fi
|
||||
slugs+="$requested"$'\n'
|
||||
slugs+="$resolved"$'\n'
|
||||
done
|
||||
if [[ -n "$AGENTS_FILE" ]]; then
|
||||
[[ -f "$AGENTS_FILE" ]] || { err "agents-file not found: $AGENTS_FILE"; exit 1; }
|
||||
@@ -220,11 +250,11 @@ build_selection() {
|
||||
line="$(printf '%s' "$line" | xargs 2>/dev/null)" # trim
|
||||
[[ -z "$line" ]] && continue
|
||||
requested="$(slugify "$line")"
|
||||
if ! agent_slug_exists "$requested"; then
|
||||
if ! resolved="$(resolve_agent "$requested")"; then
|
||||
err "Unknown agent '$line' in agents-file '$AGENTS_FILE'."
|
||||
exit 1
|
||||
fi
|
||||
slugs+="$requested"$'\n'
|
||||
slugs+="$resolved"$'\n'
|
||||
done < "$AGENTS_FILE"
|
||||
fi
|
||||
_ALLOWED_SLUGS="$(printf '%s' "$slugs" | sort -u | sed '/^$/d')"
|
||||
|
||||
@@ -41,4 +41,33 @@ output="$($INSTALLER --tool claude-code --agent 'Developer Tooling Engineer' --d
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "PASS: install.sh rejects unknown agent selections and accepts display names"
|
||||
# The file stem is the id strategy/runbooks.json uses, and for most agents it is
|
||||
# not the install slug (engineering-frontend-developer vs frontend-developer).
|
||||
output="$("$INSTALLER" --tool claude-code --agent engineering-frontend-developer --dry-run 2>&1)"
|
||||
[[ "$output" == *"Agents: 1"* ]] || {
|
||||
printf 'File-stem selection did not resolve to one agent:\n%s\n' "$output" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Every runbook roster, fed to --agents-file as the runbooks list it, resolves
|
||||
# to exactly its own agents. On main 35 of the 36 ids were "Unknown agent".
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
while IFS=$'\t' read -r runbook count ids; do
|
||||
printf '%s\n' $ids > "$AGENTS_FILE"
|
||||
set +e
|
||||
output="$("$INSTALLER" --tool claude-code --agents-file "$AGENTS_FILE" --dry-run 2>&1)"
|
||||
status=$?
|
||||
set -e
|
||||
[[ "$status" -eq 0 && "$output" == *"Agents: $count"* ]] || {
|
||||
printf 'Runbook %s roster (%s agents) did not resolve:\n%s\n' "$runbook" "$count" "$output" >&2
|
||||
exit 1
|
||||
}
|
||||
done < <(python3 - "$REPO_ROOT/strategy/runbooks.json" <<'PY'
|
||||
import json, sys
|
||||
for rb in json.load(open(sys.argv[1], encoding="utf-8"))["runbooks"]:
|
||||
ids = sorted({a for group in rb["roster"] for a in group["agents"]})
|
||||
print(f'{rb["slug"]}\t{len(ids)}\t{" ".join(ids)}')
|
||||
PY
|
||||
)
|
||||
|
||||
echo "PASS: install.sh rejects unknown agent selections and accepts display names, file stems, and runbook rosters"
|
||||
|
||||
Reference in New Issue
Block a user