b748355cbe
* add wiki/betterstack.md documenting log querying, request-ID grouping, and MCP usage
Made-with: Cursor
* fix webhook race conditions: separate runId assignment from data updates
the workflow_run webhook handler had a race where concurrent handlers assigned
the same runId to different pending records. the loser's P2002 silently dropped
data updates (jobId, status, completedAt). fix by splitting into two steps:
assignRunId() handles the race-safe unique assignment, then data updates always
target where: { runId } so they hit the correct record regardless of who won.
also downgrade R2 ObjectLockedByBucketPolicy errors from error to warn level
since duplicate webhook deliveries writing the same key is expected under load.
Made-with: Cursor
* homepage copy refresh + fix skills CLI installation
- update hero to "Agent x GitHub" with new subtagline
- rewrite intro paragraphs: workflow, harness capabilities, billing
- add feature sections: bash isolation, headless browser, MCP tools
- update FAQ answers, footer attribution, free-for-oss copy
- update APP_DESCRIPTION for SEO
- fix skills install: use npx from tmpdir instead of local binary
(the bundled action has no node_modules; running npx from tmpdir
avoids project .npmrc with pnpm settings breaking binary resolution)
- instruct agents to use markdown image syntax in upload_file tool
- start dependency installation eagerly from main.ts
- include event title in task instructions
Made-with: Cursor
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
import { modelAliases } from "../models.ts";
|
||
|
||
export const PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
||
|
||
const FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
|
||
|
||
export interface WorkflowRunFooterInfo {
|
||
owner: string;
|
||
repo: string;
|
||
runId: number;
|
||
/** optional job ID - if provided, will append /job/{jobId} to the workflow run URL */
|
||
jobId?: string | undefined;
|
||
}
|
||
|
||
export interface BuildPullfrogFooterParams {
|
||
/** add "via Pullfrog" link */
|
||
triggeredBy?: boolean;
|
||
/** add "View workflow run" link */
|
||
workflowRun?: WorkflowRunFooterInfo | undefined;
|
||
/** alternative: just pass a pre-built URL directly (for shortlinks etc.) */
|
||
workflowRunUrl?: string | undefined;
|
||
/** arbitrary custom parts (e.g., action links) */
|
||
customParts?: string[] | undefined;
|
||
/** model slug from payload (e.g., "anthropic/claude-opus"). shown in footer as "Using `Model Name`" */
|
||
model?: string | undefined;
|
||
}
|
||
|
||
function formatModelLabel(slug: string): string {
|
||
const alias = modelAliases.find((a) => a.slug === slug);
|
||
if (!alias) return `\`${slug}\``;
|
||
return alias.isFree ? `\`${alias.displayName}\` (free)` : `\`${alias.displayName}\``;
|
||
}
|
||
|
||
/**
|
||
* build a pullfrog footer with configurable parts
|
||
* always includes: frog logo at start and X link at end
|
||
* order: action links (customParts) > workflow run > model > attribution > reference links
|
||
*/
|
||
export function buildPullfrogFooter(params: BuildPullfrogFooterParams): string {
|
||
const parts: string[] = [];
|
||
|
||
if (params.customParts) {
|
||
parts.push(...params.customParts);
|
||
}
|
||
|
||
if (params.workflowRunUrl) {
|
||
parts.push(`[View workflow run](${params.workflowRunUrl})`);
|
||
} else if (params.workflowRun) {
|
||
const baseUrl = `https://github.com/${params.workflowRun.owner}/${params.workflowRun.repo}/actions/runs/${params.workflowRun.runId}`;
|
||
const url = params.workflowRun.jobId ? `${baseUrl}/job/${params.workflowRun.jobId}` : baseUrl;
|
||
parts.push(`[View workflow run](${url})`);
|
||
}
|
||
|
||
if (params.triggeredBy) {
|
||
parts.push("via [Pullfrog](https://pullfrog.com)");
|
||
}
|
||
|
||
if (params.model) {
|
||
parts.push(`Using ${formatModelLabel(params.model)}`);
|
||
}
|
||
|
||
const allParts = [...parts, "[𝕏](https://x.com/pullfrogai)"];
|
||
|
||
return `\n\n${PULLFROG_DIVIDER}\n<sup>${FROG_LOGO} | ${allParts.join(" | ")}</sup>`;
|
||
}
|
||
|
||
/**
|
||
* strip any existing pullfrog footer from a comment body
|
||
*/
|
||
export function stripExistingFooter(body: string): string {
|
||
const dividerIndex = body.indexOf(PULLFROG_DIVIDER);
|
||
if (dividerIndex === -1) {
|
||
return body;
|
||
}
|
||
return body.substring(0, dividerIndex).trimEnd();
|
||
}
|