Implement Plan button
This commit is contained in:
@@ -98415,7 +98415,11 @@ var addTools = (ctx, server, tools) => {
|
|||||||
|
|
||||||
// mcp/comment.ts
|
// mcp/comment.ts
|
||||||
var LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
|
var LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
|
||||||
async function buildCommentFooter(payload, octokit) {
|
async function buildCommentFooter({
|
||||||
|
payload,
|
||||||
|
octokit,
|
||||||
|
customParts
|
||||||
|
}) {
|
||||||
const repoContext = parseRepoContext();
|
const repoContext = parseRepoContext();
|
||||||
const runId = process.env.GITHUB_RUN_ID;
|
const runId = process.env.GITHUB_RUN_ID;
|
||||||
const agentName = payload.agent;
|
const agentName = payload.agent;
|
||||||
@@ -98432,7 +98436,7 @@ async function buildCommentFooter(payload, octokit) {
|
|||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buildPullfrogFooter({
|
const footerParams = {
|
||||||
triggeredBy: true,
|
triggeredBy: true,
|
||||||
agent: {
|
agent: {
|
||||||
displayName: agentInfo?.displayName || "Unknown agent",
|
displayName: agentInfo?.displayName || "Unknown agent",
|
||||||
@@ -98444,11 +98448,19 @@ async function buildCommentFooter(payload, octokit) {
|
|||||||
runId,
|
runId,
|
||||||
...workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}
|
...workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}
|
||||||
} : void 0
|
} : void 0
|
||||||
});
|
};
|
||||||
|
if (customParts && customParts.length > 0) {
|
||||||
|
return buildPullfrogFooter({ ...footerParams, customParts });
|
||||||
|
}
|
||||||
|
return buildPullfrogFooter(footerParams);
|
||||||
|
}
|
||||||
|
function buildImplementPlanLink(owner, repo, issueNumber, commentId) {
|
||||||
|
const apiUrl = process.env.API_URL || "https://pullfrog.com";
|
||||||
|
return `[Implement plan \u2794](${apiUrl}/trigger/${owner}/${repo}/${issueNumber}?action=implement&comment_id=${commentId})`;
|
||||||
}
|
}
|
||||||
async function addFooter(body, payload, octokit) {
|
async function addFooter(body, payload, octokit) {
|
||||||
const bodyWithoutFooter = stripExistingFooter(body);
|
const bodyWithoutFooter = stripExistingFooter(body);
|
||||||
const footer = await buildCommentFooter(payload, octokit);
|
const footer = await buildCommentFooter({ payload, octokit });
|
||||||
return `${bodyWithoutFooter}${footer}`;
|
return `${bodyWithoutFooter}${footer}`;
|
||||||
}
|
}
|
||||||
var Comment = type({
|
var Comment = type({
|
||||||
@@ -98532,9 +98544,18 @@ var ReportProgress = type({
|
|||||||
body: type.string.describe("the progress update content to share")
|
body: type.string.describe("the progress update content to share")
|
||||||
});
|
});
|
||||||
async function reportProgress(ctx, { body }) {
|
async function reportProgress(ctx, { body }) {
|
||||||
const bodyWithFooter = await addFooter(body, ctx.payload, ctx.octokit);
|
|
||||||
const existingCommentId = getProgressCommentId();
|
const existingCommentId = getProgressCommentId();
|
||||||
|
const issueNumber = ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
|
||||||
|
const isPlanMode = ctx.toolState.selectedMode === "Plan";
|
||||||
if (existingCommentId) {
|
if (existingCommentId) {
|
||||||
|
const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, existingCommentId)] : void 0;
|
||||||
|
const bodyWithoutFooter = stripExistingFooter(body);
|
||||||
|
const footer = await buildCommentFooter({
|
||||||
|
payload: ctx.payload,
|
||||||
|
octokit: ctx.octokit,
|
||||||
|
customParts
|
||||||
|
});
|
||||||
|
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
||||||
const result2 = await ctx.octokit.rest.issues.updateComment({
|
const result2 = await ctx.octokit.rest.issues.updateComment({
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
repo: ctx.name,
|
repo: ctx.name,
|
||||||
@@ -98549,18 +98570,40 @@ async function reportProgress(ctx, { body }) {
|
|||||||
action: "updated"
|
action: "updated"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const issueNumber = ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
|
|
||||||
if (issueNumber === void 0) {
|
if (issueNumber === void 0) {
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
|
const initialBody = await addFooter(body, ctx.payload, ctx.octokit);
|
||||||
const result = await ctx.octokit.rest.issues.createComment({
|
const result = await ctx.octokit.rest.issues.createComment({
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
repo: ctx.name,
|
repo: ctx.name,
|
||||||
issue_number: issueNumber,
|
issue_number: issueNumber,
|
||||||
body: bodyWithFooter
|
body: initialBody
|
||||||
});
|
});
|
||||||
setProgressCommentId(result.data.id);
|
setProgressCommentId(result.data.id);
|
||||||
progressCommentWasUpdated = true;
|
progressCommentWasUpdated = true;
|
||||||
|
if (isPlanMode) {
|
||||||
|
const customParts = [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, result.data.id)];
|
||||||
|
const bodyWithoutFooter = stripExistingFooter(body);
|
||||||
|
const footer = await buildCommentFooter({
|
||||||
|
payload: ctx.payload,
|
||||||
|
octokit: ctx.octokit,
|
||||||
|
customParts
|
||||||
|
});
|
||||||
|
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
|
||||||
|
const updateResult = await ctx.octokit.rest.issues.updateComment({
|
||||||
|
owner: ctx.owner,
|
||||||
|
repo: ctx.name,
|
||||||
|
comment_id: result.data.id,
|
||||||
|
body: bodyWithPlanLink
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
commentId: updateResult.data.id,
|
||||||
|
url: updateResult.data.html_url,
|
||||||
|
body: updateResult.data.body || "",
|
||||||
|
action: "created"
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
commentId: result.data.id,
|
commentId: result.data.id,
|
||||||
url: result.data.html_url,
|
url: result.data.html_url,
|
||||||
@@ -124718,6 +124761,7 @@ function SelectModeTool(ctx) {
|
|||||||
availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description }))
|
availableModes: ctx.modes.map((m) => ({ name: m.name, description: m.description }))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
ctx.toolState.selectedMode = selectedMode.name;
|
||||||
return {
|
return {
|
||||||
modeName: selectedMode.name,
|
modeName: selectedMode.name,
|
||||||
description: selectedMode.description,
|
description: selectedMode.description,
|
||||||
|
|||||||
@@ -212,6 +212,13 @@ interface FixReviewEvent extends BasePayloadEvent {
|
|||||||
unapproved_comments: ReviewCommentData[];
|
unapproved_comments: ReviewCommentData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ImplementPlanEvent extends BasePayloadEvent {
|
||||||
|
trigger: "implement_plan";
|
||||||
|
issue_number: number;
|
||||||
|
plan_comment_id: number;
|
||||||
|
plan_content: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface UnknownEvent extends BasePayloadEvent {
|
interface UnknownEvent extends BasePayloadEvent {
|
||||||
trigger: "unknown";
|
trigger: "unknown";
|
||||||
}
|
}
|
||||||
@@ -231,6 +238,7 @@ export type PayloadEvent =
|
|||||||
| CheckSuiteCompletedEvent
|
| CheckSuiteCompletedEvent
|
||||||
| WorkflowDispatchEvent
|
| WorkflowDispatchEvent
|
||||||
| FixReviewEvent
|
| FixReviewEvent
|
||||||
|
| ImplementPlanEvent
|
||||||
| UnknownEvent;
|
| UnknownEvent;
|
||||||
|
|
||||||
export interface DispatchOptions {
|
export interface DispatchOptions {
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ export interface DependencyInstallationState {
|
|||||||
export interface ToolState {
|
export interface ToolState {
|
||||||
prNumber?: number;
|
prNumber?: number;
|
||||||
issueNumber?: number;
|
issueNumber?: number;
|
||||||
|
selectedMode?: string;
|
||||||
review?: {
|
review?: {
|
||||||
id: number; // REST API database ID (for fix URLs)
|
id: number; // REST API database ID (for fix URLs)
|
||||||
nodeId: string; // GraphQL node ID (for mutations)
|
nodeId: string; // GraphQL node ID (for mutations)
|
||||||
|
|||||||
+75
-8
@@ -15,7 +15,17 @@ import { execute, tool } from "./shared.ts";
|
|||||||
*/
|
*/
|
||||||
export const LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
|
export const LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
|
||||||
|
|
||||||
async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<string> {
|
interface BuildCommentFooterParams {
|
||||||
|
payload: Payload;
|
||||||
|
octokit?: Octokit | undefined;
|
||||||
|
customParts?: string[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildCommentFooter({
|
||||||
|
payload,
|
||||||
|
octokit,
|
||||||
|
customParts,
|
||||||
|
}: BuildCommentFooterParams): Promise<string> {
|
||||||
const repoContext = parseRepoContext();
|
const repoContext = parseRepoContext();
|
||||||
const runId = process.env.GITHUB_RUN_ID;
|
const runId = process.env.GITHUB_RUN_ID;
|
||||||
|
|
||||||
@@ -38,7 +48,7 @@ async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildPullfrogFooter({
|
const footerParams = {
|
||||||
triggeredBy: true,
|
triggeredBy: true,
|
||||||
agent: {
|
agent: {
|
||||||
displayName: agentInfo?.displayName || "Unknown agent",
|
displayName: agentInfo?.displayName || "Unknown agent",
|
||||||
@@ -52,12 +62,27 @@ async function buildCommentFooter(payload: Payload, octokit?: Octokit): Promise<
|
|||||||
...(workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}),
|
...(workflowRunHtmlUrl ? { htmlUrl: workflowRunHtmlUrl } : {}),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (customParts && customParts.length > 0) {
|
||||||
|
return buildPullfrogFooter({ ...footerParams, customParts });
|
||||||
|
}
|
||||||
|
return buildPullfrogFooter(footerParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildImplementPlanLink(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issueNumber: number,
|
||||||
|
commentId: number
|
||||||
|
): string {
|
||||||
|
const apiUrl = process.env.API_URL || "https://pullfrog.com";
|
||||||
|
return `[Implement plan ➔](${apiUrl}/trigger/${owner}/${repo}/${issueNumber}?action=implement&comment_id=${commentId})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addFooter(body: string, payload: Payload, octokit?: Octokit): Promise<string> {
|
async function addFooter(body: string, payload: Payload, octokit?: Octokit): Promise<string> {
|
||||||
const bodyWithoutFooter = stripExistingFooter(body);
|
const bodyWithoutFooter = stripExistingFooter(body);
|
||||||
const footer = await buildCommentFooter(payload, octokit);
|
const footer = await buildCommentFooter({ payload, octokit });
|
||||||
return `${bodyWithoutFooter}${footer}`;
|
return `${bodyWithoutFooter}${footer}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,11 +206,26 @@ export async function reportProgress(
|
|||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
> {
|
> {
|
||||||
const bodyWithFooter = await addFooter(body, ctx.payload, ctx.octokit);
|
|
||||||
const existingCommentId = getProgressCommentId();
|
const existingCommentId = getProgressCommentId();
|
||||||
|
const issueNumber =
|
||||||
|
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
|
||||||
|
const isPlanMode = ctx.toolState.selectedMode === "Plan";
|
||||||
|
|
||||||
// if we already have a progress comment, update it
|
// if we already have a progress comment, update it
|
||||||
if (existingCommentId) {
|
if (existingCommentId) {
|
||||||
|
const customParts =
|
||||||
|
isPlanMode && issueNumber !== undefined
|
||||||
|
? [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, existingCommentId)]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const bodyWithoutFooter = stripExistingFooter(body);
|
||||||
|
const footer = await buildCommentFooter({
|
||||||
|
payload: ctx.payload,
|
||||||
|
octokit: ctx.octokit,
|
||||||
|
customParts,
|
||||||
|
});
|
||||||
|
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
||||||
|
|
||||||
const result = await ctx.octokit.rest.issues.updateComment({
|
const result = await ctx.octokit.rest.issues.updateComment({
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
repo: ctx.name,
|
repo: ctx.name,
|
||||||
@@ -205,24 +245,51 @@ export async function reportProgress(
|
|||||||
|
|
||||||
// no existing comment - create one
|
// no existing comment - create one
|
||||||
// use fallback chain: dynamically set context > event payload
|
// use fallback chain: dynamically set context > event payload
|
||||||
const issueNumber =
|
|
||||||
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
|
|
||||||
if (issueNumber === undefined) {
|
if (issueNumber === undefined) {
|
||||||
// cannot create comment without issue_number (e.g., workflow_dispatch events)
|
// cannot create comment without issue_number (e.g., workflow_dispatch events)
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for new comments, we need to create first, then update with Plan link if in Plan mode
|
||||||
|
const initialBody = await addFooter(body, ctx.payload, ctx.octokit);
|
||||||
|
|
||||||
const result = await ctx.octokit.rest.issues.createComment({
|
const result = await ctx.octokit.rest.issues.createComment({
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
repo: ctx.name,
|
repo: ctx.name,
|
||||||
issue_number: issueNumber,
|
issue_number: issueNumber,
|
||||||
body: bodyWithFooter,
|
body: initialBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
// store the comment ID for future updates
|
// store the comment ID for future updates
|
||||||
setProgressCommentId(result.data.id);
|
setProgressCommentId(result.data.id);
|
||||||
progressCommentWasUpdated = true;
|
progressCommentWasUpdated = true;
|
||||||
|
|
||||||
|
// if Plan mode, update the comment to add the "Implement plan" link
|
||||||
|
if (isPlanMode) {
|
||||||
|
const customParts = [buildImplementPlanLink(ctx.owner, ctx.name, issueNumber, result.data.id)];
|
||||||
|
const bodyWithoutFooter = stripExistingFooter(body);
|
||||||
|
const footer = await buildCommentFooter({
|
||||||
|
payload: ctx.payload,
|
||||||
|
octokit: ctx.octokit,
|
||||||
|
customParts,
|
||||||
|
});
|
||||||
|
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
|
||||||
|
|
||||||
|
const updateResult = await ctx.octokit.rest.issues.updateComment({
|
||||||
|
owner: ctx.owner,
|
||||||
|
repo: ctx.name,
|
||||||
|
comment_id: result.data.id,
|
||||||
|
body: bodyWithPlanLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
commentId: updateResult.data.id,
|
||||||
|
url: updateResult.data.html_url,
|
||||||
|
body: updateResult.data.body || "",
|
||||||
|
action: "created",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commentId: result.data.id,
|
commentId: result.data.id,
|
||||||
url: result.data.html_url,
|
url: result.data.html_url,
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export function SelectModeTool(ctx: ToolContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// store selected mode in toolState for use by other tools (e.g., report_progress)
|
||||||
|
ctx.toolState.selectedMode = selectedMode.name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modeName: selectedMode.name,
|
modeName: selectedMode.name,
|
||||||
description: selectedMode.description,
|
description: selectedMode.description,
|
||||||
|
|||||||
Reference in New Issue
Block a user