Compare commits

...

6 Commits

Author SHA1 Message Date
Colin McDonnell a8edd603c5 0.0.124 2025-12-03 16:41:09 -08:00
Colin McDonnell 51b37f67ca Improve flow for non-PR Build mode 2025-12-03 16:40:53 -08:00
Colin McDonnell 046de13bb3 Fix issue w/ new comments being created in Prompt mode 2025-12-03 15:21:45 -08:00
Shawn Morreau 306285577e remove unnecessary env var 2025-12-03 14:56:32 -05:00
Shawn Morreau 989a7c8960 merge main 2025-12-03 14:35:12 -05:00
Shawn Morreau 9b4bdae8bd intercept and sanitize gemini schema 2025-12-03 14:28:08 -05:00
8 changed files with 204 additions and 32 deletions
+3 -1
View File
@@ -166,7 +166,6 @@ export const gemini = agent({
env: createAgentEnv({
GEMINI_API_KEY: apiKey,
}),
timeout: 600000, // 10 minutes
onStdout: async (chunk) => {
const text = chunk.toString();
finalOutput += text;
@@ -249,6 +248,9 @@ function configureGeminiMcpServers({ mcpServers, cliPath }: ConfigureMcpServersP
const addResult = spawnSync("node", [cliPath, ...addArgs], {
stdio: "pipe",
encoding: "utf-8",
env: {
...process.env,
},
});
if (addResult.status !== 0) {
+85 -16
View File
@@ -83859,7 +83859,7 @@ function query({
// package.json
var package_default = {
name: "@pullfrog/action",
version: "0.0.123",
version: "0.0.124",
type: "module",
files: [
"index.js",
@@ -92224,9 +92224,21 @@ var modes = [
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"). Create a PR with an informative title and body. If relevant, include links to the issue or comment that triggered the PR.
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. Call report_progress one final time with a summary of the results. Include links to any created issues/PRs, e.g. \`[View PR](https://github.com/org/repo/pull/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.
- 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
[View PR \u2794](https://github.com/org/repo/pull/123)
\`\`\`
- If you created a branch without a PR, ALWAYS include a "Create PR" link and a link to the branch. e.g.:
\`\`\`md
[\`pullfrog/branch-name\`](https://github.com/pullfrogai/scratch/tree/pullfrog/branch-name) \u2022 [Create PR \u2794](https://github.com/pullfrogai/scratch/compare/main...pullfrog/branch-name?quick_pull=1&title=<informative_title>&body=<informative_body>)
\`\`\`
`
},
{
@@ -92284,10 +92296,12 @@ var modes = [
},
{
name: "Prompt",
description: "Fallback for tasks that don't fit other workflows, direct prompts via comments, or requests requiring general assistance without a specific workflow pattern",
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.
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.
@@ -92296,7 +92310,7 @@ var modes = [
3. ${reportProgressInstruction}
4. Call report_progress one final time with a summary of the results and links to any created issues, PRs, etc.`
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.`
}
];
@@ -93824,8 +93838,6 @@ var gemini = agent({
env: createAgentEnv({
GEMINI_API_KEY: apiKey
}),
timeout: 6e5,
// 10 minutes
onStdout: async (chunk) => {
const text = chunk.toString();
finalOutput += text;
@@ -93887,7 +93899,10 @@ function configureGeminiMcpServers({ mcpServers, cliPath }) {
log.info(`Adding MCP server '${serverName}' at ${serverConfig.url}...`);
const addResult = spawnSync3("node", [cliPath, ...addArgs], {
stdio: "pipe",
encoding: "utf-8"
encoding: "utf-8",
env: {
...process.env
}
});
if (addResult.status !== 0) {
throw new Error(
@@ -121641,9 +121656,58 @@ function getMcpContext() {
};
}
var tool = (toolDef) => toolDef;
function sanitizeSchema(schema2) {
if (!schema2 || typeof schema2 !== "object") {
return schema2;
}
if (Array.isArray(schema2)) {
return schema2.map(sanitizeSchema);
}
const sanitized = {};
for (const [key, value2] of Object.entries(schema2)) {
if (key === "$schema") {
continue;
}
if (key === "$defs") {
sanitized.definitions = sanitizeSchema(value2);
continue;
}
sanitized[key] = sanitizeSchema(value2);
}
return sanitized;
}
function wrapSchema(schema2) {
const originalToJsonSchema = schema2.toJsonSchema?.bind(schema2);
if (!originalToJsonSchema) {
return schema2;
}
return new Proxy(schema2, {
get(target, prop) {
if (prop === "toJsonSchema") {
return () => {
const originalSchema = originalToJsonSchema();
return sanitizeSchema(originalSchema);
};
}
return target[prop];
}
});
}
function sanitizeTool(tool2) {
if (!tool2.parameters) {
return tool2;
}
const wrappedSchema = wrapSchema(tool2.parameters);
return {
...tool2,
parameters: wrappedSchema
};
}
var addTools = (server, tools) => {
const shouldSanitize = mcpInitContext?.agentName === "gemini";
for (const tool2 of tools) {
server.addTool(tool2);
const processedTool = shouldSanitize ? sanitizeTool(tool2) : tool2;
server.addTool(processedTool);
}
return server;
};
@@ -121769,14 +121833,12 @@ function buildCommentFooter(payload) {
const runId = process.env.GITHUB_RUN_ID;
const agentName = payload.agent;
const agentInfo = agentName ? agentsManifest[agentName] : null;
const agentDisplayName = agentInfo?.displayName || "Unknown Agent";
const agentDisplayName = agentInfo?.displayName || "Unknown agent";
const agentUrl = agentInfo?.url || "https://pullfrog.ai";
const workflowRunPart = runId ? `[View workflow run](https://github.com/${repoContext.owner}/${repoContext.name}/actions/runs/${runId})` : "(workflow link unavailable)";
const workflowRunPart = runId ? `[View workflow run](https://github.com/${repoContext.owner}/${repoContext.name}/actions/runs/${runId})` : "View workflow run";
return `
${PULLFROG_DIVIDER}
---
<sup>\u{1F438} Triggered by [Pullfrog](https://pullfrog.ai) | \u{1F916} [${agentDisplayName}](${agentUrl}) | ${workflowRunPart} | [\u{1D54F}](https://x.com/pullfrogai)</sup>`;
<sup><a href="https://pullfrog.ai"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.ai/logos/frog-white-full-128px.png"><img src="https://pullfrog.ai/logos/frog-green-full-128px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>&nbsp;&nbsp;\uFF5C Triggered by [Pullfrog](https://pullfrog.ai) \uFF5C Using [${agentDisplayName}](${agentUrl}) \uFF5C ${workflowRunPart} \uFF5C [\u{1D54F}](https://x.com/pullfrogai)</sup>`;
}
function stripExistingFooter(body) {
const dividerIndex = body.indexOf(PULLFROG_DIVIDER);
@@ -121889,7 +121951,10 @@ var ReportProgressTool = tool({
}
const issueNumber = ctx.payload.event.issue_number;
if (issueNumber === void 0) {
return { suggess: true };
return {
success: false,
message: "cannot create progress comment: no issue_number found in the payload event"
};
}
const result = await ctx.octokit.rest.issues.createComment({
owner: ctx.owner,
@@ -122847,7 +122912,11 @@ async function startMcpServer(ctx) {
}
}
const allModes = [...modes, ...ctx.payload.modes || []];
const { url: url2, close } = await startMcpHttpServer({ payload: ctx.payload, modes: allModes });
const { url: url2, close } = await startMcpHttpServer({
payload: ctx.payload,
modes: allModes,
agentName: ctx.agentName
});
ctx.mcpServerUrl = url2;
ctx.mcpServerClose = close;
log.info(`\u{1F680} MCP server started at ${url2}`);
+1 -1
View File
@@ -1 +1 @@
summarize https://github.com/pullfrogai/scratch/issues/56, its status, and pertinent discussion on the issue
Implement a fun new feature in a new branch. No PR.
+5 -2
View File
@@ -281,9 +281,12 @@ async function startMcpServer(ctx: MainContext): Promise<void> {
log.info(`📝 Using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
}
}
const allModes = [...modes, ...(ctx.payload.modes || [])];
const { url, close } = await startMcpHttpServer({ payload: ctx.payload, modes: allModes });
const { url, close } = await startMcpHttpServer({
payload: ctx.payload,
modes: allModes,
agentName: ctx.agentName,
});
ctx.mcpServerUrl = url;
ctx.mcpServerClose = close;
log.info(`🚀 MCP server started at ${url}`);
+7 -6
View File
@@ -12,19 +12,17 @@ function buildCommentFooter(payload: Payload): string {
const agentName = payload.agent;
const agentInfo = agentName ? agentsManifest[agentName] : null;
const agentDisplayName = agentInfo?.displayName || "Unknown Agent";
const agentDisplayName = agentInfo?.displayName || "Unknown agent";
const agentUrl = agentInfo?.url || "https://pullfrog.ai";
// build workflow run link or show unavailable message
const workflowRunPart = runId
? `[View workflow run](https://github.com/${repoContext.owner}/${repoContext.name}/actions/runs/${runId})`
: "(workflow link unavailable)";
: "View workflow run";
return `
${PULLFROG_DIVIDER}
---
<sup>🐸 Triggered by [Pullfrog](https://pullfrog.ai) | 🤖 [${agentDisplayName}](${agentUrl}) | ${workflowRunPart} | [𝕏](https://x.com/pullfrogai)</sup>`;
<sup><a href="https://pullfrog.ai"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.ai/logos/frog-white-full-128px.png"><img src="https://pullfrog.ai/logos/frog-green-full-128px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>&nbsp;&nbsp; Triggered by [Pullfrog](https://pullfrog.ai) Using [${agentDisplayName}](${agentUrl}) ${workflowRunPart} [𝕏](https://x.com/pullfrogai)</sup>`;
}
function stripExistingFooter(body: string): string {
@@ -167,7 +165,10 @@ export const ReportProgressTool = tool({
const issueNumber = ctx.payload.event.issue_number;
if (issueNumber === undefined) {
// fail silently
return { suggess: true };
return {
success: false,
message: "cannot create progress comment: no issue_number found in the payload event",
};
// throw new Error(
// "cannot create progress comment: no issue_number found in the payload event"
// );
+84 -1
View File
@@ -8,6 +8,7 @@ import { getGitHubInstallationToken, parseRepoContext, type RepoContext } from "
export interface McpInitContext {
payload: Payload;
modes: Mode[];
agentName?: string;
}
let mcpInitContext: McpInitContext | undefined;
@@ -36,9 +37,91 @@ export function getMcpContext(): McpContext {
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => toolDef;
/**
* Sanitize JSON schema to remove problematic fields that Gemini CLI can't handle
* - Removes $schema field (causes "no schema with key or ref" errors)
* - Converts $defs to definitions (draft-07 compatibility)
* - Removes any draft-2020-12 specific features
*/
function sanitizeSchema(schema: any): any {
if (!schema || typeof schema !== "object") {
return schema;
}
if (Array.isArray(schema)) {
return schema.map(sanitizeSchema);
}
const sanitized: any = {};
for (const [key, value] of Object.entries(schema)) {
// skip $schema field entirely
if (key === "$schema") {
continue;
}
// convert $defs to definitions for draft-07 compatibility
if (key === "$defs") {
sanitized.definitions = sanitizeSchema(value);
continue;
}
// recursively sanitize nested objects
sanitized[key] = sanitizeSchema(value);
}
return sanitized;
}
/**
* Wrap a StandardSchemaV1 to intercept toJsonSchema() calls and sanitize the output
*/
function wrapSchema(schema: StandardSchemaV1<any>): StandardSchemaV1<any> {
const originalToJsonSchema = (schema as any).toJsonSchema?.bind(schema);
if (!originalToJsonSchema) {
return schema;
}
// create a proxy that intercepts toJsonSchema calls
return new Proxy(schema, {
get(target, prop) {
if (prop === "toJsonSchema") {
return () => {
const originalSchema = originalToJsonSchema();
return sanitizeSchema(originalSchema);
};
}
return (target as any)[prop];
},
}) as StandardSchemaV1<any>;
}
/**
* Transform tool to sanitize its parameter schema for Gemini CLI compatibility
*/
function sanitizeTool<T extends Tool<any, any>>(tool: T): T {
if (!tool.parameters) {
return tool;
}
// wrap the schema object to intercept toJsonSchema() calls
const wrappedSchema = wrapSchema(tool.parameters);
// create a new tool with wrapped schema
return {
...tool,
parameters: wrappedSchema,
} as T;
}
export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
// only sanitize schemas for gemini agent (it has issues with draft-2020-12 schemas)
const shouldSanitize = mcpInitContext?.agentName === "gemini";
for (const tool of tools) {
server.addTool(tool);
const processedTool = shouldSanitize ? sanitizeTool(tool) : tool;
server.addTool(processedTool);
}
return server;
};
+18 -4
View File
@@ -26,9 +26,21 @@ export const modes: Mode[] = [
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"). Create a PR with an informative title and body. If relevant, include links to the issue or comment that triggered the PR.
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. Call report_progress one final time with a summary of the results. Include links to any created issues/PRs, e.g. \`[View PR](https://github.com/org/repo/pull/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.
- 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
[View PR ➔](https://github.com/org/repo/pull/123)
\`\`\`
- If you created a branch without a PR, ALWAYS include a "Create PR" link and a link to the branch. e.g.:
\`\`\`md
[\`pullfrog/branch-name\`](https://github.com/pullfrogai/scratch/tree/pullfrog/branch-name) • [Create PR ➔](https://github.com/pullfrogai/scratch/compare/main...pullfrog/branch-name?quick_pull=1&title=<informative_title>&body=<informative_body>)
\`\`\`
`,
},
{
@@ -90,10 +102,12 @@ export const modes: Mode[] = [
{
name: "Prompt",
description:
"Fallback for tasks that don't fit other workflows, direct prompts via comments, or requests requiring general assistance without a specific workflow pattern",
"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.
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.
@@ -102,6 +116,6 @@ export const modes: Mode[] = [
3. ${reportProgressInstruction}
4. Call report_progress one final time with a summary of the results and links to any created issues, PRs, etc.`,
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.`,
},
];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@pullfrog/action",
"version": "0.0.123",
"version": "0.0.124",
"type": "module",
"files": [
"index.js",