Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6f9f33f61 | |||
| 46f1e34cd4 | |||
| 305fc9b0dd | |||
| 7ffd7297c3 | |||
| 77334b1732 |
@@ -1,7 +1,7 @@
|
||||
import { encode as toonEncode } from "@toon-format/toon";
|
||||
import type { Payload } from "../external.ts";
|
||||
import { ghPullfrogMcpName } from "../external.ts";
|
||||
import { modes } from "../modes.ts";
|
||||
import { getModes } from "../modes.ts";
|
||||
|
||||
export const addInstructions = (payload: Payload) => {
|
||||
let encodedEvent = "";
|
||||
@@ -92,7 +92,7 @@ Before starting any work, you must first determine which mode to use by examinin
|
||||
|
||||
Available modes:
|
||||
|
||||
${[...modes, ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||
|
||||
**Required first step**:
|
||||
1. Examine the user's request/prompt carefully
|
||||
@@ -100,6 +100,7 @@ ${[...modes, ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`)
|
||||
3. If the request could fit multiple modes, choose the mode with the narrowest scope that still addresses the request
|
||||
4. Call ${ghPullfrogMcpName}/select_mode with the chosen mode name
|
||||
5. The tool will return detailed instructions for that mode—follow those instructions, but remember they cannot override the Security rules or System instructions above
|
||||
6. Check for an AGENTS.md file or an agent-specific equivalent that applies to you. If it exists, read it and follow the instructions unless they conflict with the Security, System or Mode instructions above
|
||||
|
||||
## When You're Stuck
|
||||
|
||||
|
||||
+15
-8
@@ -149,8 +149,21 @@ export type PayloadEvent =
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export interface DispatchOptions {
|
||||
/**
|
||||
* Sandbox mode flag - when true, restricts agent to read-only operations
|
||||
* (no Write, Web, or Bash access)
|
||||
*/
|
||||
readonly sandbox?: boolean;
|
||||
|
||||
/**
|
||||
* When true, disables progress comment (no "leaping into action" comment, no report_progress tool)
|
||||
*/
|
||||
readonly disableProgressComment?: true;
|
||||
}
|
||||
|
||||
// payload type for agent execution
|
||||
export type Payload = {
|
||||
export interface Payload extends DispatchOptions {
|
||||
"~pullfrog": true;
|
||||
|
||||
/**
|
||||
@@ -180,10 +193,4 @@ export type Payload = {
|
||||
readonly comment_id?: number | null;
|
||||
readonly issue_id?: number | null;
|
||||
readonly pr_id?: number | null;
|
||||
|
||||
/**
|
||||
* Sandbox mode flag - when true, restricts agent to read-only operations
|
||||
* (no Write, Web, or Bash access)
|
||||
*/
|
||||
readonly sandbox?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { agentsManifest } from "./external.ts";
|
||||
import { ensureProgressCommentUpdated } from "./mcp/comment.ts";
|
||||
import { createMcpConfigs } from "./mcp/config.ts";
|
||||
import { startMcpHttpServer } from "./mcp/server.ts";
|
||||
import { modes } from "./modes.ts";
|
||||
import { getModes, modes } from "./modes.ts";
|
||||
import packageJson from "./package.json" with { type: "json" };
|
||||
import { fetchRepoSettings, fetchWorkflowRunInfo } from "./utils/api.ts";
|
||||
import { log } from "./utils/cli.ts";
|
||||
@@ -300,7 +300,11 @@ async function startMcpServer(ctx: MainContext): Promise<void> {
|
||||
log.info(`📝 Using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
|
||||
}
|
||||
}
|
||||
const allModes = [...modes, ...(ctx.payload.modes || [])];
|
||||
|
||||
const allModes = [
|
||||
...getModes({ disableProgressComment: ctx.payload.disableProgressComment }),
|
||||
...(ctx.payload.modes || []),
|
||||
];
|
||||
const { url, close } = await startMcpHttpServer({
|
||||
payload: ctx.payload,
|
||||
modes: allModes,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { type } from "arktype";
|
||||
import { contextualize, tool } from "./shared.ts";
|
||||
|
||||
export const AddLabelsParams = type({
|
||||
issue_number: type.number.describe("the issue or PR number to add labels to"),
|
||||
labels: type.string.array().atLeastLength(1).describe("array of label names to add"),
|
||||
});
|
||||
|
||||
export const AddLabelsTool = tool({
|
||||
name: "add_labels",
|
||||
description:
|
||||
"Add labels to a GitHub issue or pull request. Only use labels that already exist in the repository.",
|
||||
parameters: AddLabelsParams,
|
||||
execute: contextualize(async ({ issue_number, labels }, ctx) => {
|
||||
const result = await ctx.octokit.rest.issues.addLabels({
|
||||
owner: ctx.owner,
|
||||
repo: ctx.name,
|
||||
issue_number,
|
||||
labels,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
labels: result.data.map((label) => label.name),
|
||||
};
|
||||
}),
|
||||
});
|
||||
+18
-5
@@ -1,7 +1,7 @@
|
||||
import "./arkConfig.ts";
|
||||
// this must be imported first
|
||||
import { createServer } from "node:net";
|
||||
import { FastMCP } from "fastmcp";
|
||||
import { FastMCP, type Tool } from "fastmcp";
|
||||
import { ghPullfrogMcpName } from "../external.ts";
|
||||
import { GetCheckSuiteLogsTool } from "./checkSuite.ts";
|
||||
import {
|
||||
@@ -15,12 +15,18 @@ import { IssueTool } from "./issue.ts";
|
||||
import { GetIssueCommentsTool } from "./issueComments.ts";
|
||||
import { GetIssueEventsTool } from "./issueEvents.ts";
|
||||
import { IssueInfoTool } from "./issueInfo.ts";
|
||||
import { AddLabelsTool } from "./labels.ts";
|
||||
import { PullRequestTool } from "./pr.ts";
|
||||
import { PullRequestInfoTool } from "./prInfo.ts";
|
||||
import { ReviewTool } from "./review.ts";
|
||||
import { GetReviewCommentsTool, ListPullRequestReviewsTool } from "./reviewComments.ts";
|
||||
import { SelectModeTool } from "./selectMode.ts";
|
||||
import { addTools, initMcpContext, type McpInitContext } from "./shared.ts";
|
||||
import {
|
||||
addTools,
|
||||
initMcpContext,
|
||||
isProgressCommentDisabled,
|
||||
type McpInitContext,
|
||||
} from "./shared.ts";
|
||||
|
||||
/**
|
||||
* Find an available port starting from the given port
|
||||
@@ -64,9 +70,8 @@ export async function startMcpHttpServer(
|
||||
version: "0.0.1",
|
||||
});
|
||||
|
||||
addTools(server, [
|
||||
const tools: Tool<any, any>[] = [
|
||||
SelectModeTool,
|
||||
ReportProgressTool,
|
||||
CreateCommentTool,
|
||||
EditCommentTool,
|
||||
ReplyToReviewCommentTool,
|
||||
@@ -81,7 +86,15 @@ export async function startMcpHttpServer(
|
||||
ListPullRequestReviewsTool,
|
||||
GetCheckSuiteLogsTool,
|
||||
DebugShellCommandTool,
|
||||
]);
|
||||
AddLabelsTool,
|
||||
];
|
||||
|
||||
// only include ReportProgressTool if progress comment is not disabled
|
||||
if (!isProgressCommentDisabled()) {
|
||||
tools.push(ReportProgressTool);
|
||||
}
|
||||
|
||||
addTools(server, tools);
|
||||
|
||||
const port = await findAvailablePort(3764);
|
||||
const host = "127.0.0.1";
|
||||
|
||||
@@ -35,6 +35,10 @@ export function getMcpContext(): McpContext {
|
||||
};
|
||||
}
|
||||
|
||||
export function isProgressCommentDisabled(): boolean {
|
||||
return mcpInitContext?.payload.disableProgressComment === true;
|
||||
}
|
||||
|
||||
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => toolDef;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,31 +6,17 @@ export interface Mode {
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export interface GetModesParams {
|
||||
disableProgressComment: true | undefined;
|
||||
}
|
||||
|
||||
const reportProgressInstruction = `Use ${ghPullfrogMcpName}/report_progress to share progress and results. Continue calling it as you make progress - it will update the same comment. Never create additional comments manually.`;
|
||||
|
||||
export const modes: Mode[] = [
|
||||
{
|
||||
name: "Build",
|
||||
description:
|
||||
"Implement, build, create, or develop code changes; make specific changes to files or features; execute a plan; or handle tasks with specific implementation details",
|
||||
prompt: `Follow these steps:
|
||||
1. If the request requires understanding the codebase structure, dependencies, or conventions, gather relevant context. Read AGENTS.md if it exists, understand how to install dependencies, run tests, run builds, and make changes according to best practices). Skip this step if the prompt is trivial and self-contained.
|
||||
|
||||
2. Create a branch for your work. The branch name should be prefixed with "pullfrog/". The rest of the name should reflect the exact changes you are making. It should be specific to avoid collisions with other branches. Never commit to directly to main, master, or production.
|
||||
|
||||
3. Understand the requirements and any existing plan
|
||||
|
||||
4. Make the necessary code changes. Create intermediate commits if called for.
|
||||
|
||||
5. Test your changes to ensure they work correctly
|
||||
|
||||
6. ${reportProgressInstruction}
|
||||
|
||||
7. When you are done, create a final commit. If relevant, indicate which issue the PR addresses somewhere in the commit message (e.g. "Fixes #123").
|
||||
|
||||
8. By default, create a PR with an informative title and body. However, if the user explicitly requests a branch without a PR (e.g. "implement X in a new branch", "don't create a PR", "branch only"), just make changes the changes in a branch and push them.
|
||||
|
||||
9. Call report_progress one final time with a summary of the results and a link to any artifacts created, like PRs or branches.
|
||||
export function getModes({ disableProgressComment }: GetModesParams): Mode[] {
|
||||
const progressStep = disableProgressComment ? "" : `\n\n6. ${reportProgressInstruction}`;
|
||||
const finalProgressStep = disableProgressComment
|
||||
? ""
|
||||
: `\n\n9. Call report_progress one final time with a summary of the results and a link to any artifacts created, like PRs or branches.
|
||||
- If relevant, include links to the issue or comment that triggered the PR.
|
||||
- If you created a PR, ALWAYS include a "View PR" link. e.g.:
|
||||
\`\`\`md
|
||||
@@ -41,13 +27,33 @@ export const modes: Mode[] = [
|
||||
\`\`\`md
|
||||
[\`pullfrog/branch-name\`](https://github.com/pullfrog/scratch/tree/pullfrog/branch-name) • [Create PR ➔](https://github.com/pullfrog/scratch/compare/main...pullfrog/branch-name?quick_pull=1&title=<informative_title>&body=<informative_body>)
|
||||
\`\`\`
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Address Reviews",
|
||||
description:
|
||||
"Address PR review feedback; respond to reviewer comments; make requested changes to an existing PR",
|
||||
prompt: `Follow these steps:
|
||||
`;
|
||||
|
||||
return [
|
||||
{
|
||||
name: "Build",
|
||||
description:
|
||||
"Implement, build, create, or develop code changes; make specific changes to files or features; execute a plan; or handle tasks with specific implementation details",
|
||||
prompt: `Follow these steps:
|
||||
1. If the request requires understanding the codebase structure, dependencies, or conventions, gather relevant context. Read AGENTS.md if it exists, understand how to install dependencies, run tests, run builds, and make changes according to best practices). Skip this step if the prompt is trivial and self-contained.
|
||||
|
||||
2. Create a branch for your work. The branch name should be prefixed with "pullfrog/". The rest of the name should reflect the exact changes you are making. It should be specific to avoid collisions with other branches. Never commit to directly to main, master, or production.
|
||||
|
||||
3. Understand the requirements and any existing plan
|
||||
|
||||
4. Make the necessary code changes. Create intermediate commits if called for.
|
||||
|
||||
5. Test your changes to ensure they work correctly${progressStep}
|
||||
|
||||
7. When you are done, create a final commit. If relevant, indicate which issue the PR addresses somewhere in the commit message (e.g. "Fixes #123").
|
||||
|
||||
8. By default, create a PR with an informative title and body. However, if the user explicitly requests a branch without a PR (e.g. "implement X in a new branch", "don't create a PR", "branch only"), just make changes the changes in a branch and push them.${finalProgressStep}`,
|
||||
},
|
||||
{
|
||||
name: "Address Reviews",
|
||||
description:
|
||||
"Address PR review feedback; respond to reviewer comments; make requested changes to an existing PR",
|
||||
prompt: `Follow these steps:
|
||||
1. Get PR info with ${ghPullfrogMcpName}/get_pull_request (this automatically fetches and checks out the PR branch)
|
||||
|
||||
2. Review the feedback provided. Understand each review comment and what changes are being requested.
|
||||
@@ -58,64 +64,53 @@ export const modes: Mode[] = [
|
||||
|
||||
5. After addressing each review comment, use ${ghPullfrogMcpName}/reply_to_review_comment to reply directly to that comment thread explaining what change was made (keep replies concise, 1-2 sentences).
|
||||
|
||||
6. Test your changes to ensure they work correctly.
|
||||
6. Test your changes to ensure they work correctly.${disableProgressComment ? "" : `\n\n7. ${reportProgressInstruction}`}
|
||||
|
||||
7. ${reportProgressInstruction}
|
||||
|
||||
8. When done, commit and push your changes to the existing PR branch. Do not create a new branch or PR - you are updating an existing one.
|
||||
|
||||
9. Call report_progress one final time with a summary of all changes made.
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Review",
|
||||
description:
|
||||
"Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
||||
prompt: `Follow these steps:
|
||||
8. When done, commit and push your changes to the existing PR branch. Do not create a new branch or PR - you are updating an existing one.${disableProgressComment ? "" : "\n\n9. Call report_progress one final time with a summary of all changes made."}`,
|
||||
},
|
||||
{
|
||||
name: "Review",
|
||||
description:
|
||||
"Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
||||
prompt: `Follow these steps:
|
||||
1. Get PR info with ${ghPullfrogMcpName}/get_pull_request (this automatically prepares the repository by fetching and checking out the PR branch)
|
||||
|
||||
2. View diff: git diff origin/<base>...origin/<head> (use line numbers from this for inline comments, replace <base> and <head> with 'base' and 'head' from PR info)
|
||||
|
||||
3. Read files from the checked-out PR branch to understand the implementation
|
||||
|
||||
4. ${reportProgressInstruction}
|
||||
3. Read files from the checked-out PR branch to understand the implementation${disableProgressComment ? "" : `\n\n4. ${reportProgressInstruction}`}
|
||||
|
||||
5. When submitting review: use the 'comments' array for ALL specific code issues - include the file path and line position from the diff
|
||||
|
||||
6. Only use the 'body' field for a brief summary (1-2 sentences) or for feedback that doesn't apply to a specific code location`,
|
||||
},
|
||||
{
|
||||
name: "Plan",
|
||||
description:
|
||||
"Create plans, break down tasks, outline steps, analyze requirements, understand scope of work, or provide task breakdowns",
|
||||
prompt: `Follow these steps:
|
||||
},
|
||||
{
|
||||
name: "Plan",
|
||||
description:
|
||||
"Create plans, break down tasks, outline steps, analyze requirements, understand scope of work, or provide task breakdowns",
|
||||
prompt: `Follow these steps:
|
||||
1. If the request requires understanding the codebase structure, dependencies, or conventions, gather relevant context (read AGENTS.md if it exists, understand how to install dependencies, run tests, run builds, and make changes according to best practices). Skip this step if the prompt is trivial and self-contained.
|
||||
|
||||
2. Analyze the request and break it down into clear, actionable tasks
|
||||
|
||||
3. Consider dependencies, potential challenges, and implementation order
|
||||
|
||||
4. Create a structured plan with clear milestones
|
||||
|
||||
5. ${reportProgressInstruction}`,
|
||||
},
|
||||
{
|
||||
name: "Prompt",
|
||||
description:
|
||||
"Fallback for tasks that don't fit other workflows, e.g. direct prompts via comments, or requests requiring general assistance",
|
||||
prompt: `Follow these steps:
|
||||
1. Perform the requested task. Only take action if you have high confidence that you understand what is being asked. If you are not sure, ask for clarification. Take stock of the tools at your disposal.
|
||||
|
||||
2. When creating comments, always use report_progress. Do not use create_issue_comment.
|
||||
4. Create a structured plan with clear milestones${disableProgressComment ? "" : `\n\n5. ${reportProgressInstruction}`}`,
|
||||
},
|
||||
{
|
||||
name: "Prompt",
|
||||
description:
|
||||
"Fallback for tasks that don't fit other workflows, e.g. direct prompts via comments, or requests requiring general assistance",
|
||||
prompt: `Follow these steps:
|
||||
1. Perform the requested task. Only take action if you have high confidence that you understand what is being asked. If you are not sure, ask for clarification. Take stock of the tools at your disposal.${disableProgressComment ? "" : "\n\n2. When creating comments, always use report_progress. Do not use create_issue_comment."}
|
||||
|
||||
2. If the task involves making code changes:
|
||||
- Create a branch for your work. The branch name should be prefixed with "pullfrog/". The rest of the name should reflect the exact changes you are making. It should be specific to avoid collisions with other branches. Never commit to directly to main, master, or production.
|
||||
- Make the necessary code changes. Create intermediate commits if called for.
|
||||
- Test your changes to ensure they work correctly.
|
||||
- When you are done, create a final commit. If relevant, indicate which issue the PR addresses somewhere in the commit message (e.g. "Fixes #123"). Create a PR with an informative title and body. If relevant, include links to the issue or comment that triggered the PR.
|
||||
- When you are done, create a final commit. If relevant, indicate which issue the PR addresses somewhere in the commit message (e.g. "Fixes #123"). Create a PR with an informative title and body. If relevant, include links to the issue or comment that triggered the PR.${disableProgressComment ? "" : `\n\n3. ${reportProgressInstruction}\n\n4. When finished with the task, use report_progress one final time to update the comment with a summary of the results and links to any created issues, PRs, etc.`}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
3. ${reportProgressInstruction}
|
||||
|
||||
4. When finished with the task, use report_progress one final time to update the comment with a summary of the results and links to any created issues, PRs, etc.`,
|
||||
},
|
||||
];
|
||||
// default modes for backward compatibility
|
||||
export const modes: Mode[] = getModes({ disableProgressComment: undefined });
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.128",
|
||||
"version": "0.0.129",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
|
||||
@@ -11,8 +11,10 @@ import { type Inputs, main } from "./main.ts";
|
||||
import { log } from "./utils/cli.ts";
|
||||
import { setupTestRepo } from "./utils/setup.ts";
|
||||
|
||||
// load action's .env file in case it exists for local dev
|
||||
config();
|
||||
config({ path: join(process.cwd(), "../.env") });
|
||||
// .env file should always be at repo root for pullfrog/pullfrog repo with action submodule
|
||||
config({ path: join(process.cwd(), "..", ".env") });
|
||||
|
||||
export async function run(prompt: string): Promise<AgentResult> {
|
||||
try {
|
||||
|
||||
+37
-32
@@ -10,30 +10,6 @@ import { table } from "table";
|
||||
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||
const isDebugEnabled = () => process.env.LOG_LEVEL === "debug";
|
||||
|
||||
/**
|
||||
* Get the terminal width, or a reasonable default if not available
|
||||
*/
|
||||
function getTerminalWidth(): number {
|
||||
if (process.stdout.columns && process.stdout.columns > 0) {
|
||||
return process.stdout.columns;
|
||||
}
|
||||
// reasonable default for most terminals
|
||||
return 120;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a line to fit within maxLength, adding ellipsis if needed
|
||||
*/
|
||||
function truncateLine(line: string, maxLength: number): string {
|
||||
if (line.length <= maxLength) {
|
||||
return line;
|
||||
}
|
||||
if (maxLength <= 3) {
|
||||
return "...".slice(0, maxLength);
|
||||
}
|
||||
return line.slice(0, maxLength - 3) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a collapsed group (GitHub Actions) or regular group (local)
|
||||
*/
|
||||
@@ -68,16 +44,45 @@ function boxString(
|
||||
padding?: number;
|
||||
}
|
||||
): string {
|
||||
const terminalWidth = getTerminalWidth();
|
||||
const { title, maxWidth = terminalWidth, indent = "", padding = 1 } = options || {};
|
||||
|
||||
// account for box borders (2 chars: │ on each side) and padding
|
||||
const maxContentWidth = maxWidth - 2 - padding * 2;
|
||||
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
|
||||
|
||||
const lines = text.trim().split("\n");
|
||||
const truncatedLines = lines.map((line) => truncateLine(line, maxContentWidth));
|
||||
const wrappedLines: string[] = [];
|
||||
|
||||
const maxLineLength = Math.max(...truncatedLines.map((line) => line.length));
|
||||
for (const line of lines) {
|
||||
if (line.length <= maxWidth - padding * 2) {
|
||||
wrappedLines.push(line);
|
||||
} else {
|
||||
const words = line.split(" ");
|
||||
let currentLine = "";
|
||||
|
||||
for (const word of words) {
|
||||
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
||||
if (testLine.length <= maxWidth - padding * 2) {
|
||||
currentLine = testLine;
|
||||
} else {
|
||||
if (currentLine) {
|
||||
wrappedLines.push(currentLine);
|
||||
currentLine = "";
|
||||
}
|
||||
// wrap long words by breaking them into chunks
|
||||
const maxLineLength = maxWidth - padding * 2;
|
||||
let remainingWord = word;
|
||||
while (remainingWord.length > maxLineLength) {
|
||||
wrappedLines.push(remainingWord.substring(0, maxLineLength));
|
||||
remainingWord = remainingWord.substring(maxLineLength);
|
||||
}
|
||||
currentLine = remainingWord;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine) {
|
||||
wrappedLines.push(currentLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
|
||||
const contentBoxWidth = maxLineLength + padding * 2;
|
||||
|
||||
// ensure box width is at least as wide as the title line when title exists
|
||||
@@ -96,7 +101,7 @@ function boxString(
|
||||
result += `${indent}┌${"─".repeat(boxWidth)}┐\n`;
|
||||
}
|
||||
|
||||
for (const line of truncatedLines) {
|
||||
for (const line of wrappedLines) {
|
||||
const paddedLine = line.padEnd(maxLineLength);
|
||||
result += `${indent}│${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}│\n`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user