diff --git a/.env.example b/.env.example index 457d69b..c97e98f 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +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 +AGENT_ROUTER_LOCAL_AGENT=main GITHUB_APP_ID= GITHUB_PRIVATE_KEY_B64= diff --git a/AGENTS.md b/AGENTS.md index 3a8ac58..c73c0d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AgentRouter -GitHub App router for explicit SummonAgent commands across Fischer's personal and HuntDynamics repositories. +GitHub App router for explicit SummonAgent commands across selected personal and organization repositories. ## Run @@ -17,16 +17,16 @@ pnpm runner --agent codex --execute --once - `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. +- `src/runner/` claims jobs for Codex, Claude, or a local OpenClaw-backed lane. ## 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. +- Triggering must stay explicit: `@SummonAgent codex`, `@SummonAgent claude`, or the configured local third lane. +- Raw `@codex`, `@claude`, and raw third-lane mentions are ignored because individual agents 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` + - `local/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. diff --git a/README.md b/README.md index a07f334..31dbc3e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ It listens for GitHub comments and reviews, validates that the sender is allowed - `@SummonAgent codex fix this` - `@SummonAgent claude review this` -- `@SummonAgent huma handle this` +- `@SummonAgent local handle this` The first version is intentionally conservative: it stores jobs locally, exposes a small runner API, and only dispatches when `@SummonAgent` is mentioned with a supported lane name. No ambient code review, no surprise bot activity, no repo-by-repo workflow file required. @@ -44,13 +44,13 @@ pnpm dev 3. Open the setup URL for your personal account: ```bash -pnpm setup:url fschrhunt user +pnpm setup:url YOUR_USER user ``` -For Hunt. org setup, use: +For organization setup, use: ```bash -pnpm setup:url HuntDynamics org +pnpm setup:url YOUR_ORG org ``` 4. GitHub will redirect back to `/setup/callback`. AgentRouter exchanges the temporary manifest code and writes generated credentials to `.state/github-app.env` with file mode `0600`. @@ -63,7 +63,7 @@ pnpm dev # start the webhook server pnpm runner --agent codex # preview the next Codex job pnpm runner --agent codex --execute --once # claim one job and run Codex locally -pnpm setup:url fschrhunt user # generate a GitHub App manifest URL +pnpm setup:url YOUR_USER user # generate a GitHub App manifest URL pnpm test # run tests pnpm verify # typecheck and test ``` @@ -77,7 +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`. +- `AGENT_ROUTER_LOCAL_AGENT` optionally selects the OpenClaw agent id for the local third lane. It defaults to `main`. `GITHUB_PRIVATE_KEY_B64` should be the GitHub App private key PEM encoded as base64. @@ -91,21 +91,26 @@ The runner resolves jobs to fixed local worktrees from `workspaceRoot` and `repo ```json { - "workspaceRoot": "/Volumes/Thorium/Projects", + "workspaceRoot": "~/Projects", "repositories": { - "fschrhunt/AgentRouter": "AgentRouter", - "HuntDynamics/Halos": "Halos" + "OWNER/REPOSITORY": "Repository", + "ORG/REPOSITORY": "OrgRepository" + }, + "laneBranches": { + "codex": "codex/workspace", + "claude": "claude/workspace", + "local": "local/workspace" } } ``` -For a `@SummonAgent codex` PR job on `fschrhunt/AgentRouter`, that maps to `/Volumes/Thorium/Projects/AgentRouter/codex` and requires the branch to already be `codex/workspace`. +For a `@SummonAgent codex` PR job on `OWNER/REPOSITORY`, that maps to `~/Projects/Repository/codex` and requires the branch to already be `codex/workspace`. ## Safety Rules -- 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. +- No agent runs unless a comment or review contains `@SummonAgent` plus `codex`, `claude`, or `local`. +- Raw `@codex`, `@claude`, and `@local` mentions are ignored. +- Codex, Claude, and the local third lane are 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`. @@ -113,7 +118,8 @@ For a `@SummonAgent codex` PR job on `fschrhunt/AgentRouter`, that maps to `/Vol - PR fix jobs only run on fixed lane branches: - `@SummonAgent codex` -> `codex/workspace` - `@SummonAgent claude` -> `claude/workspace` - - `@SummonAgent huma` -> `huma/workspace` + - `@SummonAgent local` -> `local/workspace` +- Deployment-specific branch names can be set in `laneBranches`. - AgentRouter blocks fork PRs and unexpected PR branches instead of creating new branches. ## Webhook Events @@ -129,7 +135,7 @@ 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 ...`. +The third lane runs through OpenClaw with `openclaw agent --agent ${AGENT_ROUTER_LOCAL_AGENT:-main} --message ...`. ## Development diff --git a/agent-router.config.example.json b/agent-router.config.example.json index 781611d..c085a73 100644 --- a/agent-router.config.example.json +++ b/agent-router.config.example.json @@ -1,11 +1,16 @@ { "workspaceRoot": "~/Projects", "allowedRepositories": [ - "fschrhunt/AgentRouter", - "HuntDynamics/Halos" + "OWNER/REPOSITORY", + "ORG/REPOSITORY" ], "repositories": { - "fschrhunt/AgentRouter": "AgentRouter", - "HuntDynamics/Halos": "Halos" + "OWNER/REPOSITORY": "Repository", + "ORG/REPOSITORY": "OrgRepository" + }, + "laneBranches": { + "codex": "codex/workspace", + "claude": "claude/workspace", + "local": "local/workspace" } } diff --git a/package.json b/package.json index c5078b2..116c51d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "type": "module", - "description": "GitHub App router for @SummonAgent codex, claude, and huma commands.", + "description": "GitHub App router for @SummonAgent codex, claude, and local commands.", "scripts": { "dev": "tsx src/server/main.ts", "runner": "tsx src/runner/main.ts", diff --git a/src/config.ts b/src/config.ts index bf0847b..381e19b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,6 +19,7 @@ const fileConfigSchema = z.object({ allowedRepositories: z.array(z.string()).default([]), workspaceRoot: z.string().optional(), repositories: z.record(z.string(), z.string()).default({}), + laneBranches: z.record(z.string(), z.string()).default({}), }); export interface AppConfig { @@ -33,6 +34,7 @@ export interface AppConfig { githubPrivateKey?: string; allowedRepositories: Set; repositories: Map; + laneBranches: Map; } export function loadConfig(cwd = process.cwd()): AppConfig { @@ -56,6 +58,7 @@ export function loadConfig(cwd = process.cwd()): AppConfig { : undefined, allowedRepositories: new Set(fileConfig.allowedRepositories.map((repo) => repo.toLowerCase())), repositories: new Map(Object.entries(fileConfig.repositories).map(([repo, localName]) => [repo.toLowerCase(), localName])), + laneBranches: new Map(Object.entries(fileConfig.laneBranches).map(([lane, branch]) => [lane.toLowerCase(), branch])), }; } diff --git a/src/github/events.ts b/src/github/events.ts index 0cf1f50..5301707 100644 --- a/src/github/events.ts +++ b/src/github/events.ts @@ -6,7 +6,7 @@ const summonMention = /\B@summonagent\b/i; const commandPatterns: Array<[AgentName, RegExp]> = [ ["codex", /\bcodex\b/i], ["claude", /\bclaude\b/i], - ["huma", /\bhuma\b/i], + ["local", /\b(?:local|openclaw)\b/i], ]; export function detectAgentMention(body: string): AgentName | null { diff --git a/src/github/manifest.ts b/src/github/manifest.ts index d3ef8c1..ab6aee9 100644 --- a/src/github/manifest.ts +++ b/src/github/manifest.ts @@ -29,7 +29,7 @@ export function buildManifest(config: AppConfig, params: ManifestParams): Record redirect_url: `${config.publicUrl}/setup/callback`, callback_urls: [`${config.publicUrl}/setup/callback`], setup_url: `${config.publicUrl}/setup/done`, - description: "Routes explicit @SummonAgent codex/claude/huma commands to local agent lanes.", + description: "Routes explicit @SummonAgent codex/claude/local commands to local agent lanes.", public: false, default_permissions: { contents: "read", diff --git a/src/runner/main.ts b/src/runner/main.ts index d9873f4..2736128 100644 --- a/src/runner/main.ts +++ b/src/runner/main.ts @@ -7,7 +7,7 @@ import { buildAgentPrompt } from "./prompt.js"; import { resolveWorktreeTarget, type WorktreeTarget } from "./workspace.js"; const program = new Command() - .requiredOption("--agent ", "agent lane: codex, claude, or huma") + .requiredOption("--agent ", "agent lane: codex, claude, or local") .option("--server ", "AgentRouter server URL", "http://localhost:8787") .option("--once", "claim one job and exit", false) .option("--execute", "execute the local agent instead of only previewing the next job", false); @@ -15,8 +15,8 @@ const program = new Command() program.parse(); const options = program.opts<{ agent: AgentName; server: string; once: boolean; execute: boolean }>(); -if (!["codex", "claude", "huma"].includes(options.agent)) { - throw new Error("agent must be codex, claude, or huma"); +if (!["codex", "claude", "local"].includes(options.agent)) { + throw new Error("agent must be codex, claude, or local"); } const config = loadConfig(); @@ -159,11 +159,11 @@ function runAgent(agent: AgentName, target: WorktreeTarget, prompt: string): voi return; } - const humaAgent = process.env.AGENT_ROUTER_HUMA_AGENT ?? "main"; + const localAgent = process.env.AGENT_ROUTER_LOCAL_AGENT ?? "main"; runStreaming("openclaw", [ "agent", "--agent", - humaAgent, + localAgent, "--message", prompt, ], target.worktreeDir); diff --git a/src/runner/workspace.ts b/src/runner/workspace.ts index 7c18a24..7d803ad 100644 --- a/src/runner/workspace.ts +++ b/src/runner/workspace.ts @@ -10,7 +10,7 @@ export interface WorktreeTarget { export function resolveWorktreeTarget(config: AppConfig, job: AgentJob): WorktreeTarget { const repositoryDir = config.repositories.get(job.repository.toLowerCase()) ?? job.repo; - const branch = job.pullRequestHeadBranch ?? `${job.agent}/workspace`; + const branch = job.pullRequestHeadBranch ?? config.laneBranches.get(job.agent) ?? `${job.agent}/workspace`; const lane = laneFromBranch(branch); return { repositoryDir, @@ -20,7 +20,7 @@ export function resolveWorktreeTarget(config: AppConfig, job: AgentJob): Worktre } export function laneFromBranch(branch: string): string { - const match = /^(codex|claude|huma)\/workspace$/.exec(branch); + const match = /^([a-z0-9-]+)\/workspace$/i.exec(branch); if (!match) { throw new Error(`unsupported agent branch: ${branch}`); } diff --git a/src/server/router.ts b/src/server/router.ts index a784727..bc0c893 100644 --- a/src/server/router.ts +++ b/src/server/router.ts @@ -38,7 +38,7 @@ export async function handleWebhook( if (trigger.isPullRequest) { const pr = await getPullRequestContext(octokit, trigger); - const routing = evaluatePullRequestRouting(trigger, pr); + const routing = evaluatePullRequestRouting(trigger, pr, deps.config); if (!routing.allowed) { await postIssueComment(octokit, trigger, routing.message); return { status: 202, body: { blocked: true, reason: routing.reason } }; @@ -61,19 +61,20 @@ export function isRunnerAuthorized(expected: string, provided: string | undefine } export function parseAgent(value: string | null): AgentName | null { - if (value === "codex" || value === "claude" || value === "huma") return value; + if (value === "codex" || value === "claude" || value === "local") return value; return null; } -export function fixedLaneBranch(agent: AgentName): string { - return `${agent}/workspace`; +export function fixedLaneBranch(agent: AgentName, config?: AppConfig): string { + return config?.laneBranches.get(agent) ?? `${agent}/workspace`; } export function evaluatePullRequestRouting( trigger: TriggerRequest, pr: PullRequestContext, + config?: AppConfig, ): { allowed: true } | { allowed: false; reason: string; message: string } { - const expectedBranch = fixedLaneBranch(trigger.agent); + const expectedBranch = fixedLaneBranch(trigger.agent, config); if (pr.headRepository.toLowerCase() !== trigger.repository.toLowerCase()) { return { allowed: false, diff --git a/src/server/setup.ts b/src/server/setup.ts index 8b1236d..da78aaf 100644 --- a/src/server/setup.ts +++ b/src/server/setup.ts @@ -54,7 +54,7 @@ export async function handleSetupCallback(config: AppConfig, cwd: string, url: U AgentRouter App Created -

