diff --git a/.env.example b/.env.example index df6ca4a..457d69b 100644 --- a/.env.example +++ b/.env.example @@ -5,5 +5,6 @@ AGENT_ROUTER_WORKSPACE_ROOT=~/Projects AGENT_ROUTER_WEBHOOK_SECRET=change-me AGENT_ROUTER_RUNNER_TOKEN=change-me-too AGENT_ROUTER_SETUP_SECRET=change-me-three +AGENT_ROUTER_HUMA_AGENT=main GITHUB_APP_ID= GITHUB_PRIVATE_KEY_B64= diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3a8ac58 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# AgentRouter + +GitHub App router for explicit SummonAgent commands across Fischer's personal and HuntDynamics repositories. + +## Run + +```bash +pnpm install +pnpm verify +pnpm dev +pnpm runner --agent codex +pnpm runner --agent codex --execute --once +``` + +## Architecture + +- `src/server/` starts the HTTP webhook, setup callback, and runner API. +- `src/github/` parses GitHub events, manifests, and checks actor permissions. +- `src/store/` persists jobs in SQLite. +- `src/runner/` claims jobs for Codex, Claude, or Huma local lanes. + +## Project Rules + +- Triggering must stay explicit: `@SummonAgent codex`, `@SummonAgent claude`, or `@SummonAgent huma`. +- Raw `@codex`, `@claude`, and `@huma` mentions are ignored because Codex/Claude/Huma are not separate GitHub Apps. +- Whoever opened the PR owns the fix. PR jobs must stay on the matching fixed lane branch: + - `codex/workspace` + - `claude/workspace` + - `huma/workspace` +- Never act on bot-authored events. +- Keep repository allowlisting enabled before wiring real agent execution. +- Do not store GitHub private keys, webhook secrets, runner tokens, or agent API keys in git. + +## Definition Of Done + +Changes are complete when `pnpm verify` passes and any GitHub App behavior has been tested with a signed webhook fixture or a live test repository. diff --git a/README.md b/README.md index 2607511..a07f334 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Copy `.env.example` to `.env` and set: - `AGENT_ROUTER_SETUP_SECRET` - `GITHUB_APP_ID` - `GITHUB_PRIVATE_KEY_B64` +- `AGENT_ROUTER_HUMA_AGENT` optionally selects the OpenClaw agent id for the Huma lane. It defaults to `main`. `GITHUB_PRIVATE_KEY_B64` should be the GitHub App private key PEM encoded as base64. @@ -104,6 +105,7 @@ For a `@SummonAgent codex` PR job on `fschrhunt/AgentRouter`, that maps to `/Vol - No agent runs unless a comment or review contains `@SummonAgent` plus `codex`, `claude`, or `huma`. - Raw `@codex`, `@claude`, and `@huma` mentions are ignored. +- Codex, Claude, and Huma are local runner lanes behind this one GitHub App. They are not separate GitHub Apps. - Bot users are ignored. - Senders must have `write`, `maintain`, or `admin` repository permission. - Repositories can be allowlisted in `agent-router.config.json`. @@ -127,6 +129,8 @@ When a valid mention is accepted, AgentRouter stores a queued job. Runners claim For pull requests, jobs include the PR head branch and base branch. The router only queues a PR job when the PR head branch matches the mentioned agent's fixed workspace branch in the same repository. +The Huma lane runs through OpenClaw with `openclaw agent --agent ${AGENT_ROUTER_HUMA_AGENT:-main} --message ...`. + ## Development ```bash diff --git a/src/runner/main.ts b/src/runner/main.ts index 8ee54e7..d9873f4 100644 --- a/src/runner/main.ts +++ b/src/runner/main.ts @@ -159,7 +159,14 @@ function runAgent(agent: AgentName, target: WorktreeTarget, prompt: string): voi return; } - throw new Error("Huma execution is not wired yet; job was not run."); + const humaAgent = process.env.AGENT_ROUTER_HUMA_AGENT ?? "main"; + runStreaming("openclaw", [ + "agent", + "--agent", + humaAgent, + "--message", + prompt, + ], target.worktreeDir); } function runText(command: string, args: string[], cwd: string, allowFailure = false): string { diff --git a/src/server/router.ts b/src/server/router.ts index 3d4174d..a784727 100644 --- a/src/server/router.ts +++ b/src/server/router.ts @@ -79,7 +79,7 @@ export function evaluatePullRequestRouting( allowed: false, reason: "fork head repository", message: [ - `Blocked @${trigger.agent} job: I only push fixes to branches in \`${trigger.repository}\`.`, + `Blocked SummonAgent ${trigger.agent} job: I only push fixes to branches in \`${trigger.repository}\`.`, `This PR head is \`${pr.headRepository}:${pr.headBranch}\`, so I will not create a new branch or push to a fork.`, ].join("\n\n"), }; @@ -90,7 +90,7 @@ export function evaluatePullRequestRouting( allowed: false, reason: "unexpected head branch", message: [ - `Blocked @${trigger.agent} job: I only push fixes to the fixed \`${expectedBranch}\` lane branch for @${trigger.agent}.`, + `Blocked SummonAgent ${trigger.agent} job: I only push fixes to the fixed \`${expectedBranch}\` lane branch for that lane.`, `This PR head is \`${pr.headBranch}\`. I will not create a new branch or move work onto a different branch.`, ].join("\n\n"), }; @@ -101,10 +101,10 @@ export function evaluatePullRequestRouting( function queuedMessage(jobId: number, trigger: TriggerRequest): string { if (!trigger.isPullRequest) { - return `Queued @${trigger.agent} job #${jobId}.`; + return `Queued SummonAgent ${trigger.agent} job #${jobId}.`; } return [ - `Queued @${trigger.agent} job #${jobId}.`, + `Queued SummonAgent ${trigger.agent} job #${jobId}.`, `Branch policy: fixes will use \`${trigger.pullRequestHeadBranch}\` directly. No new branch will be created.`, ].join("\n\n"); }