make webhook

This commit is contained in:
2026-07-13 18:50:38 +02:00
parent 2f128550fc
commit 40c822d3bf
67 changed files with 6286 additions and 2053 deletions
@@ -0,0 +1,187 @@
import { rm } from "node:fs/promises";
import type { AgentStore } from "../../../adapters/database/store.js";
import type { ActorPolicy } from "../../../core/config.js";
import {
parseCommentPayload,
parseLabelPayload,
type WebhookRepository,
} from "../../../core/webhook.js";
import { publishJob } from "../../publication/service.js";
import {
claimJob,
type PublicationContext,
safeFailure,
upsertJobStatus,
} from "../../publication/status.js";
import {
IgnoreDelivery,
reconcileCommand,
reconcileLabels,
} from "./reconcile.js";
export async function pumpDeliveries(
store: AgentStore,
context: PublicationContext,
repositoryId: number,
repositoryFullName: string,
botId: number,
policy: ActorPolicy,
): Promise<void> {
for (let count = 0; count < 20; count += 1) {
const delivery = store.leaseDelivery();
if (!delivery) break;
try {
if (delivery.eventType === "issue_label") {
const payload = parseLabelPayload(delivery.payload);
verifyIdentity(
payload.repository,
repositoryId,
repositoryFullName,
);
await reconcileLabels(
store,
context,
repositoryId,
payload.issue.number,
payload.sender,
botId,
policy,
);
} else if (delivery.eventType === "issue_comment") {
const payload = parseCommentPayload(delivery.payload);
verifyIdentity(
payload.repository,
repositoryId,
repositoryFullName,
);
await reconcileCommand(
store,
context,
repositoryId,
payload,
botId,
policy,
);
} else
throw new IgnoreDelivery(
`Unsupported event type ${delivery.eventType}`,
);
store.completeDelivery(delivery.id);
} catch (error) {
if (error instanceof IgnoreDelivery) {
store.completeDelivery(delivery.id);
console.log(
log("info", "Ignored delivery", {
delivery: delivery.id,
reason: error.message,
}),
);
} else {
store.retryDelivery(
delivery.id,
safeFailure(error),
delivery.attempts + 1,
);
console.error(
log("error", "Delivery processing failed", {
delivery: delivery.id,
error: safeFailure(error),
}),
);
}
}
}
}
export async function pumpOutbox(
store: AgentStore,
context: PublicationContext,
): Promise<void> {
for (let count = 0; count < 10; count += 1) {
const item = store.leaseOutbox();
if (!item) break;
const job = store.getJob(item.jobId);
if (!job) {
store.retryOutbox(item, "Outbox job no longer exists");
continue;
}
try {
if (item.kind === "claim") {
if (job.cancelRequested)
await upsertJobStatus(
context.client,
context.botLogin,
job,
`Agent ${job.mode} cancelled`,
"The request was cancelled before execution.",
);
else await claimJob(context, job);
if (job.triggerLabel)
store.releaseLabelClaim(
job.repositoryId,
job.issueNumber,
job.triggerLabel,
);
store.completeClaim(item);
} else {
const outcome = await publishJob(context, job);
if (outcome.planCommentId !== undefined)
store.recordPlan(job, outcome.planCommentId);
if (job.result?.implementation)
store.recordImplementation(
job,
outcome.commitSha || null,
outcome.pullRequestNumber || null,
);
if (job.workspace)
await rm(job.workspace, {
recursive: true,
force: true,
}).catch((error) => {
console.error(
log("error", "Workspace cleanup failed", {
jobId: job.id,
error: safeFailure(error),
}),
);
});
store.completePublication(item, outcome.terminal);
}
} catch (error) {
store.retryOutbox(item, safeFailure(error));
console.error(
log("error", "Outbox operation failed", {
jobId: job.id,
kind: item.kind,
error: safeFailure(error),
}),
);
}
}
}
export function log(
level: string,
message: string,
fields: Record<string, unknown>,
): string {
return JSON.stringify({
level,
message,
...fields,
time: new Date().toISOString(),
});
}
function verifyIdentity(
repository: WebhookRepository,
id: number,
fullName: string,
): void {
if (
repository.id !== id ||
repository.full_name.toLowerCase() !== fullName.toLowerCase()
) {
throw new IgnoreDelivery("Repository identity mismatch");
}
}