v0.7.0 feat: Recruit — browse and install community agents#22
Merged
Conversation
Adds a new Recruit surface that pulls a curated registry of community agents from getcrew44/agent-registry and installs them locally. Install fetches AGENT.md and declared SKILL.md files pinned to a release tag that matches the manifest's version field (Obsidian-style integrity). Re-installing the same repo updates the existing agent in place; skills are deduped by (repo_url, in-repo path) so same-named skills from different agents don't collide. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…th HEAD A force-pushed tag could previously install successfully even when its crew44-agent.json declared a different version than the HEAD manifest that resolved it. ResolveTag now requires the pinned manifest's version field to match the requested version, with v-prefix tolerance. Mismatches surface as ErrVersionMismatch with both versions in the error message. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously ErrManifestInvalid, ErrUnsafePath, ErrRegistryInvalid,
ErrVersionMismatch, and GitHub URL parse failures bubbled up unmapped
and rpc.mapError defaulted them to CodeInternalError. The UI then
showed a generic "internal error" instead of the actual problem
("missing version", "unsafe path", etc).
mapRecruitError joins ErrBadRequest with the original recruit sentinel
at the app boundary, so the RPC layer translates these to -32000 and
errors.Is still detects the underlying sentinel for tests. Adds an
ErrRepoURLInvalid sentinel so parseGitHubRepo failures route through
the same path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
validateManifest now rejects manifests with a missing or non-v1 schema_version, closing the gap between the documented "required" status of that field and the runtime contract. FetchRegistry filters out entries missing any of the required fields (id, name, description, repo_url) and logs each drop, so a single broken community submission can't blank out the Recruit list for every user. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…load Add SourceType, Upstream (repo_url/commit/path/imported_at), and Payload (include/exclude) to the manifest. Add AgentSource.SourceDir and model.RecruitedSkill so a later installer commit can record the installed payload location and an agent-private skill index. Tighten validation to require skills[].path to end with SKILL.md, reject upstream-wrapper manifests without upstream.repo_url, and refuse gitignore-negation and traversal globs in Payload. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Build a focused matcher around path.Match that handles the subset of gitignore syntax the manifest's payload spec uses: ** for variable- depth wildcards, * and ? within a segment, and exact literal text. Anchored to repo root (no leading /) and rejects ! negation since v1.1 doesn't support it. Foundation for the archive extractor's include/exclude filtering. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add codeload tar.gz fetcher with on-disk cache under ~/.crew44/cache/recruit/github/<repohash>/<tag>/, plus an extractor that strips the wrapper directory, applies include/exclude globs, and copies only matching entries into the destination dir. Refuse symlinks, hard links, and any entry whose path contains a `..` segment; cap archive download at 50 MB, extracted payload at 200 MB, per-text-file at 1 MB, and total file count at 5,000. Skip binary entries via a null-byte heuristic on the first 8 KB of content so wrapper repos can carry image assets upstream without flooding the installed payload. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add per-agent path helpers (AgentDir, AgentSourceDir, AgentSourceTmpDir, AgentSourcePrevDir, RecruitedSkillsPath) and shared recruit cache paths (RecruitGithubCacheDir, RecruitTmpDir), plus LoadRecruitedSkills and WriteRecruitedSkillsTmp for the agent-private skill index. CommitAgentInstall parks the existing source/, recruited-skills.json, and config.json under .prev siblings, applies the new artifacts, saves config, then sweeps the .prev parking — rolling back the swap on any failure so the agent is never left half-installed. DeleteAgent's RemoveAll already handles the new source/ and recruited-skills.json paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…mmit Replace the per-file fetch path with a single tagged-archive download followed by a filtered extract into source.tmp-<install-id>/ under the agent dir. AGENT.md and every declared skills[].path must survive filtering or the install aborts before any rotation. recruited-skills metadata is generated against the final source/ path and staged alongside, then store.CommitAgentInstall swaps source/, recruited-skills.json, and config.json into place atomically. Agent records carry AgentSource.SourceDir so the runtime can resolve agent-private skill paths. resolveRunSkills merges global SkillIDs with the recruited skill index so the chat loop can later load both. Drops the pre-payload installer's global SkillRecord creation for recruited skills; a one-time purge clears legacy rows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plumb AgentSourceDir through RunRequest and inject it as CREW44_AGENT_SOURCE_DIR into the spawned runtime's env when set. chat.go fills the field from the agent's AgentSource and switches to resolveRunSkills so global SkillIDs plus the agent-private recruited skill index are both loaded for the run. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Build a deterministic tool that turns an arbitrary GitHub repo into a Crew44 wrapper agent repo: clone via tarball or copy from a local directory, scan for SKILL.md files, generate a templated AGENT.md and crew44-agent.json, and emit an IMPORT_REPORT.md summarizing what was included, skipped, and detected. A Generator hook is exposed for callers that want LLM-drafted AGENT.md, but the CLI uses the deterministic template so the importer stays hermetic. The generated manifest is re-validated against the daemon's own validator before write so a published wrapper can never trip the installer with a manifest the importer accepted. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Recruit detail page now shows the upstream repo URL plus short commit (when the manifest includes an upstream block) and a one-line note explaining that the installed payload lives under ~/.crew44/agents/agent-<id>/source/ so the runtime can read reference files locally. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move the crew44-agent.json manifest format and its pure helpers (validation, payload resolution, glob matching, GitHub repo parsing) into a new public daemon/recruit/schema package so external tooling can read and write Crew44 agent packages against a single source of truth. internal/recruit keeps a thin alias facade, so runtime call sites are unchanged. The recruit-import command is removed from this repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A failed first-time install left an empty agents/agent-<id> directory with no config.json, which ListAgents expects on every agent dir. Remove the whole agent dir on the fresh-install failure path (existing == nil and not committed). Also make legacy global skill cleanup truly best-effort: log instead of returning the error, since the agent install is already durably committed and a cleanup failure must not surface as a false install failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The agent's system-instruction file is now INSTRUCTIONS.md. Introduces schema.EntrypointFile as the single source of truth used by the payload resolver and the install/detail fetch path, so the daemon and the out-of-tree packager stay in lockstep. Updates tests and the Recruit UI copy.
- RichText: autolink http(s) URLs as new-tab anchors - RecruitRoute: render agent body as RichText, make repo URL clickable, drop source-payload blurb, rename "Designed for" -> "Recommended runtime" - archive: skip tar global/extended headers so GitHub codeload tarballs aren't misread as having multiple top-level directories Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-landing adversarial review surfaced two concrete robustness gaps: - writeJSON now writes to a temp file and renames, so a crash mid-write can't leave a truncated config.json. ListAgents skips an agent dir with a missing/unreadable config.json instead of failing the whole listing, so one interrupted install no longer hides every agent from the UI. - ExtractFilteredPayload counts all decompressed bytes pulled from gzip (including bytes drained from skipped binary entries) against the extracted-payload cap, closing a zip-bomb path where a small compressed tarball padded with skipped binaries forced unbounded decompression. Regression tests cover both paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…wser Drop the "X of Y agents" count footer and add a dedicated GitHub icon button to each Recruit row, kept out of the stats line so it reads as an action. Route all external links (target="_blank" and same-window navigation to external origins) through shell.openExternal so they open in the user's default browser — reusing an existing GitHub login — rather than a built-in Electron window. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88c9e19 to
a2d05fc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships the Recruit feature: browse community agent packages published as tagged GitHub releases and install them in one click.
Feature
src/RecruitRoute.jsx,src/RichText.jsx,src/Sidebar.jsx,src/App.jsx). Detail view renders INSTRUCTIONS.md as rich text, autolinks URLs, and links to the upstream repo.daemon/internal/recruit/*,daemon/internal/store/recruit.go,daemon/internal/app/recruit.go).daemon/recruit/schemapackage shared by the daemon installer and therecruit-importCLI so both enforce identical validation.CREW44_AGENT_SOURCE_DIRexposed to spawned runtimes for local reads of INSTRUCTIONS.md and declared skills.AGENT.md→INSTRUCTIONS.md.Hardening (from this PR's adversarial review)
config.jsonnow written atomically (temp + rename);ListAgentstolerates an incomplete agent dir instead of failing the whole listing — a crash mid-install can't hide every agent.Test Coverage
Every new schema/archive/client/store module has a matching
_test.go; the frontend addssrc/__tests__/recruit-route.test.jsx. New regression tests this PR:TestExtractFilteredPayloadBoundsBinaryDecompression,TestListAgentsSkipsAgentDirWithoutConfig. Security-relevant paths covered: traversal, symlink escape, oversize/too-many files, decompression bound, schema/glob validation.Pre-Landing Review
Reviewed the trust-boundary surface (archive extraction, GitHub fetch, glob/manifest validation, linkify). No critical issues —
safeJoin+ segment checks defend traversal, all link types are rejected, and the linkify regex only matcheshttp(s)://sojavascript:URLs can't render.Design Review
Frontend changes are styling-consistent with the existing Pretext-native UI. No AI-slop patterns flagged.
Adversarial Review (Codex + Claude)
6 findings. 2 concrete bugs fixed in this PR (config.json atomicity + zip-bomb drain, with regression tests). 4 trust-model/concurrency edges deferred to follow-up (HEAD-vs-tag instruction display, archive manifest re-verification, concurrent-install duplicate, reinstall resurrect/clobber) — tracked in local TODOS.md as P1.
Plan Completion
No plan file for this branch — n/a.
Test plan
🤖 Generated with Claude Code