Clean up url resolution

This commit is contained in:
Colin McDonnell
2026-02-13 14:24:46 +00:00
committed by pullfrog[bot]
parent 267a4586ae
commit 78cf05f111
13 changed files with 211 additions and 146 deletions
+16
View File
@@ -1,8 +1,21 @@
import { performance } from "node:perf_hooks";
import * as core from "@actions/core";
export const DEFAULT_ACTIVITY_TIMEOUT_MS = 60_000;
export const DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5_000;
// inline debug check — can't import log.ts since activity.ts is a dependency of it
function debugLog(msg: string): void {
const isDebug =
process.env.LOG_LEVEL === "debug" ||
process.env.ACTIONS_STEP_DEBUG === "true" ||
process.env.RUNNER_DEBUG === "1" ||
core.isDebug();
if (isDebug) {
core.info(`[${new Date().toISOString()}] [DEBUG] ${msg}`);
}
}
type ActivityTimeoutContext = {
timeoutMs: number;
checkIntervalMs: number;
@@ -72,8 +85,11 @@ function startProcessOutputMonitor(ctx: OutputMonitorContext): OutputMonitor {
process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
debugLog(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
const intervalId = setInterval(() => {
const idleMs = getIdleMs();
debugLog(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
if (timedOut || idleMs <= ctx.timeoutMs) return;
timedOut = true;
ctx.onTimeout(idleMs);
+2 -1
View File
@@ -1,10 +1,11 @@
import type { Agent } from "../agents/index.ts";
import { getApiUrl } from "./apiUrl.ts";
/**
* Build a helpful error message for missing API key with links to repo settings
*/
function buildMissingApiKeyError(params: { agent: Agent; owner: string; name: string }): string {
const apiUrl = process.env.API_URL || "https://pullfrog.com";
const apiUrl = getApiUrl();
const settingsUrl = `${apiUrl}/console/${params.owner}/${params.name}`;
const githubRepoUrl = `https://github.com/${params.owner}/${params.name}`;
+9
View File
@@ -0,0 +1,9 @@
/**
* resolve the Pullfrog API base URL.
*
* in the action: API_URL is not explicitly set, so this falls back to https://pullfrog.com.
* in local dev: API_URL=http://localhost:3000 (from .env).
*/
export function getApiUrl(): string {
return process.env.API_URL || "https://pullfrog.com";
}
+2 -1
View File
@@ -2,6 +2,7 @@ import { createSign } from "node:crypto";
import * as core from "@actions/core";
import { throttling } from "@octokit/plugin-throttling";
import { Octokit } from "@octokit/rest";
import { getApiUrl } from "./apiUrl.ts";
import { retry } from "./retry.ts";
export interface InstallationToken {
@@ -84,7 +85,7 @@ type AcquireTokenOptions = {
async function acquireTokenViaOIDC(opts?: AcquireTokenOptions): Promise<string> {
const oidcToken = await core.getIDToken("pullfrog-api");
const apiUrl = process.env.API_URL || "https://pullfrog.com";
const apiUrl = getApiUrl();
const params = new URLSearchParams();
// ensure the token covers GITHUB_REPOSITORY (may differ from OIDC claims repo)
+11 -10
View File
@@ -206,31 +206,36 @@ function separator(length: number = 50): void {
/**
* Main logging utility object - import this once and access all utilities
*/
/** timestamp prefix for debug mode — empty string when debug is off */
function ts(): string {
return isDebugEnabled() ? `[${new Date().toISOString()}] ` : "";
}
export const log = {
/** Print info message */
info: (...args: unknown[]): void => {
core.info(formatArgs(args));
core.info(`${ts()}${formatArgs(args)}`);
},
/** Print warning message */
warning: (...args: unknown[]): void => {
core.warning(formatArgs(args));
core.warning(`${ts()}${formatArgs(args)}`);
},
/** Print error message */
error: (...args: unknown[]): void => {
core.error(formatArgs(args));
core.error(`${ts()}${formatArgs(args)}`);
},
/** Print success message */
success: (...args: unknown[]): void => {
core.info(`» ${formatArgs(args)}`);
core.info(`${ts()}» ${formatArgs(args)}`);
},
/** Print debug message (only if LOG_LEVEL=debug) */
debug: (...args: unknown[]): void => {
if (isDebugEnabled()) {
core.info(`[DEBUG] ${formatArgs(args)}`);
core.info(`[${new Date().toISOString()}] [DEBUG] ${formatArgs(args)}`);
}
},
@@ -255,11 +260,7 @@ export const log = {
/** Log tool call information to console with formatted output */
toolCall: ({ toolName, input }: { toolName: string; input: unknown }): void => {
const inputFormatted = formatJsonValue(input);
const timestamp = isDebugEnabled() ? ` [${new Date().toISOString()}]` : "";
const output =
inputFormatted !== "{}"
? `» ${toolName}(${inputFormatted})${timestamp}`
: `» ${toolName}()${timestamp}`;
const output = inputFormatted !== "{}" ? `» ${toolName}(${inputFormatted})` : `» ${toolName}()`;
log.info(output.trimEnd());
},
+2 -1
View File
@@ -1,4 +1,5 @@
import type { AgentName, BashPermission, PushPermission, ToolPermission } from "../external.ts";
import { getApiUrl } from "./apiUrl.ts";
import type { RepoContext } from "./github.ts";
export interface Mode {
@@ -51,7 +52,7 @@ export async function fetchRunContext(params: {
token: string;
repoContext: RepoContext;
}): Promise<RunContext> {
const apiUrl = process.env.API_URL || "https://pullfrog.com";
const apiUrl = getApiUrl();
const timeoutMs = 30000;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
+5 -1
View File
@@ -148,12 +148,16 @@ export async function spawn(options: SpawnOptions): Promise<SpawnResult> {
// activity timeout: kill if no output for too long
if (activityTimeoutMs > 0) {
log.debug(`spawn activity timer: pid=${child.pid} cmd=${cmd} timeout=${activityTimeoutMs}ms`);
activityCheckIntervalId = setInterval(() => {
const idleMs = performance.now() - lastActivityTime;
log.debug(
`spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
);
if (idleMs > activityTimeoutMs) {
isActivityTimedOut = true;
const idleSec = Math.round(idleMs / 1000);
log.error(`no output for ${idleSec}s, killing process`);
log.error(`no output for ${idleSec}s from pid=${child.pid} (${cmd}), killing process`);
child.kill("SIGKILL");
clearInterval(activityCheckIntervalId);
}