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
+14
-6
@@ -49,16 +49,19 @@ describe("Inputs schema", () => {
|
||||
});
|
||||
|
||||
describe("JsonPayload schema", () => {
|
||||
it("requires ~pullfrog and version", () => {
|
||||
const result = JsonPayload.assert({ "~pullfrog": true, version: "1.2.3" });
|
||||
expect(result).toMatchObject({ "~pullfrog": true, version: "1.2.3" });
|
||||
it("requires ~pullfrog and version and prompt", () => {
|
||||
const result = JsonPayload.assert({
|
||||
"~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({ "~pullfrog": true })).toThrow();
|
||||
expect(() => JsonPayload.assert({ version: "1.2.3" })).toThrow();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["prompt", "test prompt"],
|
||||
["agent", "claude"],
|
||||
["agent", "codex"],
|
||||
["agent", "cursor"],
|
||||
@@ -72,12 +75,17 @@ describe("JsonPayload schema", () => {
|
||||
["timeout", "30s"],
|
||||
["event", { trigger: "unknown" }],
|
||||
] 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();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
+41
-26
@@ -17,12 +17,13 @@ export const JsonPayload = type({
|
||||
"~pullfrog": "true",
|
||||
version: "string",
|
||||
"agent?": AgentName.or("undefined"),
|
||||
"prompt?": "string",
|
||||
prompt: "string",
|
||||
"eventInstructions?": "string",
|
||||
"repoInstructions?": "string",
|
||||
"event?": "object",
|
||||
"effort?": Effort.or("undefined"),
|
||||
"timeout?": type.string.or("undefined"),
|
||||
"progressCommentId?": type.string.or("undefined"),
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
export function resolvePayload(repoSettings: RepoSettings) {
|
||||
const inputs = Inputs.assert({
|
||||
prompt: core.getInput("prompt", { required: true }),
|
||||
export type ResolvedPromptInput = string | typeof JsonPayload.infer;
|
||||
|
||||
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,
|
||||
timeout: core.getInput("timeout") || undefined,
|
||||
agent: core.getInput("agent") || undefined,
|
||||
@@ -80,30 +104,23 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
||||
write: core.getInput("write") || 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
|
||||
const agent: AgentName | 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
|
||||
const rawEvent = jsonPayload?.event;
|
||||
const event: PayloadEvent = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
||||
@@ -141,9 +158,7 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
||||
"~pullfrog": true as const,
|
||||
version: jsonPayload?.version ?? packageJson.version,
|
||||
agent: resolvedAgent,
|
||||
// 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,
|
||||
prompt,
|
||||
eventInstructions: jsonPayload?.eventInstructions,
|
||||
repoInstructions: jsonPayload?.repoInstructions,
|
||||
event,
|
||||
|
||||
+1
-12
@@ -1,16 +1,13 @@
|
||||
import { log } from "./cli.ts";
|
||||
import type { OctokitWithPlugins } from "./github.ts";
|
||||
import { fetchWorkflowRunInfo, type WorkflowRunInfo } from "./workflowRun.ts";
|
||||
|
||||
interface ResolveRunParams {
|
||||
octokit: OctokitWithPlugins;
|
||||
apiToken: string;
|
||||
}
|
||||
|
||||
export interface ResolveRunResult {
|
||||
runId: string;
|
||||
jobId: string | undefined;
|
||||
workflowRunInfo: WorkflowRunInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,14 +22,6 @@ export async function resolveRun(params: ResolveRunParams): Promise<ResolveRunRe
|
||||
}
|
||||
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;
|
||||
const jobName = process.env.GITHUB_JOB;
|
||||
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 {
|
||||
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