Fixed how payload-as-prompt is handled and progress comment updates (#212)
This commit is contained in:
committed by
pullfrog[bot]
parent
18ba8e5fd0
commit
bfe72ac2cf
@@ -135585,12 +135585,6 @@ var agentsManifest = {
|
|||||||
var AgentName = type.enumerated(...Object.keys(agentsManifest));
|
var AgentName = type.enumerated(...Object.keys(agentsManifest));
|
||||||
var Effort = type.enumerated("mini", "auto", "max");
|
var Effort = type.enumerated("mini", "auto", "max");
|
||||||
|
|
||||||
// mcp/bash.ts
|
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
||||||
import { closeSync, openSync, writeFileSync } from "node:fs";
|
|
||||||
import { join } from "node:path";
|
|
||||||
|
|
||||||
// utils/log.ts
|
// utils/log.ts
|
||||||
var core = __toESM(require_core(), 1);
|
var core = __toESM(require_core(), 1);
|
||||||
var import_table = __toESM(require_src(), 1);
|
var import_table = __toESM(require_src(), 1);
|
||||||
@@ -135767,6 +135761,12 @@ function formatJsonValue(value2) {
|
|||||||
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value2, null, 2) : compact;
|
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value2, null, 2) : compact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mcp/bash.ts
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
||||||
|
import { closeSync, openSync, writeFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
||||||
var LIST_ITEM_MARKER = "-";
|
var LIST_ITEM_MARKER = "-";
|
||||||
var LIST_ITEM_PREFIX = "- ";
|
var LIST_ITEM_PREFIX = "- ";
|
||||||
@@ -143291,10 +143291,12 @@ function UploadFileTool(ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/server.ts
|
// mcp/server.ts
|
||||||
function initToolState(ctx) {
|
function initToolState(params) {
|
||||||
const progressCommentIdStr = ctx.runInfo.workflowRunInfo.progressCommentId;
|
const progressCommentId = params.progressCommentId ? parseInt(params.progressCommentId, 10) : null;
|
||||||
const progressCommentId = progressCommentIdStr ? parseInt(progressCommentIdStr, 10) : null;
|
|
||||||
const resolvedId = Number.isNaN(progressCommentId) ? null : progressCommentId;
|
const resolvedId = Number.isNaN(progressCommentId) ? null : progressCommentId;
|
||||||
|
if (progressCommentId) {
|
||||||
|
log.info(`\xBB using pre-created progress comment: ${progressCommentId}`);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
progressCommentId: resolvedId,
|
progressCommentId: resolvedId,
|
||||||
backgroundProcesses: /* @__PURE__ */ new Map()
|
backgroundProcesses: /* @__PURE__ */ new Map()
|
||||||
@@ -162544,12 +162546,13 @@ var JsonPayload = type({
|
|||||||
"~pullfrog": "true",
|
"~pullfrog": "true",
|
||||||
version: "string",
|
version: "string",
|
||||||
"agent?": AgentName.or("undefined"),
|
"agent?": AgentName.or("undefined"),
|
||||||
"prompt?": "string",
|
prompt: "string",
|
||||||
"eventInstructions?": "string",
|
"eventInstructions?": "string",
|
||||||
"repoInstructions?": "string",
|
"repoInstructions?": "string",
|
||||||
"event?": "object",
|
"event?": "object",
|
||||||
"effort?": Effort.or("undefined"),
|
"effort?": Effort.or("undefined"),
|
||||||
"timeout?": type.string.or("undefined")
|
"timeout?": type.string.or("undefined"),
|
||||||
|
"progressCommentId?": type.string.or("undefined")
|
||||||
});
|
});
|
||||||
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
||||||
function isCollaborator(event) {
|
function isCollaborator(event) {
|
||||||
@@ -162579,9 +162582,23 @@ function resolveCwd(cwd2) {
|
|||||||
if (isAbsolute(cwd2)) return cwd2;
|
if (isAbsolute(cwd2)) return cwd2;
|
||||||
return workspace ? resolve(workspace, cwd2) : cwd2;
|
return workspace ? resolve(workspace, cwd2) : cwd2;
|
||||||
}
|
}
|
||||||
function resolvePayload(repoSettings) {
|
function resolvePromptInput() {
|
||||||
const inputs = Inputs.assert({
|
const prompt = core4.getInput("prompt", { required: true });
|
||||||
prompt: core4.getInput("prompt", { required: true }),
|
let parsed2;
|
||||||
|
try {
|
||||||
|
parsed2 = JSON.parse(prompt);
|
||||||
|
} catch {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
const jsonPayload = JsonPayload.assert(parsed2);
|
||||||
|
validateCompatibility(jsonPayload.version, package_default.version);
|
||||||
|
return jsonPayload;
|
||||||
|
}
|
||||||
|
function resolveNonPromptInputs() {
|
||||||
|
return Inputs.omit("prompt").assert({
|
||||||
effort: core4.getInput("effort") || void 0,
|
effort: core4.getInput("effort") || void 0,
|
||||||
timeout: core4.getInput("timeout") || void 0,
|
timeout: core4.getInput("timeout") || void 0,
|
||||||
agent: core4.getInput("agent") || void 0,
|
agent: core4.getInput("agent") || void 0,
|
||||||
@@ -162591,19 +162608,11 @@ function resolvePayload(repoSettings) {
|
|||||||
write: core4.getInput("write") || void 0,
|
write: core4.getInput("write") || void 0,
|
||||||
bash: core4.getInput("bash") || void 0
|
bash: core4.getInput("bash") || void 0
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
function resolvePayload(resolvedPromptInput, repoSettings) {
|
||||||
|
const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
|
||||||
|
const inputs = resolveNonPromptInputs();
|
||||||
const agent2 = inputs.agent !== void 0 && isAgentName(inputs.agent) ? inputs.agent : void 0;
|
const agent2 = inputs.agent !== void 0 && isAgentName(inputs.agent) ? inputs.agent : void 0;
|
||||||
let jsonPayload = null;
|
|
||||||
try {
|
|
||||||
const parsed2 = JSON.parse(inputs.prompt);
|
|
||||||
if (parsed2 && typeof parsed2 === "object" && "~pullfrog" in parsed2) {
|
|
||||||
jsonPayload = JsonPayload.assert(parsed2);
|
|
||||||
}
|
|
||||||
} catch (error50) {
|
|
||||||
if (error50 instanceof type.errors) {
|
|
||||||
throw new Error(`invalid pullfrog payload: ${error50.summary}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jsonPayload) validateCompatibility(jsonPayload.version, package_default.version);
|
|
||||||
const rawEvent = jsonPayload?.event;
|
const rawEvent = jsonPayload?.event;
|
||||||
const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
||||||
const jsonAgent = jsonPayload?.agent;
|
const jsonAgent = jsonPayload?.agent;
|
||||||
@@ -162624,9 +162633,7 @@ function resolvePayload(repoSettings) {
|
|||||||
"~pullfrog": true,
|
"~pullfrog": true,
|
||||||
version: jsonPayload?.version ?? package_default.version,
|
version: jsonPayload?.version ?? package_default.version,
|
||||||
agent: resolvedAgent,
|
agent: resolvedAgent,
|
||||||
// inverted: jsonPayload.prompt extracts the text from the JSON payload,
|
prompt,
|
||||||
// whereas inputs.prompt IS the raw JSON string when internally dispatched
|
|
||||||
prompt: jsonPayload?.prompt ?? inputs.prompt,
|
|
||||||
eventInstructions: jsonPayload?.eventInstructions,
|
eventInstructions: jsonPayload?.eventInstructions,
|
||||||
repoInstructions: jsonPayload?.repoInstructions,
|
repoInstructions: jsonPayload?.repoInstructions,
|
||||||
event,
|
event,
|
||||||
@@ -162835,35 +162842,6 @@ var Timer = class {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// utils/workflowRun.ts
|
|
||||||
async function fetchWorkflowRunInfo(params) {
|
|
||||||
const apiUrl = process.env.API_URL || "https://pullfrog.com";
|
|
||||||
const timeoutMs = 3e4;
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/api/workflow-run/${params.runId}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
// apiToken not strictly required (route only exposes public IDs)
|
|
||||||
// but claims in token enable preview API URL forwarding
|
|
||||||
Authorization: `Bearer ${params.apiToken}`,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
signal: controller.signal
|
|
||||||
});
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
if (!response.ok) {
|
|
||||||
return { progressCommentId: null };
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
return { progressCommentId: null };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// utils/workflow.ts
|
// utils/workflow.ts
|
||||||
async function resolveRun(params) {
|
async function resolveRun(params) {
|
||||||
const runId = process.env.GITHUB_RUN_ID || "";
|
const runId = process.env.GITHUB_RUN_ID || "";
|
||||||
@@ -162872,10 +162850,6 @@ async function resolveRun(params) {
|
|||||||
throw new Error(`GITHUB_REPOSITORY env var must be set to "owner/repo", got: ${githubRepo}`);
|
throw new Error(`GITHUB_REPOSITORY env var must be set to "owner/repo", got: ${githubRepo}`);
|
||||||
}
|
}
|
||||||
const [owner, repo] = githubRepo.split("/");
|
const [owner, repo] = githubRepo.split("/");
|
||||||
const workflowRunInfo = runId ? await fetchWorkflowRunInfo({ runId, apiToken: params.apiToken }) : { progressCommentId: null };
|
|
||||||
if (workflowRunInfo.progressCommentId) {
|
|
||||||
log.info(`\xBB using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
|
|
||||||
}
|
|
||||||
let jobId;
|
let jobId;
|
||||||
const jobName = process.env.GITHUB_JOB;
|
const jobName = process.env.GITHUB_JOB;
|
||||||
if (jobName && runId) {
|
if (jobName && runId) {
|
||||||
@@ -162890,7 +162864,7 @@ async function resolveRun(params) {
|
|||||||
log.debug(`\xBB found job ID: ${jobId}`);
|
log.debug(`\xBB found job ID: ${jobId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { runId, jobId, workflowRunInfo };
|
return { runId, jobId };
|
||||||
}
|
}
|
||||||
|
|
||||||
// main.ts
|
// main.ts
|
||||||
@@ -162900,17 +162874,20 @@ async function main() {
|
|||||||
normalizeEnv();
|
normalizeEnv();
|
||||||
const timer = new Timer();
|
const timer = new Timer();
|
||||||
let activityTimeout = null;
|
let activityTimeout = null;
|
||||||
|
const resolvedPromptInput = resolvePromptInput();
|
||||||
|
const toolState = initToolState({
|
||||||
|
progressCommentId: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressCommentId : void 0
|
||||||
|
});
|
||||||
|
setupExitHandler(toolState);
|
||||||
const tokenRef = __using(_stack2, await resolveInstallationToken(), true);
|
const tokenRef = __using(_stack2, await resolveInstallationToken(), true);
|
||||||
const octokit = createOctokit(tokenRef.token);
|
const octokit = createOctokit(tokenRef.token);
|
||||||
const runContext = await resolveRunContextData({ octokit, token: tokenRef.token });
|
const runContext = await resolveRunContextData({ octokit, token: tokenRef.token });
|
||||||
timer.checkpoint("runContextData");
|
timer.checkpoint("runContextData");
|
||||||
const runInfo = await resolveRun({ octokit, apiToken: runContext.apiToken });
|
const runInfo = await resolveRun({ octokit });
|
||||||
const toolState = initToolState({ runInfo });
|
|
||||||
setupExitHandler(toolState);
|
|
||||||
try {
|
try {
|
||||||
var _stack = [];
|
var _stack = [];
|
||||||
try {
|
try {
|
||||||
const payload = resolvePayload(runContext.repoSettings);
|
const payload = resolvePayload(resolvedPromptInput, runContext.repoSettings);
|
||||||
if (payload.cwd && process.cwd() !== payload.cwd) {
|
if (payload.cwd && process.cwd() !== payload.cwd) {
|
||||||
process.chdir(payload.cwd);
|
process.chdir(payload.cwd);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -47,7 +47,7 @@ export const agentsManifest = {
|
|||||||
|
|
||||||
// agent name type - union of agent slugs
|
// agent name type - union of agent slugs
|
||||||
export type AgentName = keyof typeof agentsManifest;
|
export type AgentName = keyof typeof agentsManifest;
|
||||||
export const AgentName = type.enumerated(...Object.keys(agentsManifest));
|
export const AgentName = type.enumerated(...(Object.keys(agentsManifest) as AgentName[]));
|
||||||
|
|
||||||
export type AgentApiKeyName = (typeof agentsManifest)[AgentName]["apiKeyNames"][number];
|
export type AgentApiKeyName = (typeof agentsManifest)[AgentName]["apiKeyNames"][number];
|
||||||
|
|
||||||
@@ -259,6 +259,8 @@ export interface WriteablePayload {
|
|||||||
timeout?: string | undefined;
|
timeout?: string | undefined;
|
||||||
/** working directory for the agent */
|
/** working directory for the agent */
|
||||||
cwd?: string | undefined;
|
cwd?: string | undefined;
|
||||||
|
/** pre-created progress comment ID for updating status */
|
||||||
|
progressCommentId?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// immutable payload type for agent execution
|
// immutable payload type for agent execution
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { setupExitHandler } from "./utils/exitHandler.ts";
|
|||||||
import { createOctokit } from "./utils/github.ts";
|
import { createOctokit } from "./utils/github.ts";
|
||||||
import { resolveInstructions } from "./utils/instructions.ts";
|
import { resolveInstructions } from "./utils/instructions.ts";
|
||||||
import { normalizeEnv } from "./utils/normalizeEnv.ts";
|
import { normalizeEnv } from "./utils/normalizeEnv.ts";
|
||||||
import { resolvePayload } from "./utils/payload.ts";
|
import { resolvePayload, resolvePromptInput } from "./utils/payload.ts";
|
||||||
import { handleAgentResult } from "./utils/run.ts";
|
import { handleAgentResult } from "./utils/run.ts";
|
||||||
import { resolveRunContextData } from "./utils/runContextData.ts";
|
import { resolveRunContextData } from "./utils/runContextData.ts";
|
||||||
import { createTempDirectory, setupGit } from "./utils/setup.ts";
|
import { createTempDirectory, setupGit } from "./utils/setup.ts";
|
||||||
@@ -42,22 +42,28 @@ export async function main(): Promise<MainResult> {
|
|||||||
const timer = new Timer();
|
const timer = new Timer();
|
||||||
let activityTimeout: ActivityTimeout | null = null;
|
let activityTimeout: ActivityTimeout | null = null;
|
||||||
|
|
||||||
|
// parse prompt early to extract progressCommentId for toolState
|
||||||
|
const resolvedPromptInput = resolvePromptInput();
|
||||||
|
|
||||||
|
const toolState = initToolState({
|
||||||
|
progressCommentId:
|
||||||
|
typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressCommentId : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
setupExitHandler(toolState);
|
||||||
|
|
||||||
await using tokenRef = await resolveInstallationToken();
|
await using tokenRef = await resolveInstallationToken();
|
||||||
|
|
||||||
const octokit = createOctokit(tokenRef.token);
|
const octokit = createOctokit(tokenRef.token);
|
||||||
const runContext = await resolveRunContextData({ octokit, token: tokenRef.token });
|
const runContext = await resolveRunContextData({ octokit, token: tokenRef.token });
|
||||||
timer.checkpoint("runContextData");
|
timer.checkpoint("runContextData");
|
||||||
|
|
||||||
const runInfo = await resolveRun({ octokit, apiToken: runContext.apiToken });
|
const runInfo = await resolveRun({ octokit });
|
||||||
const toolState = initToolState({ runInfo });
|
|
||||||
|
|
||||||
setupExitHandler(toolState);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// resolve payload after runContextData so permissions can use DB settings
|
// resolve payload after runContextData so permissions can use DB settings
|
||||||
// precedence: action inputs > json payload > repoSettings > fallbacks
|
// precedence: action inputs > json payload > repoSettings > fallbacks
|
||||||
const payload = resolvePayload(runContext.repoSettings);
|
const payload = resolvePayload(resolvedPromptInput, runContext.repoSettings);
|
||||||
if (payload.cwd && process.cwd() !== payload.cwd) {
|
if (payload.cwd && process.cwd() !== payload.cwd) {
|
||||||
process.chdir(payload.cwd);
|
process.chdir(payload.cwd);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -35,17 +35,20 @@ export interface ToolState {
|
|||||||
output?: string;
|
output?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
import type { ResolveRunResult } from "../utils/workflow.ts";
|
|
||||||
|
|
||||||
interface InitToolStateParams {
|
interface InitToolStateParams {
|
||||||
runInfo: ResolveRunResult;
|
progressCommentId: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initToolState(ctx: InitToolStateParams): ToolState {
|
export function initToolState(params: InitToolStateParams): ToolState {
|
||||||
const progressCommentIdStr = ctx.runInfo.workflowRunInfo.progressCommentId;
|
const progressCommentId = params.progressCommentId
|
||||||
const progressCommentId = progressCommentIdStr ? parseInt(progressCommentIdStr, 10) : null;
|
? parseInt(params.progressCommentId, 10)
|
||||||
|
: null;
|
||||||
const resolvedId = Number.isNaN(progressCommentId) ? null : progressCommentId;
|
const resolvedId = Number.isNaN(progressCommentId) ? null : progressCommentId;
|
||||||
|
|
||||||
|
if (progressCommentId) {
|
||||||
|
log.info(`» using pre-created progress comment: ${progressCommentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
progressCommentId: resolvedId,
|
progressCommentId: resolvedId,
|
||||||
backgroundProcesses: new Map(),
|
backgroundProcesses: new Map(),
|
||||||
@@ -65,6 +68,7 @@ export interface ToolContext {
|
|||||||
jobId: string | undefined;
|
jobId: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { log } from "../utils/cli.ts";
|
||||||
import type { RunContextData } from "../utils/runContextData.ts";
|
import type { RunContextData } from "../utils/runContextData.ts";
|
||||||
import { BashTool, KillBackgroundTool } from "./bash.ts";
|
import { BashTool, KillBackgroundTool } from "./bash.ts";
|
||||||
import { CheckoutPrTool } from "./checkout.ts";
|
import { CheckoutPrTool } from "./checkout.ts";
|
||||||
|
|||||||
+14
-6
@@ -49,16 +49,19 @@ describe("Inputs schema", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("JsonPayload schema", () => {
|
describe("JsonPayload schema", () => {
|
||||||
it("requires ~pullfrog and version", () => {
|
it("requires ~pullfrog and version and prompt", () => {
|
||||||
const result = JsonPayload.assert({ "~pullfrog": true, version: "1.2.3" });
|
const result = JsonPayload.assert({
|
||||||
expect(result).toMatchObject({ "~pullfrog": true, version: "1.2.3" });
|
"~pullfrog": true,
|
||||||
|
version: "1.2.3",
|
||||||
|
prompt: "test prompt",
|
||||||
|
});
|
||||||
|
expect(result).toMatchObject({ "~pullfrog": true, version: "1.2.3", prompt: "test prompt" });
|
||||||
expect(() => JsonPayload.assert({})).toThrow();
|
expect(() => JsonPayload.assert({})).toThrow();
|
||||||
expect(() => JsonPayload.assert({ "~pullfrog": true })).toThrow();
|
expect(() => JsonPayload.assert({ "~pullfrog": true })).toThrow();
|
||||||
expect(() => JsonPayload.assert({ version: "1.2.3" })).toThrow();
|
expect(() => JsonPayload.assert({ version: "1.2.3" })).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
["prompt", "test prompt"],
|
|
||||||
["agent", "claude"],
|
["agent", "claude"],
|
||||||
["agent", "codex"],
|
["agent", "codex"],
|
||||||
["agent", "cursor"],
|
["agent", "cursor"],
|
||||||
@@ -72,12 +75,17 @@ describe("JsonPayload schema", () => {
|
|||||||
["timeout", "30s"],
|
["timeout", "30s"],
|
||||||
["event", { trigger: "unknown" }],
|
["event", { trigger: "unknown" }],
|
||||||
] as const)("should accept optional %s with value %s", (prop, value) => {
|
] as const)("should accept optional %s with value %s", (prop, value) => {
|
||||||
const input = { "~pullfrog": true, version: "1.2.3", [prop]: value };
|
const input = { "~pullfrog": true, version: "1.2.3", prompt: "test prompt", [prop]: value };
|
||||||
expect(() => JsonPayload.assert(input)).not.toThrow();
|
expect(() => JsonPayload.assert(input)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([["agent"], ["effort"]] as const)("should reject invalid %s values", (prop) => {
|
it.each([["agent"], ["effort"]] as const)("should reject invalid %s values", (prop) => {
|
||||||
const input = { "~pullfrog": true, version: "1.2.3", [prop]: "invalid" as any };
|
const input = {
|
||||||
|
"~pullfrog": true,
|
||||||
|
version: "1.2.3",
|
||||||
|
prompt: "test prompt",
|
||||||
|
[prop]: "invalid" as any,
|
||||||
|
};
|
||||||
expect(() => JsonPayload.assert(input)).toThrow();
|
expect(() => JsonPayload.assert(input)).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+41
-26
@@ -17,12 +17,13 @@ export const JsonPayload = type({
|
|||||||
"~pullfrog": "true",
|
"~pullfrog": "true",
|
||||||
version: "string",
|
version: "string",
|
||||||
"agent?": AgentName.or("undefined"),
|
"agent?": AgentName.or("undefined"),
|
||||||
"prompt?": "string",
|
prompt: "string",
|
||||||
"eventInstructions?": "string",
|
"eventInstructions?": "string",
|
||||||
"repoInstructions?": "string",
|
"repoInstructions?": "string",
|
||||||
"event?": "object",
|
"event?": "object",
|
||||||
"effort?": Effort.or("undefined"),
|
"effort?": Effort.or("undefined"),
|
||||||
"timeout?": type.string.or("undefined"),
|
"timeout?": type.string.or("undefined"),
|
||||||
|
"progressCommentId?": type.string.or("undefined"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// permission levels that indicate collaborator status (have push access)
|
// permission levels that indicate collaborator status (have push access)
|
||||||
@@ -68,9 +69,32 @@ function resolveCwd(cwd: string | undefined): string | undefined {
|
|||||||
return workspace ? resolve(workspace, cwd) : cwd;
|
return workspace ? resolve(workspace, cwd) : cwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolvePayload(repoSettings: RepoSettings) {
|
export type ResolvedPromptInput = string | typeof JsonPayload.infer;
|
||||||
const inputs = Inputs.assert({
|
|
||||||
prompt: core.getInput("prompt", { required: true }),
|
export function resolvePromptInput(): ResolvedPromptInput {
|
||||||
|
const prompt = core.getInput("prompt", { required: true });
|
||||||
|
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(prompt);
|
||||||
|
} catch {
|
||||||
|
// JSON parse error is fine (plain text prompt)
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsed || typeof parsed !== "object" || !("~pullfrog" in parsed)) {
|
||||||
|
// if it doesn't look like a pullfrog payload, return the plain text prompt
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validation errors should propagate
|
||||||
|
const jsonPayload = JsonPayload.assert(parsed);
|
||||||
|
validateCompatibility(jsonPayload.version, packageJson.version);
|
||||||
|
return jsonPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNonPromptInputs() {
|
||||||
|
return Inputs.omit("prompt").assert({
|
||||||
effort: core.getInput("effort") || undefined,
|
effort: core.getInput("effort") || undefined,
|
||||||
timeout: core.getInput("timeout") || undefined,
|
timeout: core.getInput("timeout") || undefined,
|
||||||
agent: core.getInput("agent") || undefined,
|
agent: core.getInput("agent") || undefined,
|
||||||
@@ -80,30 +104,23 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
|||||||
write: core.getInput("write") || undefined,
|
write: core.getInput("write") || undefined,
|
||||||
bash: core.getInput("bash") || undefined,
|
bash: core.getInput("bash") || undefined,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePayload(
|
||||||
|
resolvedPromptInput: ResolvedPromptInput,
|
||||||
|
repoSettings: RepoSettings
|
||||||
|
) {
|
||||||
|
const [prompt, jsonPayload] =
|
||||||
|
typeof resolvedPromptInput !== "string"
|
||||||
|
? [resolvedPromptInput.prompt, resolvedPromptInput]
|
||||||
|
: [resolvedPromptInput, undefined];
|
||||||
|
|
||||||
|
const inputs = resolveNonPromptInputs();
|
||||||
|
|
||||||
// validate agent name
|
// validate agent name
|
||||||
const agent: AgentName | undefined =
|
const agent: AgentName | undefined =
|
||||||
inputs.agent !== undefined && isAgentName(inputs.agent) ? inputs.agent : undefined;
|
inputs.agent !== undefined && isAgentName(inputs.agent) ? inputs.agent : undefined;
|
||||||
|
|
||||||
// try to parse prompt as JSON payload (internal invocation)
|
|
||||||
let jsonPayload: typeof JsonPayload.infer | null = null;
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(inputs.prompt);
|
|
||||||
// if it looks like a pullfrog payload but fails validation, that's an error
|
|
||||||
if (parsed && typeof parsed === "object" && "~pullfrog" in parsed) {
|
|
||||||
jsonPayload = JsonPayload.assert(parsed);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// JSON parse error is fine (plain text prompt), but validation error should propagate
|
|
||||||
if (error instanceof type.errors) {
|
|
||||||
throw new Error(`invalid pullfrog payload: ${error.summary}`);
|
|
||||||
}
|
|
||||||
// not JSON, treat as plain string prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate version compatibility from jsonPayload
|
|
||||||
if (jsonPayload) validateCompatibility(jsonPayload.version, packageJson.version);
|
|
||||||
|
|
||||||
// resolve event - use type guard for jsonPayload.event, fallback to unknown trigger
|
// resolve event - use type guard for jsonPayload.event, fallback to unknown trigger
|
||||||
const rawEvent = jsonPayload?.event;
|
const rawEvent = jsonPayload?.event;
|
||||||
const event: PayloadEvent = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
const event: PayloadEvent = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
||||||
@@ -141,9 +158,7 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
|||||||
"~pullfrog": true as const,
|
"~pullfrog": true as const,
|
||||||
version: jsonPayload?.version ?? packageJson.version,
|
version: jsonPayload?.version ?? packageJson.version,
|
||||||
agent: resolvedAgent,
|
agent: resolvedAgent,
|
||||||
// inverted: jsonPayload.prompt extracts the text from the JSON payload,
|
prompt,
|
||||||
// whereas inputs.prompt IS the raw JSON string when internally dispatched
|
|
||||||
prompt: jsonPayload?.prompt ?? inputs.prompt,
|
|
||||||
eventInstructions: jsonPayload?.eventInstructions,
|
eventInstructions: jsonPayload?.eventInstructions,
|
||||||
repoInstructions: jsonPayload?.repoInstructions,
|
repoInstructions: jsonPayload?.repoInstructions,
|
||||||
event,
|
event,
|
||||||
|
|||||||
+1
-12
@@ -1,16 +1,13 @@
|
|||||||
import { log } from "./cli.ts";
|
import { log } from "./cli.ts";
|
||||||
import type { OctokitWithPlugins } from "./github.ts";
|
import type { OctokitWithPlugins } from "./github.ts";
|
||||||
import { fetchWorkflowRunInfo, type WorkflowRunInfo } from "./workflowRun.ts";
|
|
||||||
|
|
||||||
interface ResolveRunParams {
|
interface ResolveRunParams {
|
||||||
octokit: OctokitWithPlugins;
|
octokit: OctokitWithPlugins;
|
||||||
apiToken: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolveRunResult {
|
export interface ResolveRunResult {
|
||||||
runId: string;
|
runId: string;
|
||||||
jobId: string | undefined;
|
jobId: string | undefined;
|
||||||
workflowRunInfo: WorkflowRunInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,14 +22,6 @@ export async function resolveRun(params: ResolveRunParams): Promise<ResolveRunRe
|
|||||||
}
|
}
|
||||||
const [owner, repo] = githubRepo.split("/");
|
const [owner, repo] = githubRepo.split("/");
|
||||||
|
|
||||||
const workflowRunInfo = runId
|
|
||||||
? await fetchWorkflowRunInfo({ runId, apiToken: params.apiToken })
|
|
||||||
: { progressCommentId: null };
|
|
||||||
|
|
||||||
if (workflowRunInfo.progressCommentId) {
|
|
||||||
log.info(`» using pre-created progress comment: ${workflowRunInfo.progressCommentId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let jobId: string | undefined;
|
let jobId: string | undefined;
|
||||||
const jobName = process.env.GITHUB_JOB;
|
const jobName = process.env.GITHUB_JOB;
|
||||||
if (jobName && runId) {
|
if (jobName && runId) {
|
||||||
@@ -48,5 +37,5 @@ export async function resolveRun(params: ResolveRunParams): Promise<ResolveRunRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { runId, jobId, workflowRunInfo };
|
return { runId, jobId };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,3 @@
|
|||||||
export interface WorkflowRunInfo {
|
export interface WorkflowRunInfo {
|
||||||
progressCommentId: string | null;
|
progressCommentId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FetchWorkflowRunInfoParams {
|
|
||||||
runId: string;
|
|
||||||
apiToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch workflow run info from the Pullfrog API.
|
|
||||||
* Returns the pre-created progress comment ID if one exists.
|
|
||||||
*/
|
|
||||||
export async function fetchWorkflowRunInfo(params: FetchWorkflowRunInfoParams): Promise<WorkflowRunInfo> {
|
|
||||||
const apiUrl = process.env.API_URL || "https://pullfrog.com";
|
|
||||||
const timeoutMs = 30000;
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiUrl}/api/workflow-run/${params.runId}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
// apiToken not strictly required (route only exposes public IDs)
|
|
||||||
// but claims in token enable preview API URL forwarding
|
|
||||||
Authorization: `Bearer ${params.apiToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { progressCommentId: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = (await response.json()) as WorkflowRunInfo;
|
|
||||||
return data;
|
|
||||||
} catch {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
return { progressCommentId: null };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user