feat: adapt pullfrog for gitea + ollama

This commit is contained in:
2026-05-31 01:01:00 -05:00
parent 36ac64a5b6
commit 2aca1a3aa3
183 changed files with 1419 additions and 28292 deletions
+10 -39
View File
@@ -3,16 +3,13 @@ import "./arkConfig.ts";
import { createServer } from "node:net";
import { setTimeout as sleep } from "node:timers/promises";
import { FastMCP, type Tool } from "fastmcp";
import { type AgentId, pullfrogMcpName } from "../external.ts";
import { shockbotMcpName } from "../external.ts";
import type { Mode } from "../modes.ts";
import type { ToolState } from "../toolState.ts";
import { closeBrowserDaemon } from "../utils/browser.ts";
import type { OctokitWithPlugins } from "../utils/github.ts";
import type { Gitea } from "../utils/gitea.ts";
import type { ResolvedPayload } from "../utils/payload.ts";
import type { AccountPlan } from "../utils/runContext.ts";
import type { RunContextData } from "../utils/runContextData.ts";
import { CheckoutPrTool } from "./checkout.ts";
import { GetCheckSuiteLogsTool } from "./checkSuite.ts";
import {
CreateCommentTool,
EditCommentTool,
@@ -37,7 +34,6 @@ import { CreatePullRequestReviewTool } from "./review.ts";
import {
GetReviewCommentsTool,
ListPullRequestReviewsTool,
ResolveReviewThreadTool,
} from "./reviewComments.ts";
import { SelectModeTool } from "./selectMode.ts";
import { addTools } from "./shared.ts";
@@ -45,34 +41,21 @@ import { KillBackgroundTool, ShellTool } from "./shell.ts";
import { UploadFileTool } from "./upload.ts";
export interface ToolContext {
agentId: AgentId;
repo: RunContextData["repo"];
agentId: "ollama";
repo: { owner: string; name: string };
payload: ResolvedPayload;
octokit: OctokitWithPlugins;
githubInstallationToken: string;
gitea: Gitea;
gitToken: string;
apiToken: string;
modes: Mode[];
postCheckoutScript: string | null;
prepushScript: string | null;
prApproveEnabled: boolean;
modeInstructions: Record<string, string>;
toolState: ToolState;
runId: number | undefined;
jobId: string | undefined;
runId?: number | undefined;
jobId?: string | undefined;
mcpServerUrl: string;
tmpdir: string;
// repo-level OSS flag + account-level billing plan. together they decide
// whether pullfrog is paying for marginal infra — see `isInfraCovered` in
// the server's `utils/billing.ts`. plan gating for endpoints like the
// learnings PATCH is enforced server-side via 402, so we pass plan along
// mostly for future use / observability. see wiki/pricing.md.
oss: boolean;
plan: AccountPlan;
// resolved upstream model specifier (e.g. "google/gemini-3.1-pro-preview").
// undefined when payload.proxyModel is set or when the alias is unresolvable.
// used by the schema sanitizer to detect Gemini-routed traffic.
resolvedModel: string | undefined;
}
const mcpPortStart = 3764;
@@ -81,11 +64,11 @@ const mcpHost = "127.0.0.1";
const mcpEndpoint = "/mcp";
function readEnvPort(): number | null {
const rawPort = process.env.PULLFROG_MCP_PORT;
const rawPort = process.env.SHOCKBOT_MCP_PORT;
if (!rawPort) return null;
const parsed = Number.parseInt(rawPort, 10);
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
throw new Error(`invalid PULLFROG_MCP_PORT: ${rawPort}`);
throw new Error(`invalid SHOCKBOT_MCP_PORT: ${rawPort}`);
}
return parsed;
}
@@ -131,8 +114,6 @@ function buildCommonTools(ctx: ToolContext, outputSchema?: JsonSchema): Tool<any
CheckoutPrTool(ctx),
GetReviewCommentsTool(ctx),
ListPullRequestReviewsTool(ctx),
ResolveReviewThreadTool(ctx),
GetCheckSuiteLogsTool(ctx),
AddLabelsTool(ctx),
GitTool(ctx),
GitFetchTool(ctx),
@@ -144,7 +125,6 @@ function buildCommonTools(ctx: ToolContext, outputSchema?: JsonSchema): Tool<any
tools.push(SetOutputTool(ctx, outputSchema));
}
// MCP shell with filtered env (no secrets leaked to child processes)
if (ctx.payload.shell === "restricted") {
tools.push(ShellTool(ctx));
tools.push(KillBackgroundTool(ctx));
@@ -177,7 +157,7 @@ async function tryStartMcpServer(
tools: Tool<any, any>[],
port: number
): Promise<McpStartResult | null> {
const server = new FastMCP({ name: pullfrogMcpName, version: "0.0.1" });
const server = new FastMCP({ name: shockbotMcpName, version: "0.0.1" });
addTools(ctx, server, tools);
try {
@@ -217,7 +197,6 @@ async function selectMcpPort(ctx: ToolContext, tools: Tool<any, any>[]): Promise
}
}
// randomize start offset to reduce collision chance in parallel runs
const randomOffset = Math.floor(Math.random() * 50);
for (let offset = 0; offset < mcpPortAttempts; offset++) {
@@ -269,14 +248,6 @@ type McpHttpServerOptions = {
outputSchema?: JsonSchema | undefined;
};
/**
* Start the MCP HTTP server.
*
* The returned disposer is idempotent — safe to call multiple times.
* Callers (e.g. the inner activity-timeout handler in main.ts) may need to
* stop the server before the `await using` block exits; a subsequent
* automatic dispose is then a no-op.
*/
export async function startMcpHttpServer(
ctx: ToolContext,
options?: McpHttpServerOptions