Commit Graph
458 Commits
Author SHA1 Message Date
Rudy Celekli 37678803bf fix(installer): refuse destination file symlinks when copying 2026-09-29 13:58:44 -04:00
Michael SitarzewskiandClaude Opus 5.5 765dbfb652 Merge @rudycelekli's hardening series (#891–#906) + symlink fix (#912)
Installer, converter and CI hardening from @rudycelekli (15 PRs, each with a regression test that fails before its fix), plus a maintainer fix so the Hermes installer never follows a symlink when replacing its plugin. Tested with a 25-scenario destructive-path matrix (25/25), signal tests, real Hermes (install, upgrade, live delegation), and all 17 suites on macOS and Linux.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:26:27 -05:00
Michael SitarzewskiandClaude Opus 5.5 3963190e14 ci: run the parallel-failure and unclosed-frontmatter tests from #901 and #904
Both tests shipped with their fixes but no workflow invoked them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:09:26 -05:00
Michael SitarzewskiandClaude Opus 5.5 d801ff90ad fix(install): never follow a symlink when replacing the Hermes plugin
`rm -rf "$dest/"` follows a symlink and empties its target. basename ignores a
trailing slash, so HERMES_PLUGIN_DIR=".../agency-agents-router/" passed every
check, and a symlinked destination (our own --link install, or a user's folder)
had its target's contents deleted. #903's ownership check didn't cover it: the
check reads through the link and the rm still follows it.

- Strip all trailing slashes from the destination before any check.
- A symlinked destination is removed with `rm -f` (the link itself), never
  `rm -rf`.
- test-install-hermes-destination.sh: two new cases (trailing slash on a
  symlink to a folder with a matching plugin.yaml; re-install over a --link
  install). Both fail before this commit and pass after.

Found by a destructive-path matrix (25 scenarios, throwaway container, fake HOME
with sentinel files): main fails 10 (deletes unrelated dirs, files and symlink
targets at the plugin path); the series without this commit fails 2 (H8/H9);
with it, 25/25.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:56:23 -05:00
Michael SitarzewskiandClaude Opus 5.5 d2d2101c45 chore(eval): regenerate the convert-outputs manifest for the integrated series
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:34:01 -05:00
Michael Sitarzewski fe52dd6a2e Merge branch 'rudy-895' into integ/rudy-series
# Conflicts:
#	scripts/convert-outputs.sha256
2026-09-29 10:25:03 -05:00
Michael Sitarzewski d2089289b8 Merge branch 'rudy-906' into integ/rudy-series
# Conflicts:
#	.github/workflows/check-tools.yml
2026-09-29 10:25:02 -05:00
Michael Sitarzewski cb156e0b15 Merge branch 'rudy-902' into integ/rudy-series
# Conflicts:
#	.github/workflows/check-tools.yml
2026-09-29 10:25:02 -05:00
Michael Sitarzewski 6671f71765 Merge branch 'rudy-900' into integ/rudy-series
# Conflicts:
#	.github/workflows/check-tools.yml
2026-09-29 10:25:01 -05:00
Michael Sitarzewski 9abb989416 Merge branch 'rudy-904' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski c59cff9ce5 Merge branch 'rudy-901' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 7a0f97ca58 Merge branch 'rudy-898' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski ea3e7e5322 Merge branch 'rudy-897' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 731efe6c9b Merge branch 'rudy-896' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 611d0448e7 Merge branch 'rudy-894' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 3cc60d0e3f Merge branch 'rudy-893' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 545b569e30 Merge branch 'rudy-892' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 3c195668b0 Merge branch 'rudy-891' into integ/rudy-series 2026-09-29 10:24:41 -05:00
Michael Sitarzewski 747ad1576d Merge branch 'rudy-899' into integ/rudy-series 2026-09-29 10:24:41 -05:00
Michael Sitarzewski 71753b1b3f Merge branch 'rudy-903' into integ/rudy-series 2026-09-29 10:24:41 -05:00
fruitandClaude Opus 5.5 68f01534ef fix(install): Hermes plugin actually gets enabled on real Hermes configs (#881)
The Hermes installer printed "[OK] enabled plugin" and "Done!" while leaving agency-agents-router disabled or absent. On current Hermes this hit every user: the default plugins: block contains a column-0 "# ====" banner, and `hermes plugins enable/disable` writes enabled:/disabled: below it.

- Bound the enabled: sub-block at the first sibling key; sweep stale entries out of disabled: (Hermes: "an explicit disable wins"); handle inline [], [a,b], disabled: above enabled:, trailing comments, corrupted-scalar recovery; bail on shapes that can't be edited line-wise. (@guozi-lab)
- A column-0 comment no longer ends the plugins: block, and the rewrite's exit status propagates (`|| return 1`) so a bail warns instead of reporting success. (maintainer follow-up)
- check-hermes-config-rewrite.py: 16 cases, including Hermes' real shape.

Verified in real Hermes (official installer, 9a0a162) on configs Hermes wrote itself, with a live turn on local qwen3.8-27b: main -> "agency_agents_delegate does not exist"; this change -> delegated: true, real subagent, real result.

Closes #879

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 07:03:44 -05:00
Rudy Celekli 9445c9cbee fix(installer): avoid SIGPIPE when checking selected agents 2026-09-28 01:19:06 -04:00
Rudy Celekli 7ed03ed5e7 fix(validation): reject unclosed agent frontmatter 2026-09-28 01:08:48 -04:00
Rudy Celekli 4e2d91d687 fix(installer): protect unrelated Hermes destinations 2026-09-28 01:05:47 -04:00
Rudy Celekli 09eba4b057 fix(convert): surface parallel failures and clean buffered logs 2026-09-28 01:00:07 -04:00
Rudy Celekli 070640f34c fix(installer): guard shared paths for detected tools 2026-09-28 00:56:56 -04:00
Rudy Celekli 25d6c10b92 fix(installer): stop after failed automatic conversion 2026-09-28 00:49:42 -04:00
Rudy Celekli fda3beeaba fix(convert): refuse duplicate agent slugs before writing 2026-09-28 00:32:16 -04:00
Rudy Celekli 8fba721291 fix(ci): reject undeployable runbook rosters 2026-09-28 00:20:02 -04:00
Rudy Celekli 435cf1d232 fix(ci): reject empty required agent metadata 2026-09-28 00:16:58 -04:00
Rudy Celekli 0249ecd37f fix(ci): select direct agent files for lint and originality 2026-09-28 00:05:15 -04:00
Rudy Celekli 2f1008453b fix(hermes): preserve continued frontmatter metadata 2026-09-27 23:54:05 -04:00
Rudy Celekli 77a898401a fix(eval): preserve manifest when output validation fails 2026-09-27 23:47:25 -04:00
Rudy Celekli 2bc53022ef fix(installer): honor --path for Aider roster 2026-09-27 23:39:04 -04:00
Rudy Celekli 3e7dda0ae7 fix(installer): honor single Copilot destination override 2026-09-27 23:32:32 -04:00
Rudy Celekli a47212e9e5 fix(convert): preserve Markdown separators in agent bodies 2026-09-27 23:24:27 -04:00
Roberto SolanoandClaude Opus 5.5 479193dcce feat(integration): add DeepSeek Harness (dsh) skills support (#846)
Adds DeepSeek Harness as an integration target: 279 agency-* SKILL.md skills installed to ${DSH_HOME:-~/.dsh}/skills (or DSH_SKILLS_DIR for project scope).

Verified in DeepSeek Harness itself (@deepseek-ai/dsh 0.1.7-rc.2, installed via the documented npx path on Ubuntu 26.04 arm64): the repo installer placed 279 skills, and dsh's own skill-filesystem parser accepted all 279 with zero warnings (a planted invalid name and a missing description were both rejected with dsh's own messages).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 18:45:36 -05:00
Michael SitarzewskiandClaude Opus 5 053ddbbf39 docs(readme): remove the Related Resources section (#878)
The section held one entry, describing awesome-openclaw-agents as "derived
from this repo". That claim does not hold up: its README never mentions this
project, and only 2 of our 265 agent names appear anywhere in its list —
"incident responder" and "ux researcher", both generic role names that are
coincidence rather than lineage. It was last pushed 2026-05-25.

Asserting a derivation the other project does not claim, and that the content
does not support, is not something to leave in the README.

The section was also generating work it could not pay for. #876 asked to add a
third-party workbench to it — a reasonable thing to attempt precisely because
the section existed — and declining that meant explaining a curation standard
the one existing entry did not meet.

Removing it rather than correcting the line: a list of one, kept honest by
hand, invites requests to grow it and gives no way to judge them.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 21:38:51 -05:00
Michael SitarzewskiandClaude Opus 5 652277fba1 fix(install): tell aider users when their CONVENTIONS.md is our stale roster (#875)
#871 shrank the aider integration from 3.8M characters of concatenated agents
to a 97K roster index, because aider keeps a conventions file in context for a
whole session. The fix only reached new installs.

install_aider refuses to overwrite an existing CONVENTIONS.md, which is right —
it is aider's own user-authored file and the one on disk may be the reader's.
But "already exists (remove to reinstall)" says nothing about which file it is,
so anyone holding the pre-index roster re-ran the installer, read that, and
kept the broken file. The people the fix was written for were the ones it could
not reach.

Our generated file has always opened with "# The Agency — AI Agent
Conventions", so the installer can now tell its own stale copy from someone
else's conventions and say which it is. Neither branch writes anything.

Verified in a container, all three cases:
  stale Agency roster   -> names it, reports 3800039 bytes, says to delete and re-run
  user's own file       -> "leaving your file alone", file intact
  no file               -> installs, 97065 bytes

Windsurf has the same guard and the same gap; #870 already handles it there, so
this leaves that path alone rather than colliding with it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 20:44:43 -05:00
Michael SitarzewskiandClaude Opus 5 87f8301cad chore(eval): regenerate the convert-outputs manifest for #871 and #872
Both PRs change generators, so every agent line moves: #871 rewrites the aider
CONVENTIONS.md as a roster index rather than 3.8M characters of concatenated
agents, and #872 teaches resolve_opencode_color() the `slate` and `navy` names
that were silently falling through to grey.

Neither PR carried the manifest, so main was left in drift.

Regenerated from a clean detached worktree at origin/main rather than the
managed clone: `--update` walks the working tree, and an untracked agent file
in that clone would have added a 280th line for a file no one else has.

Verified: 29 passed, 0 failed (279 roster agents x 14 tools).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 18:39:14 -05:00
Hotragn Pettugani 1f92396556 fix(convert): two color names fall through to grey, and nothing checked (#872)
* fix(convert): two color names fall through to grey, and nothing checked

resolve_opencode_color() maps a name it does not recognise to #6B7280 and
says nothing about it. Four agents ask for a name it has never known:

  engineering/engineering-minimal-change-engineer.md   slate
  specialized/operations-manager.md                    slate
  gis/gis-technical-consultant.md                      navy
  specialized/chief-financial-officer.md               navy

All four render grey in the OpenCode integration, which reads as a
deliberate grey rather than a miss — and some agents do choose grey, so
there was nothing to notice.

`slate` and `navy` are now in the map. Values follow the CSS named color
where one exists (navy -> #000080, matching how teal already works) and
Tailwind's 500 shade otherwise (slate -> #64748B, matching gray).

Adding two names does not stop the next one, so two checks now cover it:

  - lint-agents.sh rejects a color that is neither #RRGGBB nor a name the
    converter knows, and prints the names it knows. The list is read out
    of resolve_opencode_color() rather than copied, so it cannot drift
    from the map that does the work. This runs on changed files in agent
    PRs, which is where a new color arrives.

  - test-convert-outputs.sh fails when an opencode output is #6B7280 and
    the source did not ask for grey. Grey stays a legitimate choice; the
    check is "grey only when the source said so".

CONTRIBUTING now says which color values actually work, since the template
just said `colorname or "#hexcode"`.

Verified: all 279 agents pass the new lint rule, and no opencode output
falls through to grey by accident.

* ci(lint): run the whole roster when the linter itself changes

The lint job scopes itself to the agent files a PR touched, which is right
for an agent PR and wrong for a rule change. A new rule lands without ever
having run against the other 278 agents — it either breaks a division
nobody edited or quietly does nothing, and either way CI is silent.

The colour check in the previous commit is the case in point: it changes
scripts/, so the lint workflow's path filter did not even fire, and the
rule would have merged without CI looking at a single agent.

Adds a lint-all job that runs the full-roster lint when scripts/
lint-agents.sh, convert.sh, or lib.sh is part of the diff, and says why it
is skipping when they are not. Those three paths are in the workflow's
trigger list now so the job can fire at all.
2026-09-20 18:35:13 -05:00
Hotragn Pettugani d3a3f573e3 fix(aider): CONVENTIONS.md is a roster index, not 3.8 million characters of agents (#871)
Aider loads a conventions file into context and keeps it there for the whole
session — that is what the file is for, and the docs say to load it with
--read so prompt caching can hold it. This integration concatenated every
agent body into it. At 279 agents that is 3,816,372 characters, roughly a
million tokens. No model takes that. Anyone who ran

    ./scripts/install.sh --tool aider

got a CONVENTIONS.md that either blows the context window on the first turn
or bills for a million tokens trying.

CONVENTIONS.md is now the roster index it was described as: one entry per
agent with the name, the description, the division, and the path to the
agent file. 96,823 characters, down from 3.8 million. The header explains
how to pull a single agent's full instructions into the session:

    /read-only /path/to/agency-agents/engineering/engineering-frontend-developer.md

Naming an agent in a prompt still works the way it did — the description is
what the model needed for that, and it is still there.

test-convert-outputs.sh now holds the index to being an index: it fails if
CONVENTIONS.md grows past 250,000 characters, if it does not list exactly
one path per roster agent, or if any path it prints does not resolve. The
existing round-trip check on the accumulated file still covers the names and
descriptions.
2026-09-20 18:35:10 -05:00
ad9264e309 fix(convert): track code fences when splitting OpenClaw sections, and pin it with an invariant (#855)
convert_openclaw() split on `## ` without tracking fenced code blocks, tearing six fenced examples across SOUL.md/AGENTS.md in five agents and leaving dangling fences in each. Implementation from #854 (@guozi-lab): fence helpers in lib.sh used by convert.sh and lint-agents.sh, CommonMark indent aware. Regression test from #853 (@dajiaohuang): every source fenced block must land whole in exactly one output file — verified to fail on the unfixed converter.

Reported by @AmineOzil. Closes #849. Supersedes #853 and #854.

Co-Authored-By: fruit <200041037+guozi-lab@users.noreply.github.com>
Co-Authored-By: Wu Shuwen <108231307+dajiaohuang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:57:20 -05:00
6d29a9b087 feat(agents): land six specialists — China Network, Platform, Focus Music, PDF Engine, ATS Validator, Universal Document Compiler (#845)
Consolidated landing of #768, #811, #808, #822, #842, #843 — agent files as submitted, README roster rows, regenerated Hermes README, manifest v2 (+6 agent lines). Verified together: lint, originality, guards, converted-frontmatter, outputs eval 26/26 (279 agents x 14 tools), installer 36/0, agent-selection, Hermes checks; PR CI green on all 7.

Closes #768. Closes #811. Closes #808. Closes #822. Closes #842. Closes #843.

Co-Authored-By: Sagarika Sultana <283121436+madebysaira@users.noreply.github.com>
Co-Authored-By: Sunil Kumar <24809771+sunilkumarvalmiki@users.noreply.github.com>
Co-Authored-By: augustoheiss <240949329+augustoheiss@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 19:10:22 -05:00
Michael SitarzewskiandClaude Fable 5.1 449e0c71b1 test(convert): manifest v2 — one line per agent, platform-neutral hashes, advisory drift on PRs (#844)
See the PR for the why/what/proof. Contributors no longer touch scripts/convert-outputs.sha256; drift is advisory on pull requests and strict on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:59:03 -05:00
647c8baa42 fix(install): preserve list-item indent when adding the Hermes plugin; regression test + CI (#714)
ensure_hermes_plugin_enabled() hardcoded a 2-space indent for the inserted `- agency-agents-router` line. Hermes writes plugins.enabled items at 4 spaces, so the new line and the next existing item collapsed into one plain scalar ("agency-agents-router - disk-cleanup") and every previously enabled plugin silently dropped (#839, and #689 diagnosed the same in July). The inserter now matches the existing item indent (default 4), appends after existing entries so order is preserved, and is idempotent on re-run. scripts/check-hermes-config-rewrite.{sh,py} runs seven regression cases; a small workflow runs it on every PR.

Reproduced on main with a 4-space config and verified fixed with this patch; installer suite 36/0; the regression script passes 7/7.

Fixes #839. Closes #689 (same fix, proposed first by @harshsinghmp).

Co-Authored-By: Harsh Singh <32476777+harshsinghmp@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 15:47:19 -05:00
Michael SitarzewskiandClaude Fable 5.1 1454492577 chore(eval): update the convert-outputs manifest for the Hermes builder change (#803)
#803 changed scripts/build-hermes-plugin.py, so the generated Hermes plugin legitimately
changed and the drift manifest's hermes line went stale — main's "Validate converted outputs"
step failed on drift alone (25/26 passed; every invariant held). Regenerated with
scripts/test-convert-outputs.sh --update on a clean checkout of main; the suite is 26/26.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 12:40:16 -05:00
wnwhermesandClaude Fable 5.1 80b338fea8 fix(hermes): delegate through Hermes' public subagent lifecycle instead of nested delegate_task (#803)
agency_agents_delegate previously nested delegate_task through ctx.dispatch_tool(), which returns error JSON as a string instead of raising — so the router reported delegated: true with {"error": "delegate_task requires a parent agent context."} and never delegated (#802, #838). It now launches the specialist through ctx.subagent_lifecycle (upstream hermes-agent PR #72501): bounded wait, cooperative cancel on timeout, honest delegated: false + fallback prompt on launch/terminal failure, 32,000-char context cap, toolsets option removed. Kept in scripts/build-hermes-plugin.py so regeneration preserves it; six behavior tests in scripts/test-hermes-plugin.py.

Verified live in an isolated Hermes git-main (v0.21.0) box on local Qwen: main plugin -> delegated: true with the error string in 0.01 s; this plugin -> real child session, real result.

Fixes #802. Fixes #838.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 12:32:07 -05:00
af128a9288 fix(install): honor CLAUDE_CONFIG_DIR as config root, not agents dir (#834)
resolve_dest() for claude-code now appends /agents to CLAUDE_CONFIG_DIR (the config root that replaces ~/.claude); a value already ending in /agents is used verbatim and a trailing slash is stripped. detect_claude_code() honors CLAUDE_CONFIG_DIR so relocated configs are detected. Three regression cases in scripts/test-install.sh.

Fixes #578. The diagnosis and the same fix were first proposed by @halindrome in #579 (June); this lands the current-main version with tests.

Co-Authored-By: Shane McCarron <520688+halindrome@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 13:06:36 -05:00
fruitandClaude Fable 5.1 882d4cda89 docs(opencode): warn against manual source file copying (#833)
Top-of-file note in integrations/opencode/README.md: copying source .md files into .opencode/agents/ fails OpenCode schema validation; run scripts/install.sh --tool opencode, which resolves named colors to hex and omits the tools field. Follow-up to #832 / #796.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 13:03:40 -05:00