AgentRouter GitHub App Created

+

Repository GitHub App Created

App: ${escapeHtml(conversion.slug)}

Credentials were written to ${escapeHtml(envPath)} with mode 0600.

Copy those values into your runtime environment, then restart AgentRouter.

@@ -64,7 +64,7 @@ export async function handleSetupCallback(config: AppConfig, cwd: string, url: U } export function parseManifestParams(url: URL): ManifestParams { - const account = url.searchParams.get("account") || "fschrhunt"; + const account = url.searchParams.get("account") || "OWNER"; const accountType = url.searchParams.get("type") === "org" ? "org" : "user"; const appName = url.searchParams.get("name") || undefined; return { account, accountType, appName }; diff --git a/src/setup-url.ts b/src/setup-url.ts index 359a240..717b92f 100644 --- a/src/setup-url.ts +++ b/src/setup-url.ts @@ -1,7 +1,7 @@ import { loadConfig } from "./config.js"; const config = loadConfig(); -const account = process.argv[2] ?? "fschrhunt"; +const account = process.argv[2] ?? "OWNER"; const type = process.argv[3] === "org" ? "org" : "user"; const url = new URL("/setup", config.publicUrl); url.searchParams.set("account", account); diff --git a/src/types.ts b/src/types.ts index d66be02..07a578f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -export type AgentName = "codex" | "claude" | "huma"; +export type AgentName = "codex" | "claude" | "local"; export type TriggerSource = | "issue_comment" diff --git a/test/events.test.ts b/test/events.test.ts index 373b73a..cf4bf96 100644 --- a/test/events.test.ts +++ b/test/events.test.ts @@ -5,13 +5,13 @@ describe("detectAgentMention", () => { it("detects supported agent commands after SummonAgent is mentioned", () => { expect(detectAgentMention("@SummonAgent codex fix this")).toBe("codex"); expect(detectAgentMention("@summonagent claude review this")).toBe("claude"); - expect(detectAgentMention("route to @SummonAgent huma")).toBe("huma"); + expect(detectAgentMention("route to @SummonAgent local")).toBe("local"); }); it("does not trigger on raw agent mentions", () => { expect(detectAgentMention("please @codex fix this")).toBeNull(); expect(detectAgentMention("@claude review this")).toBeNull(); - expect(detectAgentMention("route to @huma")).toBeNull(); + expect(detectAgentMention("route to @local")).toBeNull(); }); it("ignores partial words and missing lane commands", () => { @@ -24,17 +24,17 @@ describe("detectAgentMention", () => { describe("normalizeGitHubEvent", () => { it("normalizes issue comments into trigger requests", () => { const trigger = normalizeGitHubEvent("issue_comment", { - sender: { login: "fschrhunt" }, + sender: { login: "OWNER" }, installation: { id: 123 }, - repository: { full_name: "fschrhunt/AgentRouter" }, - issue: { number: 7, html_url: "https://github.com/fschrhunt/AgentRouter/issues/7" }, + repository: { full_name: "OWNER/REPOSITORY" }, + issue: { number: 7, html_url: "https://github.com/OWNER/REPOSITORY/issues/7" }, comment: { body: "@SummonAgent codex make it so", html_url: "https://github.com/comment" }, }); expect(trigger).toMatchObject({ agent: "codex", - actor: "fschrhunt", - repository: "fschrhunt/AgentRouter", + actor: "OWNER", + repository: "OWNER/REPOSITORY", targetNumber: 7, isPullRequest: false, installationId: 123, @@ -45,7 +45,7 @@ describe("normalizeGitHubEvent", () => { const trigger = normalizeGitHubEvent("issue_comment", { sender: { login: "dependabot[bot]" }, installation: { id: 123 }, - repository: { full_name: "fschrhunt/AgentRouter" }, + repository: { full_name: "OWNER/REPOSITORY" }, issue: { number: 7 }, comment: { body: "@SummonAgent codex make it so" }, }); diff --git a/test/job-store.test.ts b/test/job-store.test.ts index 2664bd8..a30c054 100644 --- a/test/job-store.test.ts +++ b/test/job-store.test.ts @@ -47,12 +47,12 @@ describe("JobStore", () => { const created = store.createJob(trigger({ isPullRequest: true, pullRequestHeadBranch: "codex/workspace", - pullRequestHeadRepository: "fschrhunt/AgentRouter", + pullRequestHeadRepository: "OWNER/REPOSITORY", pullRequestBaseBranch: "main", })); expect(created.pullRequestHeadBranch).toBe("codex/workspace"); - expect(created.pullRequestHeadRepository).toBe("fschrhunt/AgentRouter"); + expect(created.pullRequestHeadRepository).toBe("OWNER/REPOSITORY"); expect(created.pullRequestBaseBranch).toBe("main"); }); }); @@ -61,14 +61,14 @@ function trigger(overrides: Partial = {}): TriggerRequest { return { agent: "codex", source: "issue_comment", - repository: "fschrhunt/AgentRouter", - owner: "fschrhunt", - repo: "AgentRouter", - actor: "fschrhunt", + repository: "OWNER/REPOSITORY", + owner: "OWNER", + repo: "Repository", + actor: "OWNER", targetNumber: 1, isPullRequest: false, body: "@SummonAgent codex hello", - htmlUrl: "https://github.com/fschrhunt/AgentRouter/issues/1#issuecomment-1", + htmlUrl: "https://github.com/OWNER/REPOSITORY/issues/1#issuecomment-1", installationId: 123, ...overrides, }; diff --git a/test/manifest.test.ts b/test/manifest.test.ts index 1812cd0..8f68d9d 100644 --- a/test/manifest.test.ts +++ b/test/manifest.test.ts @@ -12,11 +12,12 @@ const config: AppConfig = { setupSecret: "setup", allowedRepositories: new Set(), repositories: new Map(), + laneBranches: new Map(), }; describe("GitHub App manifest", () => { it("builds a private mention-router manifest", () => { - const manifest = buildManifest(config, { account: "fschrhunt", accountType: "user", appName: "AgentRouter" }); + const manifest = buildManifest(config, { account: "OWNER", accountType: "user", appName: "AgentRouter" }); expect(manifest).toMatchObject({ name: "AgentRouter", @@ -42,12 +43,12 @@ describe("GitHub App manifest", () => { }); it("targets personal and organization app creation URLs", () => { - expect(buildManifestTarget({ account: "fschrhunt", accountType: "user" })).toBe("https://github.com/settings/apps/new"); - expect(buildManifestTarget({ account: "HuntDynamics", accountType: "org" })).toBe("https://github.com/organizations/HuntDynamics/settings/apps/new"); + expect(buildManifestTarget({ account: "OWNER", accountType: "user" })).toBe("https://github.com/settings/apps/new"); + expect(buildManifestTarget({ account: "ORG", accountType: "org" })).toBe("https://github.com/organizations/ORG/settings/apps/new"); }); it("signs and verifies setup state", () => { - const state = signSetupState(config, { account: "fschrhunt", accountType: "user" }); + const state = signSetupState(config, { account: "OWNER", accountType: "user" }); expect(verifySetupState(config, state)).toBe(true); expect(verifySetupState({ ...config, setupSecret: "other" }, state)).toBe(false); }); diff --git a/test/router.test.ts b/test/router.test.ts index 66d6888..3bdbc49 100644 --- a/test/router.test.ts +++ b/test/router.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { evaluatePullRequestRouting, fixedLaneBranch, isRunnerAuthorized, parseAgent } from "../src/server/router.js"; +import type { AppConfig } from "../src/config.js"; import type { PullRequestContext } from "../src/github/client.js"; import type { TriggerRequest } from "../src/types.js"; @@ -18,7 +19,7 @@ describe("parseAgent", () => { it("accepts supported agents", () => { expect(parseAgent("codex")).toBe("codex"); expect(parseAgent("claude")).toBe("claude"); - expect(parseAgent("huma")).toBe("huma"); + expect(parseAgent("local")).toBe("local"); }); it("rejects unknown agents", () => { @@ -30,7 +31,14 @@ describe("fixed lane PR routing", () => { it("maps agents to their fixed workspace branches", () => { expect(fixedLaneBranch("codex")).toBe("codex/workspace"); expect(fixedLaneBranch("claude")).toBe("claude/workspace"); - expect(fixedLaneBranch("huma")).toBe("huma/workspace"); + expect(fixedLaneBranch("local")).toBe("local/workspace"); + }); + + it("allows deployment-specific lane branch overrides", () => { + const config = appConfig({ laneBranches: new Map([["local", "custom/workspace"]]) }); + + expect(fixedLaneBranch("local", config)).toBe("custom/workspace"); + expect(evaluatePullRequestRouting(trigger({ agent: "local" }), pr({ headBranch: "custom/workspace" }), config)).toEqual({ allowed: true }); }); it("allows pushing to the matching fixed branch in the same repo", () => { @@ -48,7 +56,7 @@ describe("fixed lane PR routing", () => { }); it("blocks fork PRs even when the branch name matches", () => { - const result = evaluatePullRequestRouting(trigger(), pr({ headRepository: "someone/AgentRouter" })); + const result = evaluatePullRequestRouting(trigger(), pr({ headRepository: "someone/Repository" })); expect(result.allowed).toBe(false); if (!result.allowed) { @@ -62,23 +70,39 @@ function trigger(overrides: Partial = {}): TriggerRequest { return { agent: "codex", source: "issue_comment", - repository: "fschrhunt/AgentRouter", - owner: "fschrhunt", - repo: "AgentRouter", - actor: "fschrhunt", + repository: "OWNER/REPOSITORY", + owner: "OWNER", + repo: "Repository", + actor: "OWNER", targetNumber: 1, isPullRequest: true, body: "@SummonAgent codex fix this", - htmlUrl: "https://github.com/fschrhunt/AgentRouter/pull/1#issuecomment-1", + htmlUrl: "https://github.com/OWNER/REPOSITORY/pull/1#issuecomment-1", installationId: 123, ...overrides, }; } +function appConfig(overrides: Partial = {}): AppConfig { + return { + port: 8787, + publicUrl: "http://localhost:8787", + databasePath: ":memory:", + workspaceRoot: "/Projects", + webhookSecret: "webhook", + runnerToken: "runner", + setupSecret: "setup", + allowedRepositories: new Set(), + repositories: new Map(), + laneBranches: new Map(), + ...overrides, + }; +} + function pr(overrides: Partial = {}): PullRequestContext { return { headBranch: "codex/workspace", - headRepository: "fschrhunt/AgentRouter", + headRepository: "OWNER/REPOSITORY", baseBranch: "main", ...overrides, }; diff --git a/test/runner.test.ts b/test/runner.test.ts index a4871b9..4d7b55e 100644 --- a/test/runner.test.ts +++ b/test/runner.test.ts @@ -9,9 +9,9 @@ describe("runner workspace routing", () => { const target = resolveWorktreeTarget(config(), job()); expect(target).toEqual({ - repositoryDir: "AgentRouter", + repositoryDir: "Repository", branch: "codex/workspace", - worktreeDir: "/Projects/AgentRouter/codex", + worktreeDir: "/Projects/Repository/codex", }); }); @@ -29,7 +29,7 @@ describe("agent prompt", () => { expect(prompt).toContain("Do not create a new branch or worktree."); expect(prompt).toContain("Stay on codex/workspace."); - expect(prompt).toContain("https://github.com/fschrhunt/AgentRouter/pull/5"); + expect(prompt).toContain("https://github.com/OWNER/REPOSITORY/pull/5"); }); }); @@ -43,7 +43,8 @@ function config(): AppConfig { runnerToken: "runner", setupSecret: "setup", allowedRepositories: new Set(), - repositories: new Map([["fschrhunt/agentrouter", "AgentRouter"]]), + repositories: new Map([["owner/repository", "Repository"]]), + laneBranches: new Map(), }; } @@ -52,17 +53,17 @@ function job(): AgentJob { id: 1, agent: "codex", source: "issue_comment", - repository: "fschrhunt/AgentRouter", - owner: "fschrhunt", - repo: "AgentRouter", - actor: "fschrhunt", + repository: "OWNER/REPOSITORY", + owner: "OWNER", + repo: "Repository", + actor: "OWNER", targetNumber: 5, isPullRequest: true, body: "@SummonAgent codex fix the failing check", - htmlUrl: "https://github.com/fschrhunt/AgentRouter/pull/5#issuecomment-1", + htmlUrl: "https://github.com/OWNER/REPOSITORY/pull/5#issuecomment-1", installationId: 123, pullRequestHeadBranch: "codex/workspace", - pullRequestHeadRepository: "fschrhunt/AgentRouter", + pullRequestHeadRepository: "OWNER/REPOSITORY", pullRequestBaseBranch: "main", status: "queued", createdAt: "now",