Patchpass V1
Deploy / Build (push) Successful in 28s
Deploy / Test & Lint (push) Failing after 29s
Deploy / Build and Push Docker Image (push) Has been skipped

This commit is contained in:
Space-Banane
2026-07-18 20:14:44 +02:00
commit 7e05dd918c
101 changed files with 15183 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
import {
cancelChangeRequest,
consumeApproval,
createChangeRequest,
getChangeRequestForAgent,
listChangeRequestsForAgent,
updateChangeRequest,
CreateRequestInput,
} from "../../ChangeRequestService";
import { Change } from "../../ChangeNormalization";
import { CHANGES_SCHEMA, McpToolDef, errResult, fromService, json } from "../shared";
/* eslint-disable @typescript-eslint/no-explicit-any */
function coerceChanges(raw: unknown): Change[] | { error: string } {
if (!Array.isArray(raw) || raw.length === 0) {
return { error: "changes must be a non-empty array" };
}
const out: Change[] = [];
for (const [i, item] of raw.entries()) {
if (!item || typeof item !== "object") return { error: `changes[${i}] must be an object` };
const c = item as any;
if (c.type === "unified_diff") {
if (typeof c.path !== "string" || typeof c.content !== "string") {
return { error: `changes[${i}] (unified_diff) needs string 'path' and 'content'` };
}
out.push({ type: "unified_diff", path: c.path, content: c.content });
} else if (c.type === "config") {
if (typeof c.path !== "string") return { error: `changes[${i}] (config) needs a string 'path'` };
out.push({
type: "config",
path: c.path,
before: c.before,
after: c.after,
content_type: typeof c.content_type === "string" ? c.content_type : undefined,
});
} else if (c.type === "custom") {
if (typeof c.label !== "string") return { error: `changes[${i}] (custom) needs a string 'label'` };
out.push({ type: "custom", label: c.label, before: c.before, after: c.after });
} else {
return { error: `changes[${i}] has invalid type '${c.type}'` };
}
}
return out;
}
function buildInput(args: Record<string, unknown>): CreateRequestInput | { error: string } {
const title = typeof args.title === "string" ? args.title : undefined;
if (!title) return { error: "title is required" };
const changes = coerceChanges(args.changes);
if ("error" in changes) return { error: changes.error };
return {
title,
description: typeof args.description === "string" ? args.description : null,
changes,
expires_in: typeof args.expires_in === "number" ? args.expires_in : undefined,
metadata:
args.metadata && typeof args.metadata === "object"
? (args.metadata as Record<string, unknown>)
: undefined,
};
}
const commonProps = {
title: { type: "string", description: "Short, human-readable title (1200 chars)" },
description: { type: "string", description: "Optional context for the reviewer" },
changes: CHANGES_SCHEMA,
expires_in: {
type: "integer",
description: "Seconds until the request expires (6043200; default 1800 = 30 min)",
},
metadata: {
type: "object",
description: "Optional free-form context, e.g. { repository, environment }",
},
};
export const createRequest: McpToolDef = {
name: "create_request",
description:
"Submit a change request for human approval. Returns request_id, approval_url, and state (PENDING). Poll get_request until decided.",
inputSchema: { type: "object", required: ["title", "changes"], properties: commonProps },
async handler(args, { agent }) {
const input = buildInput(args);
if ("error" in input) return errResult(input.error);
return fromService(await createChangeRequest(agent, input));
},
};
export const updateRequest: McpToolDef = {
name: "update_request",
description:
"Update a PENDING or CHANGES_REQUESTED request with revised content (e.g. after the human requested changes). Resets the request to PENDING for re-review.",
inputSchema: {
type: "object",
required: ["request_id", "title", "changes"],
properties: { request_id: { type: "string" }, ...commonProps },
},
async handler(args, { agent }) {
const requestId = typeof args.request_id === "string" ? args.request_id : undefined;
if (!requestId) return errResult("request_id is required");
const input = buildInput(args);
if ("error" in input) return errResult(input.error);
return fromService(await updateChangeRequest(agent, requestId, input));
},
};
export const getRequest: McpToolDef = {
name: "get_request",
description:
"Fetch a request by id: its current state, comment, and (once decided) the platform-signed receipt.",
inputSchema: {
type: "object",
required: ["request_id"],
properties: { request_id: { type: "string" } },
},
async handler(args, { agent }) {
const requestId = typeof args.request_id === "string" ? args.request_id : undefined;
if (!requestId) return errResult("request_id is required");
return fromService(await getChangeRequestForAgent(agent, requestId));
},
};
export const cancelRequest: McpToolDef = {
name: "cancel_request",
description: "Cancel your own request before a decision is made (PENDING or CHANGES_REQUESTED).",
inputSchema: {
type: "object",
required: ["request_id"],
properties: { request_id: { type: "string" } },
},
async handler(args, { agent }) {
const requestId = typeof args.request_id === "string" ? args.request_id : undefined;
if (!requestId) return errResult("request_id is required");
return fromService(await cancelChangeRequest(agent, requestId));
},
};
export const consumeApprovalTool: McpToolDef = {
name: "consume_approval",
description:
"Consume an APPROVED request (single-use) before applying changes. Returns the signed receipt. Fails if not APPROVED or already consumed.",
inputSchema: {
type: "object",
required: ["request_id"],
properties: { request_id: { type: "string" } },
},
async handler(args, { agent }) {
const requestId = typeof args.request_id === "string" ? args.request_id : undefined;
if (!requestId) return errResult("request_id is required");
return fromService(await consumeApproval(agent, requestId));
},
};
export const listRequests: McpToolDef = {
name: "list_requests",
description: "List your recent change requests, optionally filtered by state.",
inputSchema: {
type: "object",
properties: {
state: {
type: "string",
enum: [
"PENDING",
"CHANGES_REQUESTED",
"APPROVED",
"REJECTED",
"EXPIRED",
"CONSUMED",
"CANCELLED",
],
},
page: { type: "integer" },
page_size: { type: "integer" },
},
},
async handler(args, { agent }) {
const data = await listChangeRequestsForAgent(agent, {
state: typeof args.state === "string" ? args.state : undefined,
page: typeof args.page === "number" ? args.page : 1,
pageSize: typeof args.page_size === "number" ? args.page_size : 20,
});
return json(data);
},
};