By request: one agent in Matrix, not several. Removes matrixBot.js, router.js (chat-vs-code-task classifier), litellm.js (claude-agent's own LiteLLM client), the matrix-bot-sdk dependency, runChatTask() and its gitea.js branch/PR helpers (createBranch/createPullRequest — only ever called from the now-removed chat flow), and every Matrix/LiteLLM env var from claude-agent's compose service. claude-agent already left the control room manually before this merge. It keeps its Gitea-webhook-triggered PR review, which never touched Matrix or LiteLLM to begin with. Makes PR #12 (the claude-bot/Hermes cross-reply cascade fix) moot — the bug can't happen once claude-agent has no Matrix client at all. Close #12 without merging once this lands.
66 lines
2.2 KiB
JavaScript
66 lines
2.2 KiB
JavaScript
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import { mkdtemp, rm, mkdir } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { authenticatedCloneUrl } from "./gitea.js";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const WORKSPACE_ROOT = "/workspace";
|
|
|
|
async function run(cmd, args, opts = {}) {
|
|
const { stdout } = await execFileAsync(cmd, args, {
|
|
maxBuffer: 1024 * 1024 * 32,
|
|
...opts,
|
|
});
|
|
return stdout;
|
|
}
|
|
|
|
async function withWorkspace(fn) {
|
|
await mkdir(WORKSPACE_ROOT, { recursive: true });
|
|
const dir = await mkdtemp(path.join(WORKSPACE_ROOT, "job-"));
|
|
try {
|
|
return await fn(dir);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
// Runs Claude Code headless. Unattended containers have no TTY to answer permission
|
|
// prompts, so this trusts the sandboxing of the throwaway clone dir instead:
|
|
// bypassPermissions to avoid hanging, plus --disallowedTools as defense in depth so
|
|
// Claude can never push/commit/checkout, or edit files — this is read-only review.
|
|
async function runClaude(cwd, prompt) {
|
|
const disallowed = [
|
|
"Bash(git push:*)", "Bash(git commit:*)", "Bash(git checkout:*)",
|
|
"Edit", "Write", "NotebookEdit",
|
|
];
|
|
|
|
const args = [
|
|
"-p", prompt,
|
|
"--output-format", "text",
|
|
"--permission-mode", "bypassPermissions",
|
|
"--disallowedTools", disallowed.join(","),
|
|
];
|
|
|
|
return run("claude", args, { cwd });
|
|
}
|
|
|
|
export async function reviewPullRequest({ owner, repo, ref, cloneUrl, prTitle, prBody }) {
|
|
return withWorkspace(async (dir) => {
|
|
const authedUrl = authenticatedCloneUrl(cloneUrl);
|
|
await run("git", ["clone", "--quiet", authedUrl, dir]);
|
|
await run("git", ["fetch", "--quiet", "origin", ref], { cwd: dir });
|
|
await run("git", ["checkout", "--quiet", ref], { cwd: dir });
|
|
|
|
const prompt = [
|
|
"You are reviewing a pull request. Diff HEAD against the base branch (origin/main)",
|
|
"and give a concise, specific code review: correctness bugs first, then",
|
|
"simplification/reuse/efficiency notes. Read-only — do not edit any files.",
|
|
`PR title: ${prTitle}`,
|
|
`PR description:\n${prBody}`,
|
|
].join("\n");
|
|
|
|
return runClaude(dir, prompt);
|
|
});
|
|
}
|