refactor instructions to return object with full/system/user/event/runtime properties, fix duplicate modes and json prompt extraction (#110)

This commit is contained in:
Colin McDonnell
2026-01-16 21:43:54 +00:00
committed by pullfrog[bot]
parent 410b11db71
commit cb925556e8
16 changed files with 1843 additions and 1952 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ export const claude = agent({
};
const queryInstance = query({
prompt: ctx.instructions,
prompt: ctx.instructions.full,
options: queryOptions,
});
+1 -1
View File
@@ -129,7 +129,7 @@ export const codex = agent({
const thread = codex.startThread(threadOptions);
try {
const streamedTurn = await thread.runStreamed(ctx.instructions);
const streamedTurn = await thread.runStreamed(ctx.instructions.full);
let finalOutput = "";
for await (const event of streamedTurn.events) {
+1 -1
View File
@@ -205,7 +205,7 @@ export const cursor = agent({
// build CLI args
const baseArgs = [
"--print",
ctx.instructions,
ctx.instructions.full,
"--output-format",
"stream-json",
"--approve-mcps",
+1 -1
View File
@@ -188,7 +188,7 @@ export const gemini = agent({
"--yolo",
"--output-format=stream-json",
"-p",
ctx.instructions,
ctx.instructions.full,
];
let finalOutput = "";
+1 -1
View File
@@ -30,7 +30,7 @@ export const opencode = agent({
configureOpenCode(ctx);
// message positional must come right after "run", before flags
const args = ["run", ctx.instructions, "--format", "json"];
const args = ["run", ctx.instructions.full, "--format", "json"];
process.env.HOME = tempHome;
+5 -2
View File
@@ -1,6 +1,7 @@
import type { show } from "@ark/util";
import { type AgentManifest, type AgentName, agentsManifest } from "../external.ts";
import { log } from "../utils/cli.ts";
import type { ResolvedInstructions } from "../utils/instructions.ts";
import type { ResolvedPayload } from "../utils/payload.ts";
/**
@@ -20,7 +21,7 @@ export interface AgentRunContext {
payload: ResolvedPayload;
mcpServerUrl: string;
tmpdir: string;
instructions: string;
instructions: ResolvedInstructions;
}
export const agent = <const input extends AgentInput>(input: input): defineAgent<input> => {
@@ -32,7 +33,9 @@ export const agent = <const input extends AgentInput>(input: input): defineAgent
const search = ctx.payload.search;
const write = ctx.payload.write;
log.info(`» running ${input.name} with effort=${ctx.payload.effort}...`);
log.box(ctx.instructions, { title: "Instructions" });
log.box(ctx.instructions.user.trim() + "\n\n" + ctx.instructions.event.trim(), {
title: "Instructions",
});
log.info(`» tool permissions: web=${web}, search=${search}, write=${write}, bash=${bash}`);
return input.run(ctx);
},
+1683 -1723
View File
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -1,9 +1,9 @@
import { ensureProgressCommentUpdated } from "./mcp/comment.ts";
import { startMcpHttpServer, type ToolContext, type ToolState } from "./mcp/server.ts";
import { initToolState, startMcpHttpServer } from "./mcp/server.ts";
import { computeModes } from "./modes.ts";
import { resolveAgent } from "./utils/agent.ts";
import { validateApiKey } from "./utils/apiKeys.ts";
import { log } from "./utils/cli.ts";
import { log, writeSummary } from "./utils/cli.ts";
import { reportErrorToComment } from "./utils/errorReport.ts";
import { createOctokit } from "./utils/github.ts";
import { resolveInstructions } from "./utils/instructions.ts";
@@ -37,7 +37,7 @@ export async function main(): Promise<MainResult> {
const octokit = createOctokit(tokenRef.token);
const runInfo = await resolveRun({ octokit });
const hasProgressComment = runInfo.workflowRunInfo.progressCommentId !== null;
const toolState = initToolState({ runInfo });
try {
const repo = await resolveRepoData({ octokit, token: tokenRef.token });
@@ -50,7 +50,7 @@ export async function main(): Promise<MainResult> {
process.chdir(payload.cwd);
}
const tmpdir = await createTempDirectory();
const tmpdir = createTempDirectory();
const agent = resolveAgent({ payload, repoSettings: repo.repoSettings });
@@ -60,7 +60,6 @@ export async function main(): Promise<MainResult> {
name: repo.name,
});
const toolState: ToolState = {};
await setupGit({
token: tokenRef.token,
owner: repo.owner,
@@ -71,9 +70,9 @@ export async function main(): Promise<MainResult> {
});
timer.checkpoint("git");
const modes = [...computeModes({ hasProgressComment }), ...repo.repoSettings.modes];
const modes = [...computeModes(), ...repo.repoSettings.modes];
const toolContext: ToolContext = {
await using mcpHttpServer = await startMcpHttpServer({
repo,
payload,
octokit,
@@ -83,19 +82,14 @@ export async function main(): Promise<MainResult> {
toolState,
runId: runInfo.runId,
jobId: runInfo.jobId,
hasProgressComment,
};
await using mcpHttpServer = await startMcpHttpServer(toolContext);
});
log.info(`» MCP server started at ${mcpHttpServer.url}`);
timer.checkpoint("mcpServer");
const instructions = resolveInstructions({
prompt: payload.prompt,
event: payload.event,
payload,
repoData: repo,
modes,
bash: payload.bash,
});
const result = await agent.run({
@@ -104,13 +98,19 @@ export async function main(): Promise<MainResult> {
tmpdir,
instructions,
});
// write last progress body to job summary
if (toolState.lastProgressBody) {
writeSummary(toolState.lastProgressBody);
}
const mainResult = await handleAgentResult(result);
return mainResult;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
log.error(errorMessage);
try {
await reportErrorToComment({ error: errorMessage });
await reportErrorToComment({ toolState, error: errorMessage });
} catch {
// error reporting failed, but don't let it mask the original error
}
@@ -122,7 +122,7 @@ export async function main(): Promise<MainResult> {
// ensure progress comment is updated if it was never updated during execution
// do this before revoking the token so we can still make API calls
try {
await ensureProgressCommentUpdated({ hasProgressComment });
await ensureProgressCommentUpdated(toolState);
} catch {
// error updating comment, but don't let it mask the original error
}
+42 -95
View File
@@ -1,11 +1,10 @@
import { type } from "arktype";
import type { Agent } from "../agents/index.ts";
import { buildPullfrogFooter, stripExistingFooter } from "../utils/buildPullfrogFooter.ts";
import { writeSummary } from "../utils/cli.ts";
import { createOctokit, type OctokitWithPlugins, parseRepoContext } from "../utils/github.ts";
import { getGitHubInstallationToken } from "../utils/token.ts";
import { fetchWorkflowRunInfo } from "../utils/workflowRun.ts";
import type { ToolContext } from "./server.ts";
import type { ToolContext, ToolState } from "./server.ts";
import { execute, tool } from "./shared.ts";
/**
@@ -153,42 +152,6 @@ export function EditCommentTool(ctx: ToolContext) {
});
}
/**
* Get progress comment ID from environment variable.
* This allows the webhook handler to pre-create a "leaping into action" comment
* and pass the ID to the action for updates.
*/
function getProgressCommentIdFromEnv(): number | null {
const envCommentId = process.env.PULLFROG_PROGRESS_COMMENT_ID;
if (envCommentId) {
const parsed = parseInt(envCommentId, 10);
if (!Number.isNaN(parsed)) {
return parsed;
}
}
return null;
}
// progress comment state - initialized lazily on first use to allow env var to be set after module load
const progressComment = {
id: null as number | null,
idInitialized: false,
wasUpdated: false,
};
function getProgressCommentId(): number | null {
if (!progressComment.idInitialized) {
progressComment.id = getProgressCommentIdFromEnv();
progressComment.idInitialized = true;
}
return progressComment.id;
}
function setProgressCommentId(id: number): void {
progressComment.id = id;
progressComment.idInitialized = true;
}
export const ReportProgress = type({
body: type.string.describe("the progress update content to share"),
});
@@ -196,21 +159,22 @@ export const ReportProgress = type({
/**
* Standalone function to report progress to GitHub comment.
* Can be called directly without going through the MCP tool interface.
* Returns result data if successful, undefined if comment cannot be created.
* Returns result data if successful.
* When there's no comment target (no progressCommentId and no issueNumber), returns a "skipped" result.
*/
export async function reportProgress(
ctx: ToolContext,
{ body }: { body: string }
): Promise<
| {
commentId: number;
url: string;
body: string;
action: "created" | "updated";
}
| undefined
> {
const existingCommentId = getProgressCommentId();
): Promise<{
commentId?: number;
url?: string;
body: string;
action: "created" | "updated" | "skipped";
}> {
// always track the body for job summary
ctx.toolState.lastProgressBody = body;
const existingCommentId = ctx.toolState.progressComment.id;
const issueNumber =
ctx.toolState.prNumber ?? ctx.toolState.issueNumber ?? ctx.payload.event.issue_number;
const isPlanMode = ctx.toolState.selectedMode === "Plan";
@@ -237,9 +201,7 @@ export async function reportProgress(
body: bodyWithFooter,
});
progressComment.wasUpdated = true;
writeSummary(bodyWithFooter);
ctx.toolState.progressComment.wasUpdated = true;
return {
commentId: result.data.id,
@@ -249,11 +211,12 @@ export async function reportProgress(
};
}
// no existing comment - create one
// no existing comment - need an issue/PR to create one on
// use fallback chain: dynamically set context > event payload
if (issueNumber === undefined) {
// cannot create comment without issue_number (e.g., workflow_dispatch events)
return undefined;
// no-op: no comment target (e.g., workflow_dispatch events)
// body is already tracked for job summary
return { body, action: "skipped" };
}
// for new comments, we need to create first, then update with Plan link if in Plan mode
@@ -267,8 +230,10 @@ export async function reportProgress(
});
// store the comment ID for future updates
setProgressCommentId(result.data.id);
progressComment.wasUpdated = true;
ctx.toolState.progressComment = {
id: result.data.id,
wasUpdated: true,
};
// if Plan mode, update the comment to add the "Implement plan" link
if (isPlanMode) {
@@ -290,8 +255,6 @@ export async function reportProgress(
body: bodyWithPlanLink,
});
writeSummary(bodyWithPlanLink);
return {
commentId: updateResult.data.id,
url: updateResult.data.html_url,
@@ -300,8 +263,6 @@ export async function reportProgress(
};
}
writeSummary(initialBody);
return {
commentId: result.data.id,
url: result.data.html_url,
@@ -319,13 +280,12 @@ export function ReportProgressTool(ctx: ToolContext) {
execute: execute(async ({ body }) => {
const result = await reportProgress(ctx, { body });
if (!result) {
// gracefully handle case where no comment can be created
// this happens for workflow_dispatch events or when there's no associated issue/PR
if (result.action === "skipped") {
// no-op: no comment target, but progress is still tracked for job summary
return {
success: false,
success: true,
message:
"cannot create progress comment: no issue_number found in the payload event. this may occur for workflow_dispatch events or when there is no associated issue/PR. if you need to comment on a specific issue or PR, use create_issue_comment with an explicit issueNumber.",
"progress recorded (no GitHub comment created - this may occur for workflow_dispatch events or when there is no associated issue/PR)",
};
}
@@ -337,19 +297,12 @@ export function ReportProgressTool(ctx: ToolContext) {
});
}
/**
* Check if the progress comment was updated during execution
*/
export function wasProgressCommentUpdated(): boolean {
return progressComment.wasUpdated;
}
/**
* Delete the progress comment if it exists.
* Used after submitting a PR review since the review body contains all necessary info.
*/
export async function deleteProgressComment(ctx: ToolContext): Promise<boolean> {
const existingCommentId = getProgressCommentId();
const existingCommentId = ctx.toolState.progressComment.id;
if (!existingCommentId) {
return false;
}
@@ -370,42 +323,37 @@ export async function deleteProgressComment(ctx: ToolContext): Promise<boolean>
}
// reset state but mark as "updated" so ensureProgressCommentUpdated doesn't try to handle it
progressComment.id = null;
progressComment.idInitialized = true; // keep initialized so we don't re-fetch from env
progressComment.wasUpdated = true; // mark as handled so ensureProgressCommentUpdated skips
ctx.toolState.progressComment = {
id: null,
wasUpdated: true,
};
return true;
}
interface EnsureProgressCommentUpdatedParams {
hasProgressComment: boolean;
}
/**
* Ensure the progress comment is updated with a generic error message if it was never updated.
* This should be called after agent execution completes to handle cases where the agent
* exited without ever calling reportProgress.
*
* Works even if MCP context is not initialized (e.g., if error occurs before MCP server starts).
* Will fetch comment ID from database if not available in environment variable.
* Will fetch comment ID from database if not available in toolState.
*/
export async function ensureProgressCommentUpdated(
params: EnsureProgressCommentUpdatedParams
): Promise<void> {
export async function ensureProgressCommentUpdated(toolState: ToolState): Promise<void> {
// skip if comment was already updated during execution
if (progressComment.wasUpdated) {
if (toolState.progressComment.wasUpdated) {
return;
}
// skip if no progress comment was created for this run
if (!params.hasProgressComment) {
// skip if there's already a progress body recorded (agent called report_progress)
if (toolState.lastProgressBody) {
return;
}
// try to get comment ID from env var first, then from database if needed
let existingCommentId = getProgressCommentId();
// try to get comment ID from toolState first, then from database if needed
let existingCommentId = toolState.progressComment.id;
// if not in env var, try fetching from database using run ID
// if not in toolState, try fetching from database using run ID
if (!existingCommentId) {
const runId = process.env.GITHUB_RUN_ID;
if (runId) {
@@ -413,9 +361,8 @@ export async function ensureProgressCommentUpdated(
const workflowRunInfo = await fetchWorkflowRunInfo(runId);
if (workflowRunInfo.progressCommentId) {
existingCommentId = parseInt(workflowRunInfo.progressCommentId, 10);
// cache it in env var for future use
if (!Number.isNaN(existingCommentId)) {
process.env.PULLFROG_PROGRESS_COMMENT_ID = workflowRunInfo.progressCommentId;
if (Number.isNaN(existingCommentId)) {
existingCommentId = null;
}
}
} catch {
@@ -496,7 +443,7 @@ export function ReplyToReviewCommentTool(ctx: ToolContext) {
});
// mark progress as updated so ensureProgressCommentUpdated doesn't think the run failed
progressComment.wasUpdated = true;
ctx.toolState.progressComment.wasUpdated = true;
return {
success: true,
+24 -5
View File
@@ -23,6 +23,29 @@ export interface ToolState {
promise: Promise<PrepResult[]> | undefined;
results: PrepResult[] | undefined;
};
progressComment: {
id: number | null;
wasUpdated: boolean;
};
lastProgressBody?: string;
}
import type { ResolveRunResult } from "../utils/workflow.ts";
interface InitToolStateParams {
runInfo: ResolveRunResult;
}
export function initToolState(ctx: InitToolStateParams): ToolState {
const progressCommentIdStr = ctx.runInfo.workflowRunInfo.progressCommentId;
const progressCommentId = progressCommentIdStr ? parseInt(progressCommentIdStr, 10) : null;
return {
progressComment: {
id: Number.isNaN(progressCommentId) ? null : progressCommentId,
wasUpdated: false,
},
};
}
export interface ToolContext {
@@ -35,8 +58,6 @@ export interface ToolContext {
toolState: ToolState;
runId: string;
jobId: string | undefined;
/** true if a progress comment was pre-created for this run */
hasProgressComment: boolean;
}
import { BashTool } from "./bash.ts";
@@ -141,9 +162,7 @@ export async function startMcpHttpServer(
tools.push(BashTool(ctx));
}
if (ctx.hasProgressComment) {
tools.push(ReportProgressTool(ctx));
}
tools.push(ReportProgressTool(ctx));
addTools(ctx, server, tools);
+8 -17
View File
@@ -14,16 +14,11 @@ export const ModeSchema = type({
prompt: "string",
});
export interface ComputeModesParams {
hasProgressComment: boolean;
}
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.`;
const dependencyInstallationStep = `If this task will require running tests, builds, linters, or CLI commands that need installed packages, call \`${ghPullfrogMcpName}/start_dependency_installation\` NOW. This is non-blocking and allows dependencies to install in the background while you continue. Later, call \`${ghPullfrogMcpName}/await_dependency_installation\` before running commands that need them. Skip this step if only reading code or answering questions.`;
export function computeModes(ctx: ComputeModesParams): Mode[] {
const hasProgressComment = ctx.hasProgressComment;
export function computeModes(): Mode[] {
return [
{
name: "Build",
@@ -91,14 +86,10 @@ export function computeModes(ctx: ComputeModesParams): Mode[] {
7. Test your changes to ensure they work correctly.
8. When done, commit your changes with ${ghPullfrogMcpName}/commit_files, then push with ${ghPullfrogMcpName}/push_branch. The push will automatically go to the correct remote (including fork repos). Do not create a new branch or PR - you are updating an existing one.
${
hasProgressComment
? `
9. ${reportProgressInstruction}
**CRITICAL: Keep the progress comment extremely brief.** The summary should be 1-2 sentences max (e.g., "Fixed 3 review comments and pushed changes."). Almost all detail belongs in the individual reply_to_review_comment calls, NOT in the progress comment.`
: ""
}`,
**CRITICAL: Keep the progress comment extremely brief.** The summary should be 1-2 sentences max (e.g., "Fixed 3 review comments and pushed changes."). Almost all detail belongs in the individual reply_to_review_comment calls, NOT in the progress comment.`,
},
{
name: "Review",
@@ -147,14 +138,16 @@ ${
3. Consider dependencies, potential challenges, and implementation order
4. Create a structured plan with clear milestones${hasProgressComment ? `\n\n5. ${reportProgressInstruction}` : ""}`,
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. THINK HARDER.
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.${hasProgressComment ? " When creating comments, always use report_progress. Do not use create_issue_comment." : ""}
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. When creating comments, always use report_progress. Do not use create_issue_comment.
2. If the task involves making code changes:
- Create a branch using ${ghPullfrogMcpName}/create_branch. Branch names should be prefixed with "pullfrog/" and reflect the exact changes you are making. Never commit directly to main, master, or production.
@@ -171,6 +164,4 @@ ${
];
}
export const modes: Mode[] = computeModes({
hasProgressComment: true,
});
export const modes: Mode[] = computeModes();
+7 -45
View File
@@ -1,59 +1,21 @@
import type { ToolState } from "../mcp/server.ts";
import { createOctokit, parseRepoContext } from "./github.ts";
import { getGitHubInstallationToken } from "./token.ts";
import { fetchWorkflowRunInfo } from "./workflowRun.ts";
/**
* Get progress comment ID from environment variable or database.
*/
function getProgressCommentIdFromEnv(): number | null {
const envCommentId = process.env.PULLFROG_PROGRESS_COMMENT_ID;
if (envCommentId) {
const parsed = parseInt(envCommentId, 10);
if (!Number.isNaN(parsed)) {
return parsed;
}
}
return null;
}
export async function reportErrorToComment({
error,
title,
}: {
interface ReportErrorParams {
toolState: ToolState;
error: string;
title?: string;
}): Promise<void> {
const formattedError = title ? `${title}\n\n${error}` : error;
}
// try to get comment ID from env var first, then from database if needed
let commentId = getProgressCommentIdFromEnv();
export async function reportErrorToComment(ctx: ReportErrorParams): Promise<void> {
const formattedError = ctx.title ? `${ctx.title}\n\n${ctx.error}` : ctx.error;
// if not in env var, try fetching from database using run ID
if (!commentId) {
const runId = process.env.GITHUB_RUN_ID;
if (runId) {
try {
const workflowRunInfo = await fetchWorkflowRunInfo(runId);
if (workflowRunInfo.progressCommentId) {
const parsed = parseInt(workflowRunInfo.progressCommentId, 10);
if (!Number.isNaN(parsed)) {
commentId = parsed;
// cache it in env var for future use
process.env.PULLFROG_PROGRESS_COMMENT_ID = workflowRunInfo.progressCommentId;
}
}
} catch {
// database fetch failed, continue without comment ID
}
}
}
// if no comment ID available, can't update comment
const commentId = ctx.toolState.progressComment.id;
if (!commentId) {
return;
}
// update comment directly using GitHub API
const repoContext = parseRepoContext();
const octokit = createOctokit(getGitHubInstallationToken());
+47 -39
View File
@@ -1,20 +1,17 @@
import { execSync } from "node:child_process";
import { encode as toonEncode } from "@toon-format/toon";
import { ghPullfrogMcpName } from "../external.ts";
import { computeModes, type Mode } from "../modes.ts";
import type { Mode } from "../modes.ts";
import type { ResolvedPayload } from "./payload.ts";
import type { RepoData } from "./repoData.ts";
type BashPermission = "disabled" | "restricted" | "enabled";
interface InstructionsInput {
prompt: string;
event: { trigger: string; [key: string]: unknown };
interface InstructionsContext {
payload: ResolvedPayload;
repoData: RepoData;
modes: Mode[];
bash: BashPermission;
}
function buildRuntimeContext(input: InstructionsInput): string {
function buildRuntimeContext(ctx: InstructionsContext): string {
const lines: string[] = [];
lines.push(`working_directory: ${process.cwd()}`);
@@ -27,8 +24,8 @@ function buildRuntimeContext(input: InstructionsInput): string {
// git not available or not in a repo
}
lines.push(`repo: ${input.repoData.owner}/${input.repoData.name}`);
lines.push(`default_branch: ${input.repoData.repo.default_branch}`);
lines.push(`repo: ${ctx.repoData.owner}/${ctx.repoData.name}`);
lines.push(`default_branch: ${ctx.repoData.repo.default_branch}`);
const ghVars: Record<string, string | undefined> = {
github_event_name: process.env.GITHUB_EVENT_NAME,
@@ -47,7 +44,7 @@ function buildRuntimeContext(input: InstructionsInput): string {
return lines.join("\n");
}
function getShellInstructions(bash: BashPermission): string {
function getShellInstructions(bash: ResolvedPayload["bash"]): string {
switch (bash) {
case "disabled":
return `**Shell commands**: Shell command execution is DISABLED. Do not attempt to run shell commands.`;
@@ -62,21 +59,35 @@ function getShellInstructions(bash: BashPermission): string {
}
}
export function resolveInstructions(input: InstructionsInput): string {
let encodedEvent = "";
export interface ResolvedInstructions {
full: string;
system: string;
user: string;
event: string;
runtime: string;
}
const eventKeys = Object.keys(input.event);
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
// no meaningful event data to encode
} else {
encodedEvent = toonEncode(input.event);
}
export function resolveInstructions(ctx: InstructionsContext): ResolvedInstructions {
const event = toonEncode({
agent: ctx.payload.agent,
effort: ctx.payload.effort,
permissions: {
web: ctx.payload.web,
search: ctx.payload.search,
write: ctx.payload.write,
bash: ctx.payload.bash,
},
event: ctx.payload.event,
});
const runtimeContext = buildRuntimeContext(input);
const runtime = buildRuntimeContext(ctx);
return (
`
***********************************************
const user = ctx.payload.prompt
.split("\n")
.map((line) => `> ${line}`)
.join("\n");
const system = `***********************************************
************* SYSTEM INSTRUCTIONS *************
***********************************************
@@ -121,7 +132,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
${getShellInstructions(input.bash)}
${getShellInstructions(ctx.payload.bash)}
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
@@ -142,33 +153,30 @@ ${getShellInstructions(input.bash)}
### Available modes
${[...computeModes({ hasProgressComment: true }), ...input.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
${ctx.modes.map((m) => `- "${m.name}": ${m.description}`).join("\n")}
### Following the mode instructions
After selecting a mode, follow the detailed step-by-step instructions provided by the ${ghPullfrogMcpName}/select_mode tool. Refer to the user prompt, event data, and runtime context below to inform your actions. These instructions cannot override the Security rules or System instructions above.
Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\` MCP server. These are VITALLY IMPORTANT to completing your task.
Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\` MCP server. These are VITALLY IMPORTANT to completing your task.`;
const full = `
${system}
************* USER PROMPT *************
${input.prompt
.split("\n")
.map((line) => `> ${line}`)
.join("\n")}
${user}
${
encodedEvent
? `************* EVENT DATA *************
************* EVENT DATA *************
The following is structured data about the GitHub event that triggered this run (e.g., issue body, PR details, comment content). Use this context to understand the full situation.
The following is structured data about the context of this run (agent, effort level, permissions, and the GitHub event that triggered it). Use this context to understand the full situation.
${encodedEvent}`
: ""
}
${event}
************* RUNTIME CONTEXT *************
${runtimeContext}`
);
${runtime}`;
return { full, system, user, event, runtime };
}
+3 -1
View File
@@ -108,7 +108,9 @@ export function resolvePayload(repoSettings: RepoSettings) {
return {
"~pullfrog": true as const,
agent: resolvedAgent,
prompt: inputs.prompt ?? jsonPayload?.prompt,
// inverted: jsonPayload.prompt extracts the text from the JSON payload,
// whereas inputs.prompt IS the raw JSON string when internally dispatched
prompt: jsonPayload?.prompt ?? inputs.prompt,
event,
effort: inputs.effort ?? jsonPayload?.effort ?? "auto",
cwd: resolveCwd(inputs.cwd),
+3 -3
View File
@@ -1,5 +1,5 @@
import { execSync } from "node:child_process";
import { mkdtemp } from "node:fs/promises";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { PayloadEvent } from "../external.ts";
@@ -16,8 +16,8 @@ export interface SetupOptions {
/**
* Create a shared temp directory for the action
*/
export async function createTempDirectory(): Promise<string> {
const sharedTempDir = await mkdtemp(join(tmpdir(), "pullfrog-"));
export function createTempDirectory(): string {
const sharedTempDir = mkdtempSync(join(tmpdir(), "pullfrog-"));
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
log.info(`» created temp dir at ${sharedTempDir}`);
return sharedTempDir;
-1
View File
@@ -27,7 +27,6 @@ export async function resolveRun(params: ResolveRunParams): Promise<ResolveRunRe
const workflowRunInfo = runId ? await fetchWorkflowRunInfo(runId) : { progressCommentId: null };
if (workflowRunInfo.progressCommentId) {
process.env.PULLFROG_PROGRESS_COMMENT_ID = workflowRunInfo.progressCommentId;
log.info(`» using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
}