Code style (#97)
* Cleanup * fix: populate deny array before assigning to config, add CursorCliConfig type * Fix deny array ordering and add CursorCliConfig type Move deny array population before config declaration to avoid relying on reference semantics. Add proper type interface for the CLI config object. --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
0ccaa68d3a
commit
2d2d31adfa
+14
-14
@@ -3,7 +3,7 @@ import type { Effort } from "../external.ts";
|
|||||||
import packageJson from "../package.json" with { type: "json" };
|
import packageJson from "../package.json" with { type: "json" };
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import { agent, createAgentEnv, installFromNpmTarball, type ToolPermissions } from "./shared.ts";
|
import { type AgentConfig, agent, createAgentEnv, installFromNpmTarball } from "./shared.ts";
|
||||||
|
|
||||||
// Model selection based on effort level
|
// Model selection based on effort level
|
||||||
// Note: mini uses Haiku for speed, auto uses opusplan for balance, max uses Opus for capability
|
// Note: mini uses Haiku for speed, auto uses opusplan for balance, max uses Opus for capability
|
||||||
@@ -22,14 +22,14 @@ const claudeEffortModels: Record<Effort, string> = {
|
|||||||
/**
|
/**
|
||||||
* Build disallowedTools list from ToolPermissions.
|
* Build disallowedTools list from ToolPermissions.
|
||||||
*/
|
*/
|
||||||
function buildDisallowedTools(tools: ToolPermissions): string[] {
|
function buildDisallowedTools(ctx: AgentConfig): string[] {
|
||||||
const disallowed: string[] = [];
|
const disallowed: string[] = [];
|
||||||
if (tools.web === "disabled") disallowed.push("WebFetch");
|
if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
|
||||||
if (tools.search === "disabled") disallowed.push("WebSearch");
|
if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
|
||||||
if (tools.write === "disabled") disallowed.push("Write");
|
if (ctx.tools.write === "disabled") disallowed.push("Write");
|
||||||
// both "disabled" and "restricted" block native bash
|
// both "disabled" and "restricted" block native bash
|
||||||
// "restricted" means use MCP bash tool instead
|
// "restricted" means use MCP bash tool instead
|
||||||
if (tools.bash !== "enabled") disallowed.push("Bash");
|
if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
|
||||||
return disallowed;
|
return disallowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,19 +43,19 @@ export const claude = agent({
|
|||||||
executablePath: "cli.js",
|
executablePath: "cli.js",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
// Ensure API key is NOT in process.env - only pass via SDK's env option
|
// Ensure API key is NOT in process.env - only pass via SDK's env option
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
|
|
||||||
// select model based on effort level
|
// select model based on effort level
|
||||||
const model = claudeEffortModels[effort];
|
const model = claudeEffortModels[ctx.effort];
|
||||||
log.info(`Using model: ${model} (effort: ${effort})`);
|
log.info(`Using model: ${model} (effort: ${ctx.effort})`);
|
||||||
|
|
||||||
// build disallowedTools based on tool permissions
|
// build disallowedTools based on tool permissions
|
||||||
const disallowedTools = buildDisallowedTools(tools);
|
const disallowedTools = buildDisallowedTools(ctx);
|
||||||
if (disallowedTools.length > 0) {
|
if (disallowedTools.length > 0) {
|
||||||
log.info(`🔒 disallowed tools: ${disallowedTools.join(", ")}`);
|
log.info(`🔒 disallowed tools: ${disallowedTools.join(", ")}`);
|
||||||
}
|
}
|
||||||
@@ -65,10 +65,10 @@ export const claude = agent({
|
|||||||
const queryOptions: Options = {
|
const queryOptions: Options = {
|
||||||
permissionMode: "bypassPermissions" as const,
|
permissionMode: "bypassPermissions" as const,
|
||||||
disallowedTools,
|
disallowedTools,
|
||||||
mcpServers,
|
mcpServers: ctx.mcpServers,
|
||||||
model,
|
model,
|
||||||
pathToClaudeCodeExecutable: cliPath,
|
pathToClaudeCodeExecutable: ctx.cliPath,
|
||||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }),
|
env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryInstance = query({
|
const queryInstance = query({
|
||||||
|
|||||||
+17
-27
@@ -1,6 +1,5 @@
|
|||||||
import { mkdirSync, writeFileSync } from "node:fs";
|
import { mkdirSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import type { McpHttpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
|
||||||
import {
|
import {
|
||||||
Codex,
|
Codex,
|
||||||
type CodexOptions,
|
type CodexOptions,
|
||||||
@@ -13,9 +12,9 @@ import { log } from "../utils/cli.ts";
|
|||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import {
|
import {
|
||||||
agent,
|
agent,
|
||||||
|
type AgentConfig,
|
||||||
installFromNpmTarball,
|
installFromNpmTarball,
|
||||||
setupProcessAgentEnv,
|
setupProcessAgentEnv,
|
||||||
type ToolPermissions,
|
|
||||||
} from "./shared.ts";
|
} from "./shared.ts";
|
||||||
|
|
||||||
// model configuration based on effort level
|
// model configuration based on effort level
|
||||||
@@ -35,20 +34,15 @@ const codexReasoningEffort: Record<Effort, ModelReasoningEffort | undefined> = {
|
|||||||
max: "high",
|
max: "high",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface WriteCodexConfigParams {
|
function writeCodexConfig(ctx: AgentConfig): string {
|
||||||
tempHome: string;
|
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||||
mcpServers: Record<string, McpHttpServerConfig>;
|
|
||||||
tools: ToolPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeCodexConfig({ tempHome, mcpServers, tools }: WriteCodexConfigParams): string {
|
|
||||||
const codexDir = join(tempHome, ".codex");
|
const codexDir = join(tempHome, ".codex");
|
||||||
mkdirSync(codexDir, { recursive: true });
|
mkdirSync(codexDir, { recursive: true });
|
||||||
const configPath = join(codexDir, "config.toml");
|
const configPath = join(codexDir, "config.toml");
|
||||||
|
|
||||||
// build MCP servers section
|
// build MCP servers section
|
||||||
const mcpServerSections: string[] = [];
|
const mcpServerSections: string[] = [];
|
||||||
for (const [name, config] of Object.entries(mcpServers)) {
|
for (const [name, config] of Object.entries(ctx.mcpServers)) {
|
||||||
if (config.type !== "http") continue;
|
if (config.type !== "http") continue;
|
||||||
log.info(`» adding MCP server '${name}' at ${config.url}`);
|
log.info(`» adding MCP server '${name}' at ${config.url}`);
|
||||||
mcpServerSections.push(`[mcp_servers.${name}]\nurl = "${config.url}"`);
|
mcpServerSections.push(`[mcp_servers.${name}]\nurl = "${config.url}"`);
|
||||||
@@ -58,7 +52,7 @@ function writeCodexConfig({ tempHome, mcpServers, tools }: WriteCodexConfigParam
|
|||||||
// disable native shell if bash is "disabled" or "restricted"
|
// disable native shell if bash is "disabled" or "restricted"
|
||||||
// when "restricted", agent uses MCP bash tool which filters secrets
|
// when "restricted", agent uses MCP bash tool which filters secrets
|
||||||
const features: string[] = [];
|
const features: string[] = [];
|
||||||
if (tools.bash !== "enabled") {
|
if (ctx.tools.bash !== "enabled") {
|
||||||
features.push("shell_command_tool = false");
|
features.push("shell_command_tool = false");
|
||||||
features.push("unified_exec = false");
|
features.push("unified_exec = false");
|
||||||
}
|
}
|
||||||
@@ -74,7 +68,7 @@ ${mcpServerSections.join("\n\n")}
|
|||||||
);
|
);
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`» Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})`
|
`» Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
|
||||||
);
|
);
|
||||||
|
|
||||||
return codexDir;
|
return codexDir;
|
||||||
@@ -89,28 +83,24 @@ export const codex = agent({
|
|||||||
executablePath: "bin/codex.js",
|
executablePath: "bin/codex.js",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||||
|
|
||||||
// create config directory for codex before setting HOME
|
// create config directory for codex before setting HOME
|
||||||
const configDir = join(tempHome, ".config", "codex");
|
const configDir = join(tempHome, ".config", "codex");
|
||||||
mkdirSync(configDir, { recursive: true });
|
mkdirSync(configDir, { recursive: true });
|
||||||
|
|
||||||
const codexDir = writeCodexConfig({
|
const codexDir = writeCodexConfig(ctx);
|
||||||
tempHome,
|
|
||||||
mcpServers,
|
|
||||||
tools,
|
|
||||||
});
|
|
||||||
|
|
||||||
setupProcessAgentEnv({
|
setupProcessAgentEnv({
|
||||||
OPENAI_API_KEY: apiKey,
|
OPENAI_API_KEY: ctx.apiKey,
|
||||||
HOME: tempHome,
|
HOME: tempHome,
|
||||||
CODEX_HOME: codexDir, // point Codex to our config directory
|
CODEX_HOME: codexDir, // point Codex to our config directory
|
||||||
});
|
});
|
||||||
|
|
||||||
// get model and reasoning effort based on effort level
|
// get model and reasoning effort based on effort level
|
||||||
const model = codexModel[effort];
|
const model = codexModel[ctx.effort];
|
||||||
const modelReasoningEffort = codexReasoningEffort[effort];
|
const modelReasoningEffort = codexReasoningEffort[ctx.effort];
|
||||||
log.info(`Using model: ${model}`);
|
log.info(`Using model: ${model}`);
|
||||||
if (modelReasoningEffort) {
|
if (modelReasoningEffort) {
|
||||||
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
||||||
@@ -118,8 +108,8 @@ export const codex = agent({
|
|||||||
|
|
||||||
// Configure Codex
|
// Configure Codex
|
||||||
const codexOptions: CodexOptions = {
|
const codexOptions: CodexOptions = {
|
||||||
apiKey,
|
apiKey: ctx.apiKey,
|
||||||
codexPathOverride: cliPath,
|
codexPathOverride: ctx.cliPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
const codex = new Codex(codexOptions);
|
const codex = new Codex(codexOptions);
|
||||||
@@ -129,11 +119,11 @@ export const codex = agent({
|
|||||||
model,
|
model,
|
||||||
approvalPolicy: "never" as const,
|
approvalPolicy: "never" as const,
|
||||||
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
||||||
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access",
|
sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
|
||||||
// web: controls network access
|
// web: controls network access
|
||||||
networkAccessEnabled: tools.web !== "disabled",
|
networkAccessEnabled: ctx.tools.web !== "disabled",
|
||||||
// search: controls web search
|
// search: controls web search
|
||||||
webSearchEnabled: tools.search !== "disabled",
|
webSearchEnabled: ctx.tools.search !== "disabled",
|
||||||
...(modelReasoningEffort && { modelReasoningEffort }),
|
...(modelReasoningEffort && { modelReasoningEffort }),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,7 +134,7 @@ export const codex = agent({
|
|||||||
const thread = codex.startThread(threadOptions);
|
const thread = codex.startThread(threadOptions);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools }));
|
const streamedTurn = await thread.runStreamed(addInstructions(ctx));
|
||||||
|
|
||||||
let finalOutput = "";
|
let finalOutput = "";
|
||||||
for await (const event of streamedTurn.events) {
|
for await (const event of streamedTurn.events) {
|
||||||
|
|||||||
+33
-37
@@ -5,13 +5,7 @@ import { join } from "node:path";
|
|||||||
import type { Effort } from "../external.ts";
|
import type { Effort } from "../external.ts";
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import {
|
import { type AgentConfig, agent, createAgentEnv, installFromCurl } from "./shared.ts";
|
||||||
agent,
|
|
||||||
type ConfigureMcpServersParams,
|
|
||||||
createAgentEnv,
|
|
||||||
installFromCurl,
|
|
||||||
type ToolPermissions,
|
|
||||||
} from "./shared.ts";
|
|
||||||
|
|
||||||
// effort configuration for Cursor
|
// effort configuration for Cursor
|
||||||
// only "max" overrides the model; mini/auto use default ("auto")
|
// only "max" overrides the model; mini/auto use default ("auto")
|
||||||
@@ -101,9 +95,9 @@ export const cursor = agent({
|
|||||||
executableName: "cursor-agent",
|
executableName: "cursor-agent",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
configureCursorMcpServers({ mcpServers, cliPath });
|
configureCursorMcpServers(ctx);
|
||||||
configureCursorTools({ tools });
|
configureCursorTools(ctx);
|
||||||
|
|
||||||
// determine model based on effort level
|
// determine model based on effort level
|
||||||
// respect project's .cursor/cli.json if it specifies a model
|
// respect project's .cursor/cli.json if it specifies a model
|
||||||
@@ -116,19 +110,19 @@ export const cursor = agent({
|
|||||||
if (projectConfig.model) {
|
if (projectConfig.model) {
|
||||||
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
log.info(`Using model: ${modelOverride} (effort: ${effort})`);
|
log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
|
||||||
} else if (!existsSync(projectCliConfigPath)) {
|
} else if (!existsSync(projectCliConfigPath)) {
|
||||||
log.info(`Using default model (effort: ${effort})`);
|
log.info(`Using default model (effort: ${ctx.effort})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// track logged model_call_ids to avoid duplicates
|
// track logged model_call_ids to avoid duplicates
|
||||||
@@ -202,7 +196,7 @@ export const cursor = agent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fullPrompt = addInstructions({ payload, repo, tools });
|
const fullPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(fullPrompt));
|
log.group("Full prompt", () => log.info(fullPrompt));
|
||||||
|
|
||||||
// build CLI args
|
// build CLI args
|
||||||
@@ -221,10 +215,10 @@ export const cursor = agent({
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const child = spawn(cliPath, cursorArgs, {
|
const child = spawn(ctx.cliPath, cursorArgs, {
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
env: createAgentEnv({
|
env: createAgentEnv({
|
||||||
CURSOR_API_KEY: apiKey,
|
CURSOR_API_KEY: ctx.apiKey,
|
||||||
}),
|
}),
|
||||||
stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr
|
stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr
|
||||||
});
|
});
|
||||||
@@ -317,7 +311,7 @@ export const cursor = agent({
|
|||||||
// There was an issue on macOS when you set HOME to a temp directory
|
// There was an issue on macOS when you set HOME to a temp directory
|
||||||
// it was unable to find the macOS keychain and would fail
|
// it was unable to find the macOS keychain and would fail
|
||||||
// temp solution is to stick with the actual $HOME
|
// temp solution is to stick with the actual $HOME
|
||||||
function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) {
|
function configureCursorMcpServers(ctx: AgentConfig): void {
|
||||||
const realHome = homedir();
|
const realHome = homedir();
|
||||||
const cursorConfigDir = join(realHome, ".cursor");
|
const cursorConfigDir = join(realHome, ".cursor");
|
||||||
const mcpConfigPath = join(cursorConfigDir, "mcp.json");
|
const mcpConfigPath = join(cursorConfigDir, "mcp.json");
|
||||||
@@ -325,7 +319,7 @@ function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) {
|
|||||||
|
|
||||||
// Convert to Cursor's expected format (HTTP config)
|
// Convert to Cursor's expected format (HTTP config)
|
||||||
const cursorMcpServers: Record<string, { type: string; url: string }> = {};
|
const cursorMcpServers: Record<string, { type: string; url: string }> = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Cursor: ${(serverConfig as any).type || "unknown"}`
|
`Unsupported MCP server type for Cursor: ${(serverConfig as any).type || "unknown"}`
|
||||||
@@ -342,8 +336,15 @@ function configureCursorMcpServers({ mcpServers }: ConfigureMcpServersParams) {
|
|||||||
log.info(`» MCP config written to ${mcpConfigPath}`);
|
log.info(`» MCP config written to ${mcpConfigPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigureCursorToolsParams {
|
interface CursorCliConfig {
|
||||||
tools: ToolPermissions;
|
permissions: {
|
||||||
|
allow: string[];
|
||||||
|
deny: string[];
|
||||||
|
};
|
||||||
|
sandbox?: {
|
||||||
|
mode: "enabled" | "disabled";
|
||||||
|
networkAccess?: "allowlist" | "full";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -352,7 +353,7 @@ interface ConfigureCursorToolsParams {
|
|||||||
* Config path: $XDG_CONFIG_HOME/cursor/ (not ~/.cursor/) because createAgentEnv
|
* Config path: $XDG_CONFIG_HOME/cursor/ (not ~/.cursor/) because createAgentEnv
|
||||||
* sets XDG_CONFIG_HOME=$HOME/.config.
|
* sets XDG_CONFIG_HOME=$HOME/.config.
|
||||||
*/
|
*/
|
||||||
function configureCursorTools({ tools }: ConfigureCursorToolsParams): void {
|
function configureCursorTools(ctx: AgentConfig): void {
|
||||||
const realHome = homedir();
|
const realHome = homedir();
|
||||||
const cursorConfigDir = join(realHome, ".config", "cursor");
|
const cursorConfigDir = join(realHome, ".config", "cursor");
|
||||||
const cliConfigPath = join(cursorConfigDir, "cli-config.json");
|
const cliConfigPath = join(cursorConfigDir, "cli-config.json");
|
||||||
@@ -360,23 +361,21 @@ function configureCursorTools({ tools }: ConfigureCursorToolsParams): void {
|
|||||||
|
|
||||||
// build deny list based on tool permissions
|
// build deny list based on tool permissions
|
||||||
const deny: string[] = [];
|
const deny: string[] = [];
|
||||||
if (tools.search === "disabled") deny.push("WebSearch");
|
if (ctx.tools.search === "disabled") deny.push("WebSearch");
|
||||||
if (tools.write === "disabled") deny.push("Write(**)");
|
if (ctx.tools.write === "disabled") deny.push("Write(**)");
|
||||||
// both "disabled" and "restricted" block native shell
|
// both "disabled" and "restricted" block native shell
|
||||||
if (tools.bash !== "enabled") deny.push("Shell(*)");
|
if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
|
||||||
|
|
||||||
// web: "disabled" requires sandbox with network blocking
|
const config: CursorCliConfig = {
|
||||||
// sandbox.networkAccess: "allowlist" blocks network in shell subprocesses via seatbelt
|
|
||||||
const needsSandbox = tools.web === "disabled";
|
|
||||||
|
|
||||||
const config: Record<string, unknown> = {
|
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
||||||
deny,
|
deny,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (needsSandbox) {
|
// web: "disabled" requires sandbox with network blocking
|
||||||
|
// sandbox.networkAccess: "allowlist" blocks network in shell subprocesses via seatbelt
|
||||||
|
if (ctx.tools.web === "disabled") {
|
||||||
config.sandbox = {
|
config.sandbox = {
|
||||||
mode: "enabled",
|
mode: "enabled",
|
||||||
networkAccess: "allowlist",
|
networkAccess: "allowlist",
|
||||||
@@ -384,8 +383,5 @@ function configureCursorTools({ tools }: ConfigureCursorToolsParams): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
||||||
log.info(`» CLI config written to ${cliConfigPath}`);
|
log.info(`» CLI config written to ${cliConfigPath}`, JSON.stringify(config, null, 2));
|
||||||
log.info(
|
|
||||||
`🔧 Cursor permissions: allow=${(config.permissions as { allow: string[] }).allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-36
@@ -5,13 +5,7 @@ import type { Effort } from "../external.ts";
|
|||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
import { spawn } from "../utils/subprocess.ts";
|
import { spawn } from "../utils/subprocess.ts";
|
||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import {
|
import { type AgentConfig, agent, createAgentEnv, installFromGithub } from "./shared.ts";
|
||||||
agent,
|
|
||||||
type ConfigureMcpServersParams,
|
|
||||||
createAgentEnv,
|
|
||||||
installFromGithub,
|
|
||||||
type ToolPermissions,
|
|
||||||
} from "./shared.ts";
|
|
||||||
|
|
||||||
// effort configuration: model + thinking level
|
// effort configuration: model + thinking level
|
||||||
// thinkingLevel is set via settings.json modelConfig.generateContentConfig.thinkingConfig
|
// thinkingLevel is set via settings.json modelConfig.generateContentConfig.thinkingConfig
|
||||||
@@ -172,18 +166,14 @@ export const gemini = agent({
|
|||||||
...(githubInstallationToken && { githubInstallationToken }),
|
...(githubInstallationToken && { githubInstallationToken }),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
// get model and thinking level based on effort
|
const model = configureGeminiSettings(ctx);
|
||||||
const { model, thinkingLevel } = geminiEffortConfig[effort];
|
|
||||||
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
|
||||||
|
|
||||||
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
|
if (!ctx.apiKey) {
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionPrompt = addInstructions({ payload, repo, tools });
|
const sessionPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(sessionPrompt));
|
log.group("Full prompt", () => log.info(sessionPrompt));
|
||||||
|
|
||||||
// build CLI args - --yolo for auto-approval
|
// build CLI args - --yolo for auto-approval
|
||||||
@@ -196,8 +186,8 @@ export const gemini = agent({
|
|||||||
try {
|
try {
|
||||||
const result = await spawn({
|
const result = await spawn({
|
||||||
cmd: "node",
|
cmd: "node",
|
||||||
args: [cliPath, ...args],
|
args: [ctx.cliPath, ...args],
|
||||||
env: createAgentEnv({ GEMINI_API_KEY: apiKey }),
|
env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
|
||||||
onStdout: async (chunk) => {
|
onStdout: async (chunk) => {
|
||||||
const text = chunk.toString();
|
const text = chunk.toString();
|
||||||
finalOutput += text;
|
finalOutput += text;
|
||||||
@@ -270,25 +260,16 @@ export const gemini = agent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ConfigureGeminiParams {
|
|
||||||
mcpServers: ConfigureMcpServersParams["mcpServers"];
|
|
||||||
tools: ToolPermissions;
|
|
||||||
thinkingLevel: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Gemini CLI settings by writing to settings.json.
|
* Configure Gemini CLI settings by writing to settings.json.
|
||||||
* - MCP servers: uses `httpUrl` for HTTP/streamable transport
|
* Returns the model to use for CLI args.
|
||||||
* - thinkingLevel: configured via modelConfig.generateContentConfig.thinkingConfig
|
|
||||||
* - tools.exclude: disables native tools based on ToolPermissions (v0.3.0+ format)
|
|
||||||
*
|
*
|
||||||
* See: https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md
|
* See: https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md
|
||||||
*/
|
*/
|
||||||
function configureGeminiSettings({
|
function configureGeminiSettings(ctx: AgentConfig): string {
|
||||||
mcpServers,
|
const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
|
||||||
tools,
|
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
||||||
thinkingLevel,
|
|
||||||
}: ConfigureGeminiParams): void {
|
|
||||||
const realHome = homedir();
|
const realHome = homedir();
|
||||||
const geminiConfigDir = join(realHome, ".gemini");
|
const geminiConfigDir = join(realHome, ".gemini");
|
||||||
const settingsPath = join(geminiConfigDir, "settings.json");
|
const settingsPath = join(geminiConfigDir, "settings.json");
|
||||||
@@ -319,7 +300,7 @@ function configureGeminiSettings({
|
|||||||
excludeTools?: string[];
|
excludeTools?: string[];
|
||||||
}
|
}
|
||||||
const geminiMcpServers: Record<string, GeminiMcpServerConfig> = {};
|
const geminiMcpServers: Record<string, GeminiMcpServerConfig> = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Gemini: ${(serverConfig as { type?: string }).type || "unknown"}`
|
`Unsupported MCP server type for Gemini: ${(serverConfig as { type?: string }).type || "unknown"}`
|
||||||
@@ -334,10 +315,10 @@ function configureGeminiSettings({
|
|||||||
|
|
||||||
// build tools.exclude based on permissions (v0.3.0+ nested format)
|
// build tools.exclude based on permissions (v0.3.0+ nested format)
|
||||||
const exclude: string[] = [];
|
const exclude: string[] = [];
|
||||||
if (tools.bash !== "enabled") exclude.push("run_shell_command");
|
if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
|
||||||
if (tools.write === "disabled") exclude.push("write_file");
|
if (ctx.tools.write === "disabled") exclude.push("write_file");
|
||||||
if (tools.web === "disabled") exclude.push("web_fetch");
|
if (ctx.tools.web === "disabled") exclude.push("web_fetch");
|
||||||
if (tools.search === "disabled") exclude.push("google_web_search");
|
if (ctx.tools.search === "disabled") exclude.push("google_web_search");
|
||||||
|
|
||||||
// merge with existing settings, overwriting mcpServers and modelConfig
|
// merge with existing settings, overwriting mcpServers and modelConfig
|
||||||
const newSettings: Record<string, unknown> = {
|
const newSettings: Record<string, unknown> = {
|
||||||
@@ -361,4 +342,6 @@ function configureGeminiSettings({
|
|||||||
if (exclude.length > 0) {
|
if (exclude.length > 0) {
|
||||||
log.info(`🔒 excluded tools: ${exclude.join(", ")}`);
|
log.info(`🔒 excluded tools: ${exclude.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-17
@@ -3,14 +3,7 @@ import { encode as toonEncode } from "@toon-format/toon";
|
|||||||
import type { Payload } from "../external.ts";
|
import type { Payload } from "../external.ts";
|
||||||
import { ghPullfrogMcpName } from "../external.ts";
|
import { ghPullfrogMcpName } from "../external.ts";
|
||||||
import { getModes } from "../modes.ts";
|
import { getModes } from "../modes.ts";
|
||||||
import type { ToolPermissions } from "./shared.ts";
|
import type { RepoInfo, ToolPermissions } from "./shared.ts";
|
||||||
|
|
||||||
interface RepoInfo {
|
|
||||||
owner: string;
|
|
||||||
name: string;
|
|
||||||
defaultBranch: string;
|
|
||||||
isPublic: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build runtime context string with git status, repo data, and GitHub Actions variables
|
* Build runtime context string with git status, repo data, and GitHub Actions variables
|
||||||
@@ -52,7 +45,7 @@ function buildRuntimeContext(repo: RepoInfo): string {
|
|||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddInstructionsParams {
|
interface AddInstructionsCtx {
|
||||||
payload: Payload;
|
payload: Payload;
|
||||||
repo: RepoInfo;
|
repo: RepoInfo;
|
||||||
tools: ToolPermissions;
|
tools: ToolPermissions;
|
||||||
@@ -76,19 +69,19 @@ function getShellInstructions(bash: ToolPermissions["bash"]): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addInstructions = ({ payload, repo, tools }: AddInstructionsParams) => {
|
export const addInstructions = (ctx: AddInstructionsCtx) => {
|
||||||
let encodedEvent = "";
|
let encodedEvent = "";
|
||||||
|
|
||||||
const eventKeys = Object.keys(payload.event);
|
const eventKeys = Object.keys(ctx.payload.event);
|
||||||
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
||||||
// no meaningful event data to encode
|
// no meaningful event data to encode
|
||||||
} else {
|
} else {
|
||||||
// extract only essential fields to reduce token usage
|
// extract only essential fields to reduce token usage
|
||||||
// const essentialEvent = payload.event;
|
// const essentialEvent = ctx.payload.event;
|
||||||
encodedEvent = toonEncode(payload.event);
|
encodedEvent = toonEncode(ctx.payload.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const runtimeContext = buildRuntimeContext(repo);
|
const runtimeContext = buildRuntimeContext(ctx.repo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`
|
`
|
||||||
@@ -159,7 +152,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
|
|||||||
|
|
||||||
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
||||||
|
|
||||||
${getShellInstructions(tools.bash)}
|
${getShellInstructions(ctx.tools.bash)}
|
||||||
|
|
||||||
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
||||||
|
|
||||||
@@ -180,7 +173,7 @@ ${getShellInstructions(tools.bash)}
|
|||||||
|
|
||||||
### Available modes
|
### Available modes
|
||||||
|
|
||||||
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||||
|
|
||||||
### Following the mode instructions
|
### Following the mode instructions
|
||||||
|
|
||||||
@@ -190,7 +183,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
|
|||||||
|
|
||||||
************* USER PROMPT *************
|
************* USER PROMPT *************
|
||||||
|
|
||||||
${payload.prompt
|
${ctx.payload.prompt
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line) => `> ${line}`)
|
.map((line) => `> ${line}`)
|
||||||
.join("\n")}
|
.join("\n")}
|
||||||
|
|||||||
+12
-27
@@ -5,11 +5,10 @@ import { spawn } from "../utils/subprocess.ts";
|
|||||||
import { addInstructions } from "./instructions.ts";
|
import { addInstructions } from "./instructions.ts";
|
||||||
import {
|
import {
|
||||||
agent,
|
agent,
|
||||||
type ConfigureMcpServersParams,
|
type AgentConfig,
|
||||||
createAgentEnv,
|
createAgentEnv,
|
||||||
installFromNpmTarball,
|
installFromNpmTarball,
|
||||||
setupProcessAgentEnv,
|
setupProcessAgentEnv,
|
||||||
type ToolPermissions,
|
|
||||||
} from "./shared.ts";
|
} from "./shared.ts";
|
||||||
|
|
||||||
export const opencode = agent({
|
export const opencode = agent({
|
||||||
@@ -22,24 +21,15 @@ export const opencode = agent({
|
|||||||
installDependencies: true,
|
installDependencies: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({
|
run: async (ctx) => {
|
||||||
payload,
|
|
||||||
apiKey: _apiKey,
|
|
||||||
apiKeys,
|
|
||||||
mcpServers,
|
|
||||||
cliPath,
|
|
||||||
repo,
|
|
||||||
effort: _effort,
|
|
||||||
tools,
|
|
||||||
}) => {
|
|
||||||
// 1. configure home/config directory
|
// 1. configure home/config directory
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||||
const configDir = join(tempHome, ".config", "opencode");
|
const configDir = join(tempHome, ".config", "opencode");
|
||||||
mkdirSync(configDir, { recursive: true });
|
mkdirSync(configDir, { recursive: true });
|
||||||
|
|
||||||
configureOpenCode({ mcpServers, tools });
|
configureOpenCode(ctx);
|
||||||
|
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
|
|
||||||
// message positional must come right after "run", before flags
|
// message positional must come right after "run", before flags
|
||||||
@@ -60,7 +50,7 @@ export const opencode = agent({
|
|||||||
delete env.GITHUB_TOKEN;
|
delete env.GITHUB_TOKEN;
|
||||||
|
|
||||||
// add API keys from apiKeys object
|
// add API keys from apiKeys object
|
||||||
for (const [key, value] of Object.entries(apiKeys || {})) {
|
for (const [key, value] of Object.entries(ctx.apiKeys || {})) {
|
||||||
env[key.toUpperCase()] = value;
|
env[key.toUpperCase()] = value;
|
||||||
// also set GOOGLE_GENERATIVE_AI_API_KEY for Google provider compatibility
|
// also set GOOGLE_GENERATIVE_AI_API_KEY for Google provider compatibility
|
||||||
if (key === "GEMINI_API_KEY") {
|
if (key === "GEMINI_API_KEY") {
|
||||||
@@ -71,7 +61,7 @@ export const opencode = agent({
|
|||||||
// run OpenCode in the repository directory (process.cwd() is set to GITHUB_WORKSPACE or repo dir)
|
// run OpenCode in the repository directory (process.cwd() is set to GITHUB_WORKSPACE or repo dir)
|
||||||
const repoDir = process.cwd();
|
const repoDir = process.cwd();
|
||||||
|
|
||||||
log.info(`🚀 Starting OpenCode CLI: ${cliPath} ${args.join(" ")}`);
|
log.info(`🚀 Starting OpenCode CLI: ${ctx.cliPath} ${args.join(" ")}`);
|
||||||
log.info(`📁 Working directory: ${repoDir}`);
|
log.info(`📁 Working directory: ${repoDir}`);
|
||||||
log.debug(`🏠 HOME: ${env.HOME}`);
|
log.debug(`🏠 HOME: ${env.HOME}`);
|
||||||
log.debug(`📋 XDG_CONFIG_HOME: ${env.XDG_CONFIG_HOME}`);
|
log.debug(`📋 XDG_CONFIG_HOME: ${env.XDG_CONFIG_HOME}`);
|
||||||
@@ -83,7 +73,7 @@ export const opencode = agent({
|
|||||||
let output = "";
|
let output = "";
|
||||||
let stdoutBuffer = ""; // buffer for incomplete lines across chunks
|
let stdoutBuffer = ""; // buffer for incomplete lines across chunks
|
||||||
const result = await spawn({
|
const result = await spawn({
|
||||||
cmd: cliPath,
|
cmd: ctx.cliPath,
|
||||||
args,
|
args,
|
||||||
cwd: repoDir,
|
cwd: repoDir,
|
||||||
env,
|
env,
|
||||||
@@ -191,16 +181,11 @@ export const opencode = agent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ConfigureOpenCodeParams {
|
|
||||||
mcpServers: ConfigureMcpServersParams["mcpServers"];
|
|
||||||
tools: ToolPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure OpenCode via opencode.json config file.
|
* Configure OpenCode via opencode.json config file.
|
||||||
* Builds complete config with MCP servers and permissions in a single write to avoid race conditions.
|
* Builds complete config with MCP servers and permissions in a single write to avoid race conditions.
|
||||||
*/
|
*/
|
||||||
function configureOpenCode({ mcpServers, tools }: ConfigureOpenCodeParams): void {
|
function configureOpenCode(ctx: AgentConfig): void {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||||
const configDir = join(tempHome, ".config", "opencode");
|
const configDir = join(tempHome, ".config", "opencode");
|
||||||
mkdirSync(configDir, { recursive: true });
|
mkdirSync(configDir, { recursive: true });
|
||||||
@@ -208,7 +193,7 @@ function configureOpenCode({ mcpServers, tools }: ConfigureOpenCodeParams): void
|
|||||||
|
|
||||||
// build MCP servers config
|
// build MCP servers config
|
||||||
const opencodeMcpServers: Record<string, { type: "remote"; url: string }> = {};
|
const opencodeMcpServers: Record<string, { type: "remote"; url: string }> = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
log.error(
|
log.error(
|
||||||
`unsupported MCP server type for OpenCode: ${(serverConfig as never as { type: string }).type || "unknown"}`
|
`unsupported MCP server type for OpenCode: ${(serverConfig as never as { type: string }).type || "unknown"}`
|
||||||
@@ -227,9 +212,9 @@ function configureOpenCode({ mcpServers, tools }: ConfigureOpenCodeParams): void
|
|||||||
// build permission object based on tool permissions
|
// build permission object based on tool permissions
|
||||||
// note: OpenCode has no built-in web search tool
|
// note: OpenCode has no built-in web search tool
|
||||||
const permission = {
|
const permission = {
|
||||||
edit: tools.write === "disabled" ? "deny" : "allow",
|
edit: ctx.tools.write === "disabled" ? "deny" : "allow",
|
||||||
bash: tools.bash !== "enabled" ? "deny" : "allow",
|
bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
|
||||||
webfetch: tools.web === "disabled" ? "deny" : "allow",
|
webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
|
||||||
doom_loop: "allow",
|
doom_loop: "allow",
|
||||||
external_directory: "allow",
|
external_directory: "allow",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -66,14 +66,6 @@ export interface AgentConfig {
|
|||||||
tools: ToolPermissions;
|
tools: ToolPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for configuring MCP servers
|
|
||||||
*/
|
|
||||||
export interface ConfigureMcpServersParams {
|
|
||||||
mcpServers: Record<string, McpHttpServerConfig>;
|
|
||||||
cliPath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add agent-specific vars to a whitelisted environment object for agent subprocesses.
|
* Add agent-specific vars to a whitelisted environment object for agent subprocesses.
|
||||||
*
|
*
|
||||||
|
|||||||
+101
-112
@@ -100032,11 +100032,19 @@ var package_default = {
|
|||||||
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
||||||
};
|
};
|
||||||
|
|
||||||
// utils/cli.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);
|
||||||
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
||||||
|
function formatArgs(args3) {
|
||||||
|
return args3.map((arg) => {
|
||||||
|
if (typeof arg === "string") return arg;
|
||||||
|
if (arg instanceof Error) return `${arg.message}
|
||||||
|
${arg.stack}`;
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
}).join(" ");
|
||||||
|
}
|
||||||
function startGroup2(name) {
|
function startGroup2(name) {
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core.startGroup(name);
|
core.startGroup(name);
|
||||||
@@ -100145,25 +100153,25 @@ function separator(length = 50) {
|
|||||||
}
|
}
|
||||||
var log = {
|
var log = {
|
||||||
/** Print info message */
|
/** Print info message */
|
||||||
info: (message) => {
|
info: (...args3) => {
|
||||||
core.info(message);
|
core.info(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print warning message */
|
/** Print warning message */
|
||||||
warning: (message) => {
|
warning: (...args3) => {
|
||||||
core.warning(message);
|
core.warning(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print error message */
|
/** Print error message */
|
||||||
error: (message) => {
|
error: (...args3) => {
|
||||||
core.error(message);
|
core.error(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print success message */
|
/** Print success message */
|
||||||
success: (message) => {
|
success: (...args3) => {
|
||||||
core.info(`\u2705 ${message}`);
|
core.info(`\u2705 ${formatArgs(args3)}`);
|
||||||
},
|
},
|
||||||
/** Print debug message (only if LOG_LEVEL=debug) */
|
/** Print debug message (only if LOG_LEVEL=debug) */
|
||||||
debug: (message) => {
|
debug: (...args3) => {
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
core.info(`[DEBUG] ${message}`);
|
core.info(`[DEBUG] ${formatArgs(args3)}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** Print a formatted box with text */
|
/** Print a formatted box with text */
|
||||||
@@ -100420,14 +100428,14 @@ function getShellInstructions(bash) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var addInstructions = ({ payload, repo, tools }) => {
|
var addInstructions = (ctx) => {
|
||||||
let encodedEvent = "";
|
let encodedEvent = "";
|
||||||
const eventKeys = Object.keys(payload.event);
|
const eventKeys = Object.keys(ctx.payload.event);
|
||||||
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
||||||
} else {
|
} else {
|
||||||
encodedEvent = encode(payload.event);
|
encodedEvent = encode(ctx.payload.event);
|
||||||
}
|
}
|
||||||
const runtimeContext = buildRuntimeContext(repo);
|
const runtimeContext = buildRuntimeContext(ctx.repo);
|
||||||
return `
|
return `
|
||||||
***********************************************
|
***********************************************
|
||||||
************* SYSTEM INSTRUCTIONS *************
|
************* SYSTEM INSTRUCTIONS *************
|
||||||
@@ -100475,7 +100483,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
|
|||||||
|
|
||||||
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
||||||
|
|
||||||
${getShellInstructions(tools.bash)}
|
${getShellInstructions(ctx.tools.bash)}
|
||||||
|
|
||||||
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
||||||
|
|
||||||
@@ -100496,7 +100504,7 @@ ${getShellInstructions(tools.bash)}
|
|||||||
|
|
||||||
### Available modes
|
### Available modes
|
||||||
|
|
||||||
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||||
|
|
||||||
### Following the mode instructions
|
### Following the mode instructions
|
||||||
|
|
||||||
@@ -100506,7 +100514,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
|
|||||||
|
|
||||||
************* USER PROMPT *************
|
************* USER PROMPT *************
|
||||||
|
|
||||||
${payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
|
${ctx.payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
|
||||||
|
|
||||||
${encodedEvent ? `************* EVENT DATA *************
|
${encodedEvent ? `************* EVENT DATA *************
|
||||||
|
|
||||||
@@ -104635,12 +104643,12 @@ var claudeEffortModels = {
|
|||||||
auto: "opusplan",
|
auto: "opusplan",
|
||||||
max: "opus"
|
max: "opus"
|
||||||
};
|
};
|
||||||
function buildDisallowedTools(tools) {
|
function buildDisallowedTools(ctx) {
|
||||||
const disallowed = [];
|
const disallowed = [];
|
||||||
if (tools.web === "disabled") disallowed.push("WebFetch");
|
if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
|
||||||
if (tools.search === "disabled") disallowed.push("WebSearch");
|
if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
|
||||||
if (tools.write === "disabled") disallowed.push("Write");
|
if (ctx.tools.write === "disabled") disallowed.push("Write");
|
||||||
if (tools.bash !== "enabled") disallowed.push("Bash");
|
if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
|
||||||
return disallowed;
|
return disallowed;
|
||||||
}
|
}
|
||||||
var claude = agent({
|
var claude = agent({
|
||||||
@@ -104653,23 +104661,23 @@ var claude = agent({
|
|||||||
executablePath: "cli.js"
|
executablePath: "cli.js"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
const model = claudeEffortModels[effort];
|
const model = claudeEffortModels[ctx.effort];
|
||||||
log.info(`Using model: ${model} (effort: ${effort})`);
|
log.info(`Using model: ${model} (effort: ${ctx.effort})`);
|
||||||
const disallowedTools = buildDisallowedTools(tools);
|
const disallowedTools = buildDisallowedTools(ctx);
|
||||||
if (disallowedTools.length > 0) {
|
if (disallowedTools.length > 0) {
|
||||||
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
|
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
|
||||||
}
|
}
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
permissionMode: "bypassPermissions",
|
permissionMode: "bypassPermissions",
|
||||||
disallowedTools,
|
disallowedTools,
|
||||||
mcpServers,
|
mcpServers: ctx.mcpServers,
|
||||||
model,
|
model,
|
||||||
pathToClaudeCodeExecutable: cliPath,
|
pathToClaudeCodeExecutable: ctx.cliPath,
|
||||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey })
|
env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey })
|
||||||
};
|
};
|
||||||
const queryInstance = query({
|
const queryInstance = query({
|
||||||
prompt,
|
prompt,
|
||||||
@@ -105132,19 +105140,20 @@ var codexReasoningEffort = {
|
|||||||
// use default
|
// use default
|
||||||
max: "high"
|
max: "high"
|
||||||
};
|
};
|
||||||
function writeCodexConfig({ tempHome, mcpServers, tools }) {
|
function writeCodexConfig(ctx) {
|
||||||
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const codexDir = join6(tempHome, ".codex");
|
const codexDir = join6(tempHome, ".codex");
|
||||||
mkdirSync3(codexDir, { recursive: true });
|
mkdirSync3(codexDir, { recursive: true });
|
||||||
const configPath = join6(codexDir, "config.toml");
|
const configPath = join6(codexDir, "config.toml");
|
||||||
const mcpServerSections = [];
|
const mcpServerSections = [];
|
||||||
for (const [name, config4] of Object.entries(mcpServers)) {
|
for (const [name, config4] of Object.entries(ctx.mcpServers)) {
|
||||||
if (config4.type !== "http") continue;
|
if (config4.type !== "http") continue;
|
||||||
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
|
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
|
||||||
mcpServerSections.push(`[mcp_servers.${name}]
|
mcpServerSections.push(`[mcp_servers.${name}]
|
||||||
url = "${config4.url}"`);
|
url = "${config4.url}"`);
|
||||||
}
|
}
|
||||||
const features = [];
|
const features = [];
|
||||||
if (tools.bash !== "enabled") {
|
if (ctx.tools.bash !== "enabled") {
|
||||||
features.push("shell_command_tool = false");
|
features.push("shell_command_tool = false");
|
||||||
features.push("unified_exec = false");
|
features.push("unified_exec = false");
|
||||||
}
|
}
|
||||||
@@ -105159,7 +105168,7 @@ ${mcpServerSections.join("\n\n")}
|
|||||||
`.trim() + "\n"
|
`.trim() + "\n"
|
||||||
);
|
);
|
||||||
log.info(
|
log.info(
|
||||||
`\xBB Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})`
|
`\xBB Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
|
||||||
);
|
);
|
||||||
return codexDir;
|
return codexDir;
|
||||||
}
|
}
|
||||||
@@ -105172,41 +105181,37 @@ var codex = agent({
|
|||||||
executablePath: "bin/codex.js"
|
executablePath: "bin/codex.js"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join6(tempHome, ".config", "codex");
|
const configDir = join6(tempHome, ".config", "codex");
|
||||||
mkdirSync3(configDir, { recursive: true });
|
mkdirSync3(configDir, { recursive: true });
|
||||||
const codexDir = writeCodexConfig({
|
const codexDir = writeCodexConfig(ctx);
|
||||||
tempHome,
|
|
||||||
mcpServers,
|
|
||||||
tools
|
|
||||||
});
|
|
||||||
setupProcessAgentEnv({
|
setupProcessAgentEnv({
|
||||||
OPENAI_API_KEY: apiKey,
|
OPENAI_API_KEY: ctx.apiKey,
|
||||||
HOME: tempHome,
|
HOME: tempHome,
|
||||||
CODEX_HOME: codexDir
|
CODEX_HOME: codexDir
|
||||||
// point Codex to our config directory
|
// point Codex to our config directory
|
||||||
});
|
});
|
||||||
const model = codexModel[effort];
|
const model = codexModel[ctx.effort];
|
||||||
const modelReasoningEffort = codexReasoningEffort[effort];
|
const modelReasoningEffort = codexReasoningEffort[ctx.effort];
|
||||||
log.info(`Using model: ${model}`);
|
log.info(`Using model: ${model}`);
|
||||||
if (modelReasoningEffort) {
|
if (modelReasoningEffort) {
|
||||||
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
||||||
}
|
}
|
||||||
const codexOptions = {
|
const codexOptions = {
|
||||||
apiKey,
|
apiKey: ctx.apiKey,
|
||||||
codexPathOverride: cliPath
|
codexPathOverride: ctx.cliPath
|
||||||
};
|
};
|
||||||
const codex2 = new Codex(codexOptions);
|
const codex2 = new Codex(codexOptions);
|
||||||
const threadOptions = {
|
const threadOptions = {
|
||||||
model,
|
model,
|
||||||
approvalPolicy: "never",
|
approvalPolicy: "never",
|
||||||
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
||||||
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access",
|
sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
|
||||||
// web: controls network access
|
// web: controls network access
|
||||||
networkAccessEnabled: tools.web !== "disabled",
|
networkAccessEnabled: ctx.tools.web !== "disabled",
|
||||||
// search: controls web search
|
// search: controls web search
|
||||||
webSearchEnabled: tools.search !== "disabled",
|
webSearchEnabled: ctx.tools.search !== "disabled",
|
||||||
...modelReasoningEffort && { modelReasoningEffort }
|
...modelReasoningEffort && { modelReasoningEffort }
|
||||||
};
|
};
|
||||||
log.info(
|
log.info(
|
||||||
@@ -105214,7 +105219,7 @@ var codex = agent({
|
|||||||
);
|
);
|
||||||
const thread = codex2.startThread(threadOptions);
|
const thread = codex2.startThread(threadOptions);
|
||||||
try {
|
try {
|
||||||
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools }));
|
const streamedTurn = await thread.runStreamed(addInstructions(ctx));
|
||||||
let finalOutput2 = "";
|
let finalOutput2 = "";
|
||||||
for await (const event of streamedTurn.events) {
|
for await (const event of streamedTurn.events) {
|
||||||
const handler2 = messageHandlers2[event.type];
|
const handler2 = messageHandlers2[event.type];
|
||||||
@@ -105341,9 +105346,9 @@ var cursor = agent({
|
|||||||
executableName: "cursor-agent"
|
executableName: "cursor-agent"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
configureCursorMcpServers({ mcpServers, cliPath });
|
configureCursorMcpServers(ctx);
|
||||||
configureCursorTools({ tools });
|
configureCursorTools(ctx);
|
||||||
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
|
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
|
||||||
let modelOverride = null;
|
let modelOverride = null;
|
||||||
if (existsSync4(projectCliConfigPath)) {
|
if (existsSync4(projectCliConfigPath)) {
|
||||||
@@ -105352,18 +105357,18 @@ var cursor = agent({
|
|||||||
if (projectConfig.model) {
|
if (projectConfig.model) {
|
||||||
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
log.info(`Using model: ${modelOverride} (effort: ${effort})`);
|
log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
|
||||||
} else if (!existsSync4(projectCliConfigPath)) {
|
} else if (!existsSync4(projectCliConfigPath)) {
|
||||||
log.info(`Using default model (effort: ${effort})`);
|
log.info(`Using default model (effort: ${ctx.effort})`);
|
||||||
}
|
}
|
||||||
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
||||||
const messageHandlers5 = {
|
const messageHandlers5 = {
|
||||||
@@ -105415,7 +105420,7 @@ var cursor = agent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const fullPrompt = addInstructions({ payload, repo, tools });
|
const fullPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(fullPrompt));
|
log.group("Full prompt", () => log.info(fullPrompt));
|
||||||
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
|
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
@@ -105425,10 +105430,10 @@ var cursor = agent({
|
|||||||
log.info("Running Cursor CLI...");
|
log.info("Running Cursor CLI...");
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
return new Promise((resolve2) => {
|
return new Promise((resolve2) => {
|
||||||
const child = spawn3(cliPath, cursorArgs, {
|
const child = spawn3(ctx.cliPath, cursorArgs, {
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
env: createAgentEnv({
|
env: createAgentEnv({
|
||||||
CURSOR_API_KEY: apiKey
|
CURSOR_API_KEY: ctx.apiKey
|
||||||
}),
|
}),
|
||||||
stdio: ["ignore", "pipe", "pipe"]
|
stdio: ["ignore", "pipe", "pipe"]
|
||||||
// Ignore stdin, pipe stdout/stderr
|
// Ignore stdin, pipe stdout/stderr
|
||||||
@@ -105503,13 +105508,13 @@ var cursor = agent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureCursorMcpServers({ mcpServers }) {
|
function configureCursorMcpServers(ctx) {
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const cursorConfigDir = join7(realHome, ".cursor");
|
const cursorConfigDir = join7(realHome, ".cursor");
|
||||||
const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
|
const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
|
||||||
mkdirSync4(cursorConfigDir, { recursive: true });
|
mkdirSync4(cursorConfigDir, { recursive: true });
|
||||||
const cursorMcpServers = {};
|
const cursorMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
|
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
|
||||||
@@ -105523,33 +105528,29 @@ function configureCursorMcpServers({ mcpServers }) {
|
|||||||
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
|
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
|
||||||
log.info(`\xBB MCP config written to ${mcpConfigPath}`);
|
log.info(`\xBB MCP config written to ${mcpConfigPath}`);
|
||||||
}
|
}
|
||||||
function configureCursorTools({ tools }) {
|
function configureCursorTools(ctx) {
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const cursorConfigDir = join7(realHome, ".config", "cursor");
|
const cursorConfigDir = join7(realHome, ".config", "cursor");
|
||||||
const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
|
const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
|
||||||
mkdirSync4(cursorConfigDir, { recursive: true });
|
mkdirSync4(cursorConfigDir, { recursive: true });
|
||||||
const deny = [];
|
const deny = [];
|
||||||
if (tools.search === "disabled") deny.push("WebSearch");
|
if (ctx.tools.search === "disabled") deny.push("WebSearch");
|
||||||
if (tools.write === "disabled") deny.push("Write(**)");
|
if (ctx.tools.write === "disabled") deny.push("Write(**)");
|
||||||
if (tools.bash !== "enabled") deny.push("Shell(*)");
|
if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
|
||||||
const needsSandbox = tools.web === "disabled";
|
|
||||||
const config4 = {
|
const config4 = {
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
||||||
deny
|
deny
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (needsSandbox) {
|
if (ctx.tools.web === "disabled") {
|
||||||
config4.sandbox = {
|
config4.sandbox = {
|
||||||
mode: "enabled",
|
mode: "enabled",
|
||||||
networkAccess: "allowlist"
|
networkAccess: "allowlist"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
|
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
|
||||||
log.info(`\xBB CLI config written to ${cliConfigPath}`);
|
log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
|
||||||
log.info(
|
|
||||||
`\u{1F527} Cursor permissions: allow=${config4.permissions.allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/gemini.ts
|
// agents/gemini.ts
|
||||||
@@ -105725,14 +105726,12 @@ var gemini = agent({
|
|||||||
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
|
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const { model, thinkingLevel } = geminiEffortConfig[effort];
|
const model = configureGeminiSettings(ctx);
|
||||||
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
if (!ctx.apiKey) {
|
||||||
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||||
}
|
}
|
||||||
const sessionPrompt = addInstructions({ payload, repo, tools });
|
const sessionPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(sessionPrompt));
|
log.group("Full prompt", () => log.info(sessionPrompt));
|
||||||
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||||
let finalOutput2 = "";
|
let finalOutput2 = "";
|
||||||
@@ -105740,8 +105739,8 @@ var gemini = agent({
|
|||||||
try {
|
try {
|
||||||
const result = await spawn4({
|
const result = await spawn4({
|
||||||
cmd: "node",
|
cmd: "node",
|
||||||
args: [cliPath, ...args3],
|
args: [ctx.cliPath, ...args3],
|
||||||
env: createAgentEnv({ GEMINI_API_KEY: apiKey }),
|
env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
|
||||||
onStdout: async (chunk) => {
|
onStdout: async (chunk) => {
|
||||||
const text = chunk.toString();
|
const text = chunk.toString();
|
||||||
finalOutput2 += text;
|
finalOutput2 += text;
|
||||||
@@ -105798,11 +105797,9 @@ var gemini = agent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureGeminiSettings({
|
function configureGeminiSettings(ctx) {
|
||||||
mcpServers,
|
const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
|
||||||
tools,
|
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
||||||
thinkingLevel
|
|
||||||
}) {
|
|
||||||
const realHome = homedir3();
|
const realHome = homedir3();
|
||||||
const geminiConfigDir = join8(realHome, ".gemini");
|
const geminiConfigDir = join8(realHome, ".gemini");
|
||||||
const settingsPath = join8(geminiConfigDir, "settings.json");
|
const settingsPath = join8(geminiConfigDir, "settings.json");
|
||||||
@@ -105814,7 +105811,7 @@ function configureGeminiSettings({
|
|||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
const geminiMcpServers = {};
|
const geminiMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
|
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
|
||||||
@@ -105828,10 +105825,10 @@ function configureGeminiSettings({
|
|||||||
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
||||||
}
|
}
|
||||||
const exclude = [];
|
const exclude = [];
|
||||||
if (tools.bash !== "enabled") exclude.push("run_shell_command");
|
if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
|
||||||
if (tools.write === "disabled") exclude.push("write_file");
|
if (ctx.tools.write === "disabled") exclude.push("write_file");
|
||||||
if (tools.web === "disabled") exclude.push("web_fetch");
|
if (ctx.tools.web === "disabled") exclude.push("web_fetch");
|
||||||
if (tools.search === "disabled") exclude.push("google_web_search");
|
if (ctx.tools.search === "disabled") exclude.push("google_web_search");
|
||||||
const newSettings = {
|
const newSettings = {
|
||||||
...existingSettings,
|
...existingSettings,
|
||||||
mcpServers: geminiMcpServers,
|
mcpServers: geminiMcpServers,
|
||||||
@@ -105852,6 +105849,7 @@ function configureGeminiSettings({
|
|||||||
if (exclude.length > 0) {
|
if (exclude.length > 0) {
|
||||||
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
|
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/opencode.ts
|
// agents/opencode.ts
|
||||||
@@ -105867,21 +105865,12 @@ var opencode = agent({
|
|||||||
installDependencies: true
|
installDependencies: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({
|
run: async (ctx) => {
|
||||||
payload,
|
|
||||||
apiKey: _apiKey,
|
|
||||||
apiKeys,
|
|
||||||
mcpServers,
|
|
||||||
cliPath,
|
|
||||||
repo,
|
|
||||||
effort: _effort,
|
|
||||||
tools
|
|
||||||
}) => {
|
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join9(tempHome, ".config", "opencode");
|
const configDir = join9(tempHome, ".config", "opencode");
|
||||||
mkdirSync6(configDir, { recursive: true });
|
mkdirSync6(configDir, { recursive: true });
|
||||||
configureOpenCode({ mcpServers, tools });
|
configureOpenCode(ctx);
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
const args3 = ["run", prompt, "--format", "json"];
|
const args3 = ["run", prompt, "--format", "json"];
|
||||||
setupProcessAgentEnv({ HOME: tempHome });
|
setupProcessAgentEnv({ HOME: tempHome });
|
||||||
@@ -105890,14 +105879,14 @@ var opencode = agent({
|
|||||||
XDG_CONFIG_HOME: join9(tempHome, ".config")
|
XDG_CONFIG_HOME: join9(tempHome, ".config")
|
||||||
};
|
};
|
||||||
delete env3.GITHUB_TOKEN;
|
delete env3.GITHUB_TOKEN;
|
||||||
for (const [key, value2] of Object.entries(apiKeys || {})) {
|
for (const [key, value2] of Object.entries(ctx.apiKeys || {})) {
|
||||||
env3[key.toUpperCase()] = value2;
|
env3[key.toUpperCase()] = value2;
|
||||||
if (key === "GEMINI_API_KEY") {
|
if (key === "GEMINI_API_KEY") {
|
||||||
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
|
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const repoDir = process.cwd();
|
const repoDir = process.cwd();
|
||||||
log.info(`\u{1F680} Starting OpenCode CLI: ${cliPath} ${args3.join(" ")}`);
|
log.info(`\u{1F680} Starting OpenCode CLI: ${ctx.cliPath} ${args3.join(" ")}`);
|
||||||
log.info(`\u{1F4C1} Working directory: ${repoDir}`);
|
log.info(`\u{1F4C1} Working directory: ${repoDir}`);
|
||||||
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
|
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
|
||||||
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
|
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
|
||||||
@@ -105907,7 +105896,7 @@ var opencode = agent({
|
|||||||
let output = "";
|
let output = "";
|
||||||
let stdoutBuffer = "";
|
let stdoutBuffer = "";
|
||||||
const result = await spawn4({
|
const result = await spawn4({
|
||||||
cmd: cliPath,
|
cmd: ctx.cliPath,
|
||||||
args: args3,
|
args: args3,
|
||||||
cwd: repoDir,
|
cwd: repoDir,
|
||||||
env: env3,
|
env: env3,
|
||||||
@@ -105993,13 +105982,13 @@ var opencode = agent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureOpenCode({ mcpServers, tools }) {
|
function configureOpenCode(ctx) {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join9(tempHome, ".config", "opencode");
|
const configDir = join9(tempHome, ".config", "opencode");
|
||||||
mkdirSync6(configDir, { recursive: true });
|
mkdirSync6(configDir, { recursive: true });
|
||||||
const configPath = join9(configDir, "opencode.json");
|
const configPath = join9(configDir, "opencode.json");
|
||||||
const opencodeMcpServers = {};
|
const opencodeMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
log.error(
|
log.error(
|
||||||
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
|
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
|
||||||
@@ -106014,9 +106003,9 @@ function configureOpenCode({ mcpServers, tools }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const permission = {
|
const permission = {
|
||||||
edit: tools.write === "disabled" ? "deny" : "allow",
|
edit: ctx.tools.write === "disabled" ? "deny" : "allow",
|
||||||
bash: tools.bash !== "enabled" ? "deny" : "allow",
|
bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
|
||||||
webfetch: tools.web === "disabled" ? "deny" : "allow",
|
webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
|
||||||
doom_loop: "allow",
|
doom_loop: "allow",
|
||||||
external_directory: "allow"
|
external_directory: "allow"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100032,11 +100032,19 @@ var package_default = {
|
|||||||
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
||||||
};
|
};
|
||||||
|
|
||||||
// utils/cli.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);
|
||||||
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
||||||
|
function formatArgs(args3) {
|
||||||
|
return args3.map((arg) => {
|
||||||
|
if (typeof arg === "string") return arg;
|
||||||
|
if (arg instanceof Error) return `${arg.message}
|
||||||
|
${arg.stack}`;
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
}).join(" ");
|
||||||
|
}
|
||||||
function startGroup2(name) {
|
function startGroup2(name) {
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core.startGroup(name);
|
core.startGroup(name);
|
||||||
@@ -100145,25 +100153,25 @@ function separator(length = 50) {
|
|||||||
}
|
}
|
||||||
var log = {
|
var log = {
|
||||||
/** Print info message */
|
/** Print info message */
|
||||||
info: (message) => {
|
info: (...args3) => {
|
||||||
core.info(message);
|
core.info(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print warning message */
|
/** Print warning message */
|
||||||
warning: (message) => {
|
warning: (...args3) => {
|
||||||
core.warning(message);
|
core.warning(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print error message */
|
/** Print error message */
|
||||||
error: (message) => {
|
error: (...args3) => {
|
||||||
core.error(message);
|
core.error(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print success message */
|
/** Print success message */
|
||||||
success: (message) => {
|
success: (...args3) => {
|
||||||
core.info(`\u2705 ${message}`);
|
core.info(`\u2705 ${formatArgs(args3)}`);
|
||||||
},
|
},
|
||||||
/** Print debug message (only if LOG_LEVEL=debug) */
|
/** Print debug message (only if LOG_LEVEL=debug) */
|
||||||
debug: (message) => {
|
debug: (...args3) => {
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
core.info(`[DEBUG] ${message}`);
|
core.info(`[DEBUG] ${formatArgs(args3)}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** Print a formatted box with text */
|
/** Print a formatted box with text */
|
||||||
@@ -100420,14 +100428,14 @@ function getShellInstructions(bash) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var addInstructions = ({ payload, repo, tools }) => {
|
var addInstructions = (ctx) => {
|
||||||
let encodedEvent = "";
|
let encodedEvent = "";
|
||||||
const eventKeys = Object.keys(payload.event);
|
const eventKeys = Object.keys(ctx.payload.event);
|
||||||
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
||||||
} else {
|
} else {
|
||||||
encodedEvent = encode(payload.event);
|
encodedEvent = encode(ctx.payload.event);
|
||||||
}
|
}
|
||||||
const runtimeContext = buildRuntimeContext(repo);
|
const runtimeContext = buildRuntimeContext(ctx.repo);
|
||||||
return `
|
return `
|
||||||
***********************************************
|
***********************************************
|
||||||
************* SYSTEM INSTRUCTIONS *************
|
************* SYSTEM INSTRUCTIONS *************
|
||||||
@@ -100475,7 +100483,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
|
|||||||
|
|
||||||
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
||||||
|
|
||||||
${getShellInstructions(tools.bash)}
|
${getShellInstructions(ctx.tools.bash)}
|
||||||
|
|
||||||
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
||||||
|
|
||||||
@@ -100496,7 +100504,7 @@ ${getShellInstructions(tools.bash)}
|
|||||||
|
|
||||||
### Available modes
|
### Available modes
|
||||||
|
|
||||||
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||||
|
|
||||||
### Following the mode instructions
|
### Following the mode instructions
|
||||||
|
|
||||||
@@ -100506,7 +100514,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
|
|||||||
|
|
||||||
************* USER PROMPT *************
|
************* USER PROMPT *************
|
||||||
|
|
||||||
${payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
|
${ctx.payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
|
||||||
|
|
||||||
${encodedEvent ? `************* EVENT DATA *************
|
${encodedEvent ? `************* EVENT DATA *************
|
||||||
|
|
||||||
@@ -104635,12 +104643,12 @@ var claudeEffortModels = {
|
|||||||
auto: "opusplan",
|
auto: "opusplan",
|
||||||
max: "opus"
|
max: "opus"
|
||||||
};
|
};
|
||||||
function buildDisallowedTools(tools) {
|
function buildDisallowedTools(ctx) {
|
||||||
const disallowed = [];
|
const disallowed = [];
|
||||||
if (tools.web === "disabled") disallowed.push("WebFetch");
|
if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
|
||||||
if (tools.search === "disabled") disallowed.push("WebSearch");
|
if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
|
||||||
if (tools.write === "disabled") disallowed.push("Write");
|
if (ctx.tools.write === "disabled") disallowed.push("Write");
|
||||||
if (tools.bash !== "enabled") disallowed.push("Bash");
|
if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
|
||||||
return disallowed;
|
return disallowed;
|
||||||
}
|
}
|
||||||
var claude = agent({
|
var claude = agent({
|
||||||
@@ -104653,23 +104661,23 @@ var claude = agent({
|
|||||||
executablePath: "cli.js"
|
executablePath: "cli.js"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
const model = claudeEffortModels[effort];
|
const model = claudeEffortModels[ctx.effort];
|
||||||
log.info(`Using model: ${model} (effort: ${effort})`);
|
log.info(`Using model: ${model} (effort: ${ctx.effort})`);
|
||||||
const disallowedTools = buildDisallowedTools(tools);
|
const disallowedTools = buildDisallowedTools(ctx);
|
||||||
if (disallowedTools.length > 0) {
|
if (disallowedTools.length > 0) {
|
||||||
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
|
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
|
||||||
}
|
}
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
permissionMode: "bypassPermissions",
|
permissionMode: "bypassPermissions",
|
||||||
disallowedTools,
|
disallowedTools,
|
||||||
mcpServers,
|
mcpServers: ctx.mcpServers,
|
||||||
model,
|
model,
|
||||||
pathToClaudeCodeExecutable: cliPath,
|
pathToClaudeCodeExecutable: ctx.cliPath,
|
||||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey })
|
env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey })
|
||||||
};
|
};
|
||||||
const queryInstance = query({
|
const queryInstance = query({
|
||||||
prompt,
|
prompt,
|
||||||
@@ -105132,19 +105140,20 @@ var codexReasoningEffort = {
|
|||||||
// use default
|
// use default
|
||||||
max: "high"
|
max: "high"
|
||||||
};
|
};
|
||||||
function writeCodexConfig({ tempHome, mcpServers, tools }) {
|
function writeCodexConfig(ctx) {
|
||||||
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const codexDir = join6(tempHome, ".codex");
|
const codexDir = join6(tempHome, ".codex");
|
||||||
mkdirSync3(codexDir, { recursive: true });
|
mkdirSync3(codexDir, { recursive: true });
|
||||||
const configPath = join6(codexDir, "config.toml");
|
const configPath = join6(codexDir, "config.toml");
|
||||||
const mcpServerSections = [];
|
const mcpServerSections = [];
|
||||||
for (const [name, config4] of Object.entries(mcpServers)) {
|
for (const [name, config4] of Object.entries(ctx.mcpServers)) {
|
||||||
if (config4.type !== "http") continue;
|
if (config4.type !== "http") continue;
|
||||||
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
|
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
|
||||||
mcpServerSections.push(`[mcp_servers.${name}]
|
mcpServerSections.push(`[mcp_servers.${name}]
|
||||||
url = "${config4.url}"`);
|
url = "${config4.url}"`);
|
||||||
}
|
}
|
||||||
const features = [];
|
const features = [];
|
||||||
if (tools.bash !== "enabled") {
|
if (ctx.tools.bash !== "enabled") {
|
||||||
features.push("shell_command_tool = false");
|
features.push("shell_command_tool = false");
|
||||||
features.push("unified_exec = false");
|
features.push("unified_exec = false");
|
||||||
}
|
}
|
||||||
@@ -105159,7 +105168,7 @@ ${mcpServerSections.join("\n\n")}
|
|||||||
`.trim() + "\n"
|
`.trim() + "\n"
|
||||||
);
|
);
|
||||||
log.info(
|
log.info(
|
||||||
`\xBB Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})`
|
`\xBB Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
|
||||||
);
|
);
|
||||||
return codexDir;
|
return codexDir;
|
||||||
}
|
}
|
||||||
@@ -105172,41 +105181,37 @@ var codex = agent({
|
|||||||
executablePath: "bin/codex.js"
|
executablePath: "bin/codex.js"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join6(tempHome, ".config", "codex");
|
const configDir = join6(tempHome, ".config", "codex");
|
||||||
mkdirSync3(configDir, { recursive: true });
|
mkdirSync3(configDir, { recursive: true });
|
||||||
const codexDir = writeCodexConfig({
|
const codexDir = writeCodexConfig(ctx);
|
||||||
tempHome,
|
|
||||||
mcpServers,
|
|
||||||
tools
|
|
||||||
});
|
|
||||||
setupProcessAgentEnv({
|
setupProcessAgentEnv({
|
||||||
OPENAI_API_KEY: apiKey,
|
OPENAI_API_KEY: ctx.apiKey,
|
||||||
HOME: tempHome,
|
HOME: tempHome,
|
||||||
CODEX_HOME: codexDir
|
CODEX_HOME: codexDir
|
||||||
// point Codex to our config directory
|
// point Codex to our config directory
|
||||||
});
|
});
|
||||||
const model = codexModel[effort];
|
const model = codexModel[ctx.effort];
|
||||||
const modelReasoningEffort = codexReasoningEffort[effort];
|
const modelReasoningEffort = codexReasoningEffort[ctx.effort];
|
||||||
log.info(`Using model: ${model}`);
|
log.info(`Using model: ${model}`);
|
||||||
if (modelReasoningEffort) {
|
if (modelReasoningEffort) {
|
||||||
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
||||||
}
|
}
|
||||||
const codexOptions = {
|
const codexOptions = {
|
||||||
apiKey,
|
apiKey: ctx.apiKey,
|
||||||
codexPathOverride: cliPath
|
codexPathOverride: ctx.cliPath
|
||||||
};
|
};
|
||||||
const codex2 = new Codex(codexOptions);
|
const codex2 = new Codex(codexOptions);
|
||||||
const threadOptions = {
|
const threadOptions = {
|
||||||
model,
|
model,
|
||||||
approvalPolicy: "never",
|
approvalPolicy: "never",
|
||||||
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
||||||
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access",
|
sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
|
||||||
// web: controls network access
|
// web: controls network access
|
||||||
networkAccessEnabled: tools.web !== "disabled",
|
networkAccessEnabled: ctx.tools.web !== "disabled",
|
||||||
// search: controls web search
|
// search: controls web search
|
||||||
webSearchEnabled: tools.search !== "disabled",
|
webSearchEnabled: ctx.tools.search !== "disabled",
|
||||||
...modelReasoningEffort && { modelReasoningEffort }
|
...modelReasoningEffort && { modelReasoningEffort }
|
||||||
};
|
};
|
||||||
log.info(
|
log.info(
|
||||||
@@ -105214,7 +105219,7 @@ var codex = agent({
|
|||||||
);
|
);
|
||||||
const thread = codex2.startThread(threadOptions);
|
const thread = codex2.startThread(threadOptions);
|
||||||
try {
|
try {
|
||||||
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools }));
|
const streamedTurn = await thread.runStreamed(addInstructions(ctx));
|
||||||
let finalOutput2 = "";
|
let finalOutput2 = "";
|
||||||
for await (const event of streamedTurn.events) {
|
for await (const event of streamedTurn.events) {
|
||||||
const handler2 = messageHandlers2[event.type];
|
const handler2 = messageHandlers2[event.type];
|
||||||
@@ -105341,9 +105346,9 @@ var cursor = agent({
|
|||||||
executableName: "cursor-agent"
|
executableName: "cursor-agent"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
configureCursorMcpServers({ mcpServers, cliPath });
|
configureCursorMcpServers(ctx);
|
||||||
configureCursorTools({ tools });
|
configureCursorTools(ctx);
|
||||||
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
|
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
|
||||||
let modelOverride = null;
|
let modelOverride = null;
|
||||||
if (existsSync4(projectCliConfigPath)) {
|
if (existsSync4(projectCliConfigPath)) {
|
||||||
@@ -105352,18 +105357,18 @@ var cursor = agent({
|
|||||||
if (projectConfig.model) {
|
if (projectConfig.model) {
|
||||||
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
log.info(`Using model: ${modelOverride} (effort: ${effort})`);
|
log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
|
||||||
} else if (!existsSync4(projectCliConfigPath)) {
|
} else if (!existsSync4(projectCliConfigPath)) {
|
||||||
log.info(`Using default model (effort: ${effort})`);
|
log.info(`Using default model (effort: ${ctx.effort})`);
|
||||||
}
|
}
|
||||||
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
||||||
const messageHandlers5 = {
|
const messageHandlers5 = {
|
||||||
@@ -105415,7 +105420,7 @@ var cursor = agent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const fullPrompt = addInstructions({ payload, repo, tools });
|
const fullPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(fullPrompt));
|
log.group("Full prompt", () => log.info(fullPrompt));
|
||||||
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
|
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
@@ -105425,10 +105430,10 @@ var cursor = agent({
|
|||||||
log.info("Running Cursor CLI...");
|
log.info("Running Cursor CLI...");
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
return new Promise((resolve2) => {
|
return new Promise((resolve2) => {
|
||||||
const child = spawn3(cliPath, cursorArgs, {
|
const child = spawn3(ctx.cliPath, cursorArgs, {
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
env: createAgentEnv({
|
env: createAgentEnv({
|
||||||
CURSOR_API_KEY: apiKey
|
CURSOR_API_KEY: ctx.apiKey
|
||||||
}),
|
}),
|
||||||
stdio: ["ignore", "pipe", "pipe"]
|
stdio: ["ignore", "pipe", "pipe"]
|
||||||
// Ignore stdin, pipe stdout/stderr
|
// Ignore stdin, pipe stdout/stderr
|
||||||
@@ -105503,13 +105508,13 @@ var cursor = agent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureCursorMcpServers({ mcpServers }) {
|
function configureCursorMcpServers(ctx) {
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const cursorConfigDir = join7(realHome, ".cursor");
|
const cursorConfigDir = join7(realHome, ".cursor");
|
||||||
const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
|
const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
|
||||||
mkdirSync4(cursorConfigDir, { recursive: true });
|
mkdirSync4(cursorConfigDir, { recursive: true });
|
||||||
const cursorMcpServers = {};
|
const cursorMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
|
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
|
||||||
@@ -105523,33 +105528,29 @@ function configureCursorMcpServers({ mcpServers }) {
|
|||||||
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
|
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
|
||||||
log.info(`\xBB MCP config written to ${mcpConfigPath}`);
|
log.info(`\xBB MCP config written to ${mcpConfigPath}`);
|
||||||
}
|
}
|
||||||
function configureCursorTools({ tools }) {
|
function configureCursorTools(ctx) {
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const cursorConfigDir = join7(realHome, ".config", "cursor");
|
const cursorConfigDir = join7(realHome, ".config", "cursor");
|
||||||
const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
|
const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
|
||||||
mkdirSync4(cursorConfigDir, { recursive: true });
|
mkdirSync4(cursorConfigDir, { recursive: true });
|
||||||
const deny = [];
|
const deny = [];
|
||||||
if (tools.search === "disabled") deny.push("WebSearch");
|
if (ctx.tools.search === "disabled") deny.push("WebSearch");
|
||||||
if (tools.write === "disabled") deny.push("Write(**)");
|
if (ctx.tools.write === "disabled") deny.push("Write(**)");
|
||||||
if (tools.bash !== "enabled") deny.push("Shell(*)");
|
if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
|
||||||
const needsSandbox = tools.web === "disabled";
|
|
||||||
const config4 = {
|
const config4 = {
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
||||||
deny
|
deny
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (needsSandbox) {
|
if (ctx.tools.web === "disabled") {
|
||||||
config4.sandbox = {
|
config4.sandbox = {
|
||||||
mode: "enabled",
|
mode: "enabled",
|
||||||
networkAccess: "allowlist"
|
networkAccess: "allowlist"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
|
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
|
||||||
log.info(`\xBB CLI config written to ${cliConfigPath}`);
|
log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
|
||||||
log.info(
|
|
||||||
`\u{1F527} Cursor permissions: allow=${config4.permissions.allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/gemini.ts
|
// agents/gemini.ts
|
||||||
@@ -105725,14 +105726,12 @@ var gemini = agent({
|
|||||||
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
|
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const { model, thinkingLevel } = geminiEffortConfig[effort];
|
const model = configureGeminiSettings(ctx);
|
||||||
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
if (!ctx.apiKey) {
|
||||||
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||||
}
|
}
|
||||||
const sessionPrompt = addInstructions({ payload, repo, tools });
|
const sessionPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(sessionPrompt));
|
log.group("Full prompt", () => log.info(sessionPrompt));
|
||||||
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||||
let finalOutput2 = "";
|
let finalOutput2 = "";
|
||||||
@@ -105740,8 +105739,8 @@ var gemini = agent({
|
|||||||
try {
|
try {
|
||||||
const result = await spawn4({
|
const result = await spawn4({
|
||||||
cmd: "node",
|
cmd: "node",
|
||||||
args: [cliPath, ...args3],
|
args: [ctx.cliPath, ...args3],
|
||||||
env: createAgentEnv({ GEMINI_API_KEY: apiKey }),
|
env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
|
||||||
onStdout: async (chunk) => {
|
onStdout: async (chunk) => {
|
||||||
const text = chunk.toString();
|
const text = chunk.toString();
|
||||||
finalOutput2 += text;
|
finalOutput2 += text;
|
||||||
@@ -105798,11 +105797,9 @@ var gemini = agent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureGeminiSettings({
|
function configureGeminiSettings(ctx) {
|
||||||
mcpServers,
|
const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
|
||||||
tools,
|
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
||||||
thinkingLevel
|
|
||||||
}) {
|
|
||||||
const realHome = homedir3();
|
const realHome = homedir3();
|
||||||
const geminiConfigDir = join8(realHome, ".gemini");
|
const geminiConfigDir = join8(realHome, ".gemini");
|
||||||
const settingsPath = join8(geminiConfigDir, "settings.json");
|
const settingsPath = join8(geminiConfigDir, "settings.json");
|
||||||
@@ -105814,7 +105811,7 @@ function configureGeminiSettings({
|
|||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
const geminiMcpServers = {};
|
const geminiMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
|
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
|
||||||
@@ -105828,10 +105825,10 @@ function configureGeminiSettings({
|
|||||||
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
||||||
}
|
}
|
||||||
const exclude = [];
|
const exclude = [];
|
||||||
if (tools.bash !== "enabled") exclude.push("run_shell_command");
|
if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
|
||||||
if (tools.write === "disabled") exclude.push("write_file");
|
if (ctx.tools.write === "disabled") exclude.push("write_file");
|
||||||
if (tools.web === "disabled") exclude.push("web_fetch");
|
if (ctx.tools.web === "disabled") exclude.push("web_fetch");
|
||||||
if (tools.search === "disabled") exclude.push("google_web_search");
|
if (ctx.tools.search === "disabled") exclude.push("google_web_search");
|
||||||
const newSettings = {
|
const newSettings = {
|
||||||
...existingSettings,
|
...existingSettings,
|
||||||
mcpServers: geminiMcpServers,
|
mcpServers: geminiMcpServers,
|
||||||
@@ -105852,6 +105849,7 @@ function configureGeminiSettings({
|
|||||||
if (exclude.length > 0) {
|
if (exclude.length > 0) {
|
||||||
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
|
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/opencode.ts
|
// agents/opencode.ts
|
||||||
@@ -105867,21 +105865,12 @@ var opencode = agent({
|
|||||||
installDependencies: true
|
installDependencies: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({
|
run: async (ctx) => {
|
||||||
payload,
|
|
||||||
apiKey: _apiKey,
|
|
||||||
apiKeys,
|
|
||||||
mcpServers,
|
|
||||||
cliPath,
|
|
||||||
repo,
|
|
||||||
effort: _effort,
|
|
||||||
tools
|
|
||||||
}) => {
|
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join9(tempHome, ".config", "opencode");
|
const configDir = join9(tempHome, ".config", "opencode");
|
||||||
mkdirSync6(configDir, { recursive: true });
|
mkdirSync6(configDir, { recursive: true });
|
||||||
configureOpenCode({ mcpServers, tools });
|
configureOpenCode(ctx);
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
const args3 = ["run", prompt, "--format", "json"];
|
const args3 = ["run", prompt, "--format", "json"];
|
||||||
setupProcessAgentEnv({ HOME: tempHome });
|
setupProcessAgentEnv({ HOME: tempHome });
|
||||||
@@ -105890,14 +105879,14 @@ var opencode = agent({
|
|||||||
XDG_CONFIG_HOME: join9(tempHome, ".config")
|
XDG_CONFIG_HOME: join9(tempHome, ".config")
|
||||||
};
|
};
|
||||||
delete env3.GITHUB_TOKEN;
|
delete env3.GITHUB_TOKEN;
|
||||||
for (const [key, value2] of Object.entries(apiKeys || {})) {
|
for (const [key, value2] of Object.entries(ctx.apiKeys || {})) {
|
||||||
env3[key.toUpperCase()] = value2;
|
env3[key.toUpperCase()] = value2;
|
||||||
if (key === "GEMINI_API_KEY") {
|
if (key === "GEMINI_API_KEY") {
|
||||||
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
|
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const repoDir = process.cwd();
|
const repoDir = process.cwd();
|
||||||
log.info(`\u{1F680} Starting OpenCode CLI: ${cliPath} ${args3.join(" ")}`);
|
log.info(`\u{1F680} Starting OpenCode CLI: ${ctx.cliPath} ${args3.join(" ")}`);
|
||||||
log.info(`\u{1F4C1} Working directory: ${repoDir}`);
|
log.info(`\u{1F4C1} Working directory: ${repoDir}`);
|
||||||
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
|
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
|
||||||
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
|
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
|
||||||
@@ -105907,7 +105896,7 @@ var opencode = agent({
|
|||||||
let output = "";
|
let output = "";
|
||||||
let stdoutBuffer = "";
|
let stdoutBuffer = "";
|
||||||
const result = await spawn4({
|
const result = await spawn4({
|
||||||
cmd: cliPath,
|
cmd: ctx.cliPath,
|
||||||
args: args3,
|
args: args3,
|
||||||
cwd: repoDir,
|
cwd: repoDir,
|
||||||
env: env3,
|
env: env3,
|
||||||
@@ -105993,13 +105982,13 @@ var opencode = agent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureOpenCode({ mcpServers, tools }) {
|
function configureOpenCode(ctx) {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join9(tempHome, ".config", "opencode");
|
const configDir = join9(tempHome, ".config", "opencode");
|
||||||
mkdirSync6(configDir, { recursive: true });
|
mkdirSync6(configDir, { recursive: true });
|
||||||
const configPath = join9(configDir, "opencode.json");
|
const configPath = join9(configDir, "opencode.json");
|
||||||
const opencodeMcpServers = {};
|
const opencodeMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
log.error(
|
log.error(
|
||||||
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
|
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
|
||||||
@@ -106014,9 +106003,9 @@ function configureOpenCode({ mcpServers, tools }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const permission = {
|
const permission = {
|
||||||
edit: tools.write === "disabled" ? "deny" : "allow",
|
edit: ctx.tools.write === "disabled" ? "deny" : "allow",
|
||||||
bash: tools.bash !== "enabled" ? "deny" : "allow",
|
bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
|
||||||
webfetch: tools.web === "disabled" ? "deny" : "allow",
|
webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
|
||||||
doom_loop: "allow",
|
doom_loop: "allow",
|
||||||
external_directory: "allow"
|
external_directory: "allow"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25505,11 +25505,19 @@ var core3 = __toESM(require_core(), 1);
|
|||||||
var core2 = __toESM(require_core(), 1);
|
var core2 = __toESM(require_core(), 1);
|
||||||
import { createSign } from "node:crypto";
|
import { createSign } from "node:crypto";
|
||||||
|
|
||||||
// utils/cli.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);
|
||||||
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
||||||
|
function formatArgs(args) {
|
||||||
|
return args.map((arg) => {
|
||||||
|
if (typeof arg === "string") return arg;
|
||||||
|
if (arg instanceof Error) return `${arg.message}
|
||||||
|
${arg.stack}`;
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
}).join(" ");
|
||||||
|
}
|
||||||
function startGroup2(name) {
|
function startGroup2(name) {
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core.startGroup(name);
|
core.startGroup(name);
|
||||||
@@ -25614,25 +25622,25 @@ function separator(length = 50) {
|
|||||||
}
|
}
|
||||||
var log = {
|
var log = {
|
||||||
/** Print info message */
|
/** Print info message */
|
||||||
info: (message) => {
|
info: (...args) => {
|
||||||
core.info(message);
|
core.info(formatArgs(args));
|
||||||
},
|
},
|
||||||
/** Print warning message */
|
/** Print warning message */
|
||||||
warning: (message) => {
|
warning: (...args) => {
|
||||||
core.warning(message);
|
core.warning(formatArgs(args));
|
||||||
},
|
},
|
||||||
/** Print error message */
|
/** Print error message */
|
||||||
error: (message) => {
|
error: (...args) => {
|
||||||
core.error(message);
|
core.error(formatArgs(args));
|
||||||
},
|
},
|
||||||
/** Print success message */
|
/** Print success message */
|
||||||
success: (message) => {
|
success: (...args) => {
|
||||||
core.info(`\u2705 ${message}`);
|
core.info(`\u2705 ${formatArgs(args)}`);
|
||||||
},
|
},
|
||||||
/** Print debug message (only if LOG_LEVEL=debug) */
|
/** Print debug message (only if LOG_LEVEL=debug) */
|
||||||
debug: (message) => {
|
debug: (...args) => {
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
core.info(`[DEBUG] ${message}`);
|
core.info(`[DEBUG] ${formatArgs(args)}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** Print a formatted box with text */
|
/** Print a formatted box with text */
|
||||||
|
|||||||
@@ -100032,11 +100032,19 @@ var package_default = {
|
|||||||
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
packageManager: "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
||||||
};
|
};
|
||||||
|
|
||||||
// utils/cli.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);
|
||||||
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
var isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
var isDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || core.isDebug();
|
||||||
|
function formatArgs(args3) {
|
||||||
|
return args3.map((arg) => {
|
||||||
|
if (typeof arg === "string") return arg;
|
||||||
|
if (arg instanceof Error) return `${arg.message}
|
||||||
|
${arg.stack}`;
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
}).join(" ");
|
||||||
|
}
|
||||||
function startGroup2(name) {
|
function startGroup2(name) {
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core.startGroup(name);
|
core.startGroup(name);
|
||||||
@@ -100145,25 +100153,25 @@ function separator(length = 50) {
|
|||||||
}
|
}
|
||||||
var log = {
|
var log = {
|
||||||
/** Print info message */
|
/** Print info message */
|
||||||
info: (message) => {
|
info: (...args3) => {
|
||||||
core.info(message);
|
core.info(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print warning message */
|
/** Print warning message */
|
||||||
warning: (message) => {
|
warning: (...args3) => {
|
||||||
core.warning(message);
|
core.warning(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print error message */
|
/** Print error message */
|
||||||
error: (message) => {
|
error: (...args3) => {
|
||||||
core.error(message);
|
core.error(formatArgs(args3));
|
||||||
},
|
},
|
||||||
/** Print success message */
|
/** Print success message */
|
||||||
success: (message) => {
|
success: (...args3) => {
|
||||||
core.info(`\u2705 ${message}`);
|
core.info(`\u2705 ${formatArgs(args3)}`);
|
||||||
},
|
},
|
||||||
/** Print debug message (only if LOG_LEVEL=debug) */
|
/** Print debug message (only if LOG_LEVEL=debug) */
|
||||||
debug: (message) => {
|
debug: (...args3) => {
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
core.info(`[DEBUG] ${message}`);
|
core.info(`[DEBUG] ${formatArgs(args3)}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** Print a formatted box with text */
|
/** Print a formatted box with text */
|
||||||
@@ -100420,14 +100428,14 @@ function getShellInstructions(bash) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var addInstructions = ({ payload, repo, tools }) => {
|
var addInstructions = (ctx) => {
|
||||||
let encodedEvent = "";
|
let encodedEvent = "";
|
||||||
const eventKeys = Object.keys(payload.event);
|
const eventKeys = Object.keys(ctx.payload.event);
|
||||||
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
if (eventKeys.length === 1 && eventKeys[0] === "trigger") {
|
||||||
} else {
|
} else {
|
||||||
encodedEvent = encode(payload.event);
|
encodedEvent = encode(ctx.payload.event);
|
||||||
}
|
}
|
||||||
const runtimeContext = buildRuntimeContext(repo);
|
const runtimeContext = buildRuntimeContext(ctx.repo);
|
||||||
return `
|
return `
|
||||||
***********************************************
|
***********************************************
|
||||||
************* SYSTEM INSTRUCTIONS *************
|
************* SYSTEM INSTRUCTIONS *************
|
||||||
@@ -100475,7 +100483,7 @@ Tool names may be formatted as \`(server name)/(tool name)\`, for example: \`${g
|
|||||||
|
|
||||||
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
**Efficiency**: Trust the tools - do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error.
|
||||||
|
|
||||||
${getShellInstructions(tools.bash)}
|
${getShellInstructions(ctx.tools.bash)}
|
||||||
|
|
||||||
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
**Command execution**: Never use \`sleep\` to wait for commands to complete. Commands run synchronously - when the bash tool returns, the command has finished.
|
||||||
|
|
||||||
@@ -100496,7 +100504,7 @@ ${getShellInstructions(tools.bash)}
|
|||||||
|
|
||||||
### Available modes
|
### Available modes
|
||||||
|
|
||||||
${[...getModes({ disableProgressComment: payload.disableProgressComment }), ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
${[...getModes({ disableProgressComment: ctx.payload.disableProgressComment }), ...ctx.payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")}
|
||||||
|
|
||||||
### Following the mode instructions
|
### Following the mode instructions
|
||||||
|
|
||||||
@@ -100506,7 +100514,7 @@ Eagerly inspect the MCP tools available to you via the \`${ghPullfrogMcpName}\`
|
|||||||
|
|
||||||
************* USER PROMPT *************
|
************* USER PROMPT *************
|
||||||
|
|
||||||
${payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
|
${ctx.payload.prompt.split("\n").map((line) => `> ${line}`).join("\n")}
|
||||||
|
|
||||||
${encodedEvent ? `************* EVENT DATA *************
|
${encodedEvent ? `************* EVENT DATA *************
|
||||||
|
|
||||||
@@ -104635,12 +104643,12 @@ var claudeEffortModels = {
|
|||||||
auto: "opusplan",
|
auto: "opusplan",
|
||||||
max: "opus"
|
max: "opus"
|
||||||
};
|
};
|
||||||
function buildDisallowedTools(tools) {
|
function buildDisallowedTools(ctx) {
|
||||||
const disallowed = [];
|
const disallowed = [];
|
||||||
if (tools.web === "disabled") disallowed.push("WebFetch");
|
if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
|
||||||
if (tools.search === "disabled") disallowed.push("WebSearch");
|
if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
|
||||||
if (tools.write === "disabled") disallowed.push("Write");
|
if (ctx.tools.write === "disabled") disallowed.push("Write");
|
||||||
if (tools.bash !== "enabled") disallowed.push("Bash");
|
if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
|
||||||
return disallowed;
|
return disallowed;
|
||||||
}
|
}
|
||||||
var claude = agent({
|
var claude = agent({
|
||||||
@@ -104653,23 +104661,23 @@ var claude = agent({
|
|||||||
executablePath: "cli.js"
|
executablePath: "cli.js"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
const model = claudeEffortModels[effort];
|
const model = claudeEffortModels[ctx.effort];
|
||||||
log.info(`Using model: ${model} (effort: ${effort})`);
|
log.info(`Using model: ${model} (effort: ${ctx.effort})`);
|
||||||
const disallowedTools = buildDisallowedTools(tools);
|
const disallowedTools = buildDisallowedTools(ctx);
|
||||||
if (disallowedTools.length > 0) {
|
if (disallowedTools.length > 0) {
|
||||||
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
|
log.info(`\u{1F512} disallowed tools: ${disallowedTools.join(", ")}`);
|
||||||
}
|
}
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
permissionMode: "bypassPermissions",
|
permissionMode: "bypassPermissions",
|
||||||
disallowedTools,
|
disallowedTools,
|
||||||
mcpServers,
|
mcpServers: ctx.mcpServers,
|
||||||
model,
|
model,
|
||||||
pathToClaudeCodeExecutable: cliPath,
|
pathToClaudeCodeExecutable: ctx.cliPath,
|
||||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey })
|
env: createAgentEnv({ ANTHROPIC_API_KEY: ctx.apiKey })
|
||||||
};
|
};
|
||||||
const queryInstance = query({
|
const queryInstance = query({
|
||||||
prompt,
|
prompt,
|
||||||
@@ -105132,19 +105140,20 @@ var codexReasoningEffort = {
|
|||||||
// use default
|
// use default
|
||||||
max: "high"
|
max: "high"
|
||||||
};
|
};
|
||||||
function writeCodexConfig({ tempHome, mcpServers, tools }) {
|
function writeCodexConfig(ctx) {
|
||||||
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const codexDir = join6(tempHome, ".codex");
|
const codexDir = join6(tempHome, ".codex");
|
||||||
mkdirSync3(codexDir, { recursive: true });
|
mkdirSync3(codexDir, { recursive: true });
|
||||||
const configPath = join6(codexDir, "config.toml");
|
const configPath = join6(codexDir, "config.toml");
|
||||||
const mcpServerSections = [];
|
const mcpServerSections = [];
|
||||||
for (const [name, config4] of Object.entries(mcpServers)) {
|
for (const [name, config4] of Object.entries(ctx.mcpServers)) {
|
||||||
if (config4.type !== "http") continue;
|
if (config4.type !== "http") continue;
|
||||||
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
|
log.info(`\xBB adding MCP server '${name}' at ${config4.url}`);
|
||||||
mcpServerSections.push(`[mcp_servers.${name}]
|
mcpServerSections.push(`[mcp_servers.${name}]
|
||||||
url = "${config4.url}"`);
|
url = "${config4.url}"`);
|
||||||
}
|
}
|
||||||
const features = [];
|
const features = [];
|
||||||
if (tools.bash !== "enabled") {
|
if (ctx.tools.bash !== "enabled") {
|
||||||
features.push("shell_command_tool = false");
|
features.push("shell_command_tool = false");
|
||||||
features.push("unified_exec = false");
|
features.push("unified_exec = false");
|
||||||
}
|
}
|
||||||
@@ -105159,7 +105168,7 @@ ${mcpServerSections.join("\n\n")}
|
|||||||
`.trim() + "\n"
|
`.trim() + "\n"
|
||||||
);
|
);
|
||||||
log.info(
|
log.info(
|
||||||
`\xBB Codex config written to ${configPath} (shell: ${tools.bash === "enabled" ? "enabled" : "disabled"})`
|
`\xBB Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
|
||||||
);
|
);
|
||||||
return codexDir;
|
return codexDir;
|
||||||
}
|
}
|
||||||
@@ -105172,41 +105181,37 @@ var codex = agent({
|
|||||||
executablePath: "bin/codex.js"
|
executablePath: "bin/codex.js"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join6(tempHome, ".config", "codex");
|
const configDir = join6(tempHome, ".config", "codex");
|
||||||
mkdirSync3(configDir, { recursive: true });
|
mkdirSync3(configDir, { recursive: true });
|
||||||
const codexDir = writeCodexConfig({
|
const codexDir = writeCodexConfig(ctx);
|
||||||
tempHome,
|
|
||||||
mcpServers,
|
|
||||||
tools
|
|
||||||
});
|
|
||||||
setupProcessAgentEnv({
|
setupProcessAgentEnv({
|
||||||
OPENAI_API_KEY: apiKey,
|
OPENAI_API_KEY: ctx.apiKey,
|
||||||
HOME: tempHome,
|
HOME: tempHome,
|
||||||
CODEX_HOME: codexDir
|
CODEX_HOME: codexDir
|
||||||
// point Codex to our config directory
|
// point Codex to our config directory
|
||||||
});
|
});
|
||||||
const model = codexModel[effort];
|
const model = codexModel[ctx.effort];
|
||||||
const modelReasoningEffort = codexReasoningEffort[effort];
|
const modelReasoningEffort = codexReasoningEffort[ctx.effort];
|
||||||
log.info(`Using model: ${model}`);
|
log.info(`Using model: ${model}`);
|
||||||
if (modelReasoningEffort) {
|
if (modelReasoningEffort) {
|
||||||
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
log.info(`Using modelReasoningEffort: ${modelReasoningEffort}`);
|
||||||
}
|
}
|
||||||
const codexOptions = {
|
const codexOptions = {
|
||||||
apiKey,
|
apiKey: ctx.apiKey,
|
||||||
codexPathOverride: cliPath
|
codexPathOverride: ctx.cliPath
|
||||||
};
|
};
|
||||||
const codex2 = new Codex(codexOptions);
|
const codex2 = new Codex(codexOptions);
|
||||||
const threadOptions = {
|
const threadOptions = {
|
||||||
model,
|
model,
|
||||||
approvalPolicy: "never",
|
approvalPolicy: "never",
|
||||||
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
||||||
sandboxMode: tools.write === "disabled" ? "read-only" : "danger-full-access",
|
sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
|
||||||
// web: controls network access
|
// web: controls network access
|
||||||
networkAccessEnabled: tools.web !== "disabled",
|
networkAccessEnabled: ctx.tools.web !== "disabled",
|
||||||
// search: controls web search
|
// search: controls web search
|
||||||
webSearchEnabled: tools.search !== "disabled",
|
webSearchEnabled: ctx.tools.search !== "disabled",
|
||||||
...modelReasoningEffort && { modelReasoningEffort }
|
...modelReasoningEffort && { modelReasoningEffort }
|
||||||
};
|
};
|
||||||
log.info(
|
log.info(
|
||||||
@@ -105214,7 +105219,7 @@ var codex = agent({
|
|||||||
);
|
);
|
||||||
const thread = codex2.startThread(threadOptions);
|
const thread = codex2.startThread(threadOptions);
|
||||||
try {
|
try {
|
||||||
const streamedTurn = await thread.runStreamed(addInstructions({ payload, repo, tools }));
|
const streamedTurn = await thread.runStreamed(addInstructions(ctx));
|
||||||
let finalOutput2 = "";
|
let finalOutput2 = "";
|
||||||
for await (const event of streamedTurn.events) {
|
for await (const event of streamedTurn.events) {
|
||||||
const handler2 = messageHandlers2[event.type];
|
const handler2 = messageHandlers2[event.type];
|
||||||
@@ -105341,9 +105346,9 @@ var cursor = agent({
|
|||||||
executableName: "cursor-agent"
|
executableName: "cursor-agent"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
configureCursorMcpServers({ mcpServers, cliPath });
|
configureCursorMcpServers(ctx);
|
||||||
configureCursorTools({ tools });
|
configureCursorTools(ctx);
|
||||||
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
|
const projectCliConfigPath = join7(process.cwd(), ".cursor", "cli.json");
|
||||||
let modelOverride = null;
|
let modelOverride = null;
|
||||||
if (existsSync4(projectCliConfigPath)) {
|
if (existsSync4(projectCliConfigPath)) {
|
||||||
@@ -105352,18 +105357,18 @@ var cursor = agent({
|
|||||||
if (projectConfig.model) {
|
if (projectConfig.model) {
|
||||||
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
log.info(`Using model from project .cursor/cli.json: ${projectConfig.model}`);
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[effort];
|
modelOverride = cursorEffortModels[ctx.effort];
|
||||||
}
|
}
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
log.info(`Using model: ${modelOverride} (effort: ${effort})`);
|
log.info(`Using model: ${modelOverride} (effort: ${ctx.effort})`);
|
||||||
} else if (!existsSync4(projectCliConfigPath)) {
|
} else if (!existsSync4(projectCliConfigPath)) {
|
||||||
log.info(`Using default model (effort: ${effort})`);
|
log.info(`Using default model (effort: ${ctx.effort})`);
|
||||||
}
|
}
|
||||||
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
||||||
const messageHandlers5 = {
|
const messageHandlers5 = {
|
||||||
@@ -105415,7 +105420,7 @@ var cursor = agent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const fullPrompt = addInstructions({ payload, repo, tools });
|
const fullPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(fullPrompt));
|
log.group("Full prompt", () => log.info(fullPrompt));
|
||||||
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
|
const baseArgs = ["--print", fullPrompt, "--output-format", "stream-json", "--approve-mcps"];
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
@@ -105425,10 +105430,10 @@ var cursor = agent({
|
|||||||
log.info("Running Cursor CLI...");
|
log.info("Running Cursor CLI...");
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
return new Promise((resolve2) => {
|
return new Promise((resolve2) => {
|
||||||
const child = spawn3(cliPath, cursorArgs, {
|
const child = spawn3(ctx.cliPath, cursorArgs, {
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
env: createAgentEnv({
|
env: createAgentEnv({
|
||||||
CURSOR_API_KEY: apiKey
|
CURSOR_API_KEY: ctx.apiKey
|
||||||
}),
|
}),
|
||||||
stdio: ["ignore", "pipe", "pipe"]
|
stdio: ["ignore", "pipe", "pipe"]
|
||||||
// Ignore stdin, pipe stdout/stderr
|
// Ignore stdin, pipe stdout/stderr
|
||||||
@@ -105503,13 +105508,13 @@ var cursor = agent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureCursorMcpServers({ mcpServers }) {
|
function configureCursorMcpServers(ctx) {
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const cursorConfigDir = join7(realHome, ".cursor");
|
const cursorConfigDir = join7(realHome, ".cursor");
|
||||||
const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
|
const mcpConfigPath = join7(cursorConfigDir, "mcp.json");
|
||||||
mkdirSync4(cursorConfigDir, { recursive: true });
|
mkdirSync4(cursorConfigDir, { recursive: true });
|
||||||
const cursorMcpServers = {};
|
const cursorMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
|
`Unsupported MCP server type for Cursor: ${serverConfig.type || "unknown"}`
|
||||||
@@ -105523,33 +105528,29 @@ function configureCursorMcpServers({ mcpServers }) {
|
|||||||
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
|
writeFileSync2(mcpConfigPath, JSON.stringify({ mcpServers: cursorMcpServers }, null, 2), "utf-8");
|
||||||
log.info(`\xBB MCP config written to ${mcpConfigPath}`);
|
log.info(`\xBB MCP config written to ${mcpConfigPath}`);
|
||||||
}
|
}
|
||||||
function configureCursorTools({ tools }) {
|
function configureCursorTools(ctx) {
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const cursorConfigDir = join7(realHome, ".config", "cursor");
|
const cursorConfigDir = join7(realHome, ".config", "cursor");
|
||||||
const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
|
const cliConfigPath = join7(cursorConfigDir, "cli-config.json");
|
||||||
mkdirSync4(cursorConfigDir, { recursive: true });
|
mkdirSync4(cursorConfigDir, { recursive: true });
|
||||||
const deny = [];
|
const deny = [];
|
||||||
if (tools.search === "disabled") deny.push("WebSearch");
|
if (ctx.tools.search === "disabled") deny.push("WebSearch");
|
||||||
if (tools.write === "disabled") deny.push("Write(**)");
|
if (ctx.tools.write === "disabled") deny.push("Write(**)");
|
||||||
if (tools.bash !== "enabled") deny.push("Shell(*)");
|
if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
|
||||||
const needsSandbox = tools.web === "disabled";
|
|
||||||
const config4 = {
|
const config4 = {
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
||||||
deny
|
deny
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (needsSandbox) {
|
if (ctx.tools.web === "disabled") {
|
||||||
config4.sandbox = {
|
config4.sandbox = {
|
||||||
mode: "enabled",
|
mode: "enabled",
|
||||||
networkAccess: "allowlist"
|
networkAccess: "allowlist"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
|
writeFileSync2(cliConfigPath, JSON.stringify(config4, null, 2), "utf-8");
|
||||||
log.info(`\xBB CLI config written to ${cliConfigPath}`);
|
log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config4, null, 2));
|
||||||
log.info(
|
|
||||||
`\u{1F527} Cursor permissions: allow=${config4.permissions.allow.join(",")}, deny=${deny.join(",") || "(none)"}, sandbox=${needsSandbox ? "enabled" : "disabled"}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/gemini.ts
|
// agents/gemini.ts
|
||||||
@@ -105725,14 +105726,12 @@ var gemini = agent({
|
|||||||
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
|
...githubInstallationToken2 && { githubInstallationToken: githubInstallationToken2 }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort, tools }) => {
|
run: async (ctx) => {
|
||||||
const { model, thinkingLevel } = geminiEffortConfig[effort];
|
const model = configureGeminiSettings(ctx);
|
||||||
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
if (!ctx.apiKey) {
|
||||||
configureGeminiSettings({ mcpServers, tools, thinkingLevel });
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||||
}
|
}
|
||||||
const sessionPrompt = addInstructions({ payload, repo, tools });
|
const sessionPrompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(sessionPrompt));
|
log.group("Full prompt", () => log.info(sessionPrompt));
|
||||||
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
const args3 = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||||
let finalOutput2 = "";
|
let finalOutput2 = "";
|
||||||
@@ -105740,8 +105739,8 @@ var gemini = agent({
|
|||||||
try {
|
try {
|
||||||
const result = await spawn4({
|
const result = await spawn4({
|
||||||
cmd: "node",
|
cmd: "node",
|
||||||
args: [cliPath, ...args3],
|
args: [ctx.cliPath, ...args3],
|
||||||
env: createAgentEnv({ GEMINI_API_KEY: apiKey }),
|
env: createAgentEnv({ GEMINI_API_KEY: ctx.apiKey }),
|
||||||
onStdout: async (chunk) => {
|
onStdout: async (chunk) => {
|
||||||
const text = chunk.toString();
|
const text = chunk.toString();
|
||||||
finalOutput2 += text;
|
finalOutput2 += text;
|
||||||
@@ -105798,11 +105797,9 @@ var gemini = agent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureGeminiSettings({
|
function configureGeminiSettings(ctx) {
|
||||||
mcpServers,
|
const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
|
||||||
tools,
|
log.info(`Using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
||||||
thinkingLevel
|
|
||||||
}) {
|
|
||||||
const realHome = homedir3();
|
const realHome = homedir3();
|
||||||
const geminiConfigDir = join8(realHome, ".gemini");
|
const geminiConfigDir = join8(realHome, ".gemini");
|
||||||
const settingsPath = join8(geminiConfigDir, "settings.json");
|
const settingsPath = join8(geminiConfigDir, "settings.json");
|
||||||
@@ -105814,7 +105811,7 @@ function configureGeminiSettings({
|
|||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
const geminiMcpServers = {};
|
const geminiMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
|
`Unsupported MCP server type for Gemini: ${serverConfig.type || "unknown"}`
|
||||||
@@ -105828,10 +105825,10 @@ function configureGeminiSettings({
|
|||||||
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
log.info(`adding MCP server '${serverName}' at ${serverConfig.url}...`);
|
||||||
}
|
}
|
||||||
const exclude = [];
|
const exclude = [];
|
||||||
if (tools.bash !== "enabled") exclude.push("run_shell_command");
|
if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
|
||||||
if (tools.write === "disabled") exclude.push("write_file");
|
if (ctx.tools.write === "disabled") exclude.push("write_file");
|
||||||
if (tools.web === "disabled") exclude.push("web_fetch");
|
if (ctx.tools.web === "disabled") exclude.push("web_fetch");
|
||||||
if (tools.search === "disabled") exclude.push("google_web_search");
|
if (ctx.tools.search === "disabled") exclude.push("google_web_search");
|
||||||
const newSettings = {
|
const newSettings = {
|
||||||
...existingSettings,
|
...existingSettings,
|
||||||
mcpServers: geminiMcpServers,
|
mcpServers: geminiMcpServers,
|
||||||
@@ -105852,6 +105849,7 @@ function configureGeminiSettings({
|
|||||||
if (exclude.length > 0) {
|
if (exclude.length > 0) {
|
||||||
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
|
log.info(`\u{1F512} excluded tools: ${exclude.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/opencode.ts
|
// agents/opencode.ts
|
||||||
@@ -105867,21 +105865,12 @@ var opencode = agent({
|
|||||||
installDependencies: true
|
installDependencies: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run: async ({
|
run: async (ctx) => {
|
||||||
payload,
|
|
||||||
apiKey: _apiKey,
|
|
||||||
apiKeys,
|
|
||||||
mcpServers,
|
|
||||||
cliPath,
|
|
||||||
repo,
|
|
||||||
effort: _effort,
|
|
||||||
tools
|
|
||||||
}) => {
|
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join9(tempHome, ".config", "opencode");
|
const configDir = join9(tempHome, ".config", "opencode");
|
||||||
mkdirSync6(configDir, { recursive: true });
|
mkdirSync6(configDir, { recursive: true });
|
||||||
configureOpenCode({ mcpServers, tools });
|
configureOpenCode(ctx);
|
||||||
const prompt = addInstructions({ payload, repo, tools });
|
const prompt = addInstructions(ctx);
|
||||||
log.group("Full prompt", () => log.info(prompt));
|
log.group("Full prompt", () => log.info(prompt));
|
||||||
const args3 = ["run", prompt, "--format", "json"];
|
const args3 = ["run", prompt, "--format", "json"];
|
||||||
setupProcessAgentEnv({ HOME: tempHome });
|
setupProcessAgentEnv({ HOME: tempHome });
|
||||||
@@ -105890,14 +105879,14 @@ var opencode = agent({
|
|||||||
XDG_CONFIG_HOME: join9(tempHome, ".config")
|
XDG_CONFIG_HOME: join9(tempHome, ".config")
|
||||||
};
|
};
|
||||||
delete env3.GITHUB_TOKEN;
|
delete env3.GITHUB_TOKEN;
|
||||||
for (const [key, value2] of Object.entries(apiKeys || {})) {
|
for (const [key, value2] of Object.entries(ctx.apiKeys || {})) {
|
||||||
env3[key.toUpperCase()] = value2;
|
env3[key.toUpperCase()] = value2;
|
||||||
if (key === "GEMINI_API_KEY") {
|
if (key === "GEMINI_API_KEY") {
|
||||||
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
|
env3.GOOGLE_GENERATIVE_AI_API_KEY = value2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const repoDir = process.cwd();
|
const repoDir = process.cwd();
|
||||||
log.info(`\u{1F680} Starting OpenCode CLI: ${cliPath} ${args3.join(" ")}`);
|
log.info(`\u{1F680} Starting OpenCode CLI: ${ctx.cliPath} ${args3.join(" ")}`);
|
||||||
log.info(`\u{1F4C1} Working directory: ${repoDir}`);
|
log.info(`\u{1F4C1} Working directory: ${repoDir}`);
|
||||||
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
|
log.debug(`\u{1F3E0} HOME: ${env3.HOME}`);
|
||||||
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
|
log.debug(`\u{1F4CB} XDG_CONFIG_HOME: ${env3.XDG_CONFIG_HOME}`);
|
||||||
@@ -105907,7 +105896,7 @@ var opencode = agent({
|
|||||||
let output = "";
|
let output = "";
|
||||||
let stdoutBuffer = "";
|
let stdoutBuffer = "";
|
||||||
const result = await spawn4({
|
const result = await spawn4({
|
||||||
cmd: cliPath,
|
cmd: ctx.cliPath,
|
||||||
args: args3,
|
args: args3,
|
||||||
cwd: repoDir,
|
cwd: repoDir,
|
||||||
env: env3,
|
env: env3,
|
||||||
@@ -105993,13 +105982,13 @@ var opencode = agent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
function configureOpenCode({ mcpServers, tools }) {
|
function configureOpenCode(ctx) {
|
||||||
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
const tempHome = process.env.PULLFROG_TEMP_DIR;
|
||||||
const configDir = join9(tempHome, ".config", "opencode");
|
const configDir = join9(tempHome, ".config", "opencode");
|
||||||
mkdirSync6(configDir, { recursive: true });
|
mkdirSync6(configDir, { recursive: true });
|
||||||
const configPath = join9(configDir, "opencode.json");
|
const configPath = join9(configDir, "opencode.json");
|
||||||
const opencodeMcpServers = {};
|
const opencodeMcpServers = {};
|
||||||
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
for (const [serverName, serverConfig] of Object.entries(ctx.mcpServers)) {
|
||||||
if (serverConfig.type !== "http") {
|
if (serverConfig.type !== "http") {
|
||||||
log.error(
|
log.error(
|
||||||
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
|
`unsupported MCP server type for OpenCode: ${serverConfig.type || "unknown"}`
|
||||||
@@ -106014,9 +106003,9 @@ function configureOpenCode({ mcpServers, tools }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const permission = {
|
const permission = {
|
||||||
edit: tools.write === "disabled" ? "deny" : "allow",
|
edit: ctx.tools.write === "disabled" ? "deny" : "allow",
|
||||||
bash: tools.bash !== "enabled" ? "deny" : "allow",
|
bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
|
||||||
webfetch: tools.web === "disabled" ? "deny" : "allow",
|
webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
|
||||||
doom_loop: "allow",
|
doom_loop: "allow",
|
||||||
external_directory: "allow"
|
external_directory: "allow"
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-266
@@ -1,275 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* CLI output utilities that work well in both local and GitHub Actions environments
|
* CLI utilities
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import * as core from "@actions/core";
|
|
||||||
import { table } from "table";
|
|
||||||
|
|
||||||
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
// re-export logging utilities for backward compatibility
|
||||||
|
export { formatIndentedField, formatJsonValue, log, writeSummary } from "./log.ts";
|
||||||
const isDebugEnabled = () =>
|
|
||||||
process.env.LOG_LEVEL === "debug" ||
|
|
||||||
process.env.ACTIONS_STEP_DEBUG === "true" ||
|
|
||||||
process.env.RUNNER_DEBUG === "1" ||
|
|
||||||
core.isDebug();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a collapsed group (GitHub Actions) or regular group (local)
|
|
||||||
*/
|
|
||||||
function startGroup(name: string): void {
|
|
||||||
if (isGitHubActions) {
|
|
||||||
core.startGroup(name);
|
|
||||||
} else {
|
|
||||||
console.group(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End a collapsed group
|
|
||||||
*/
|
|
||||||
function endGroup(): void {
|
|
||||||
if (isGitHubActions) {
|
|
||||||
core.endGroup();
|
|
||||||
} else {
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a callback within a collapsed group
|
|
||||||
*/
|
|
||||||
function group(name: string, fn: () => void): void {
|
|
||||||
startGroup(name);
|
|
||||||
fn();
|
|
||||||
endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a formatted box with text (for console output)
|
|
||||||
*/
|
|
||||||
function boxString(
|
|
||||||
text: string,
|
|
||||||
options?: {
|
|
||||||
title?: string;
|
|
||||||
maxWidth?: number;
|
|
||||||
indent?: string;
|
|
||||||
padding?: number;
|
|
||||||
}
|
|
||||||
): string {
|
|
||||||
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
|
|
||||||
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
const wrappedLines: string[] = [];
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.length <= maxWidth - padding * 2) {
|
|
||||||
wrappedLines.push(line);
|
|
||||||
} else {
|
|
||||||
const words = line.split(" ");
|
|
||||||
let currentLine = "";
|
|
||||||
|
|
||||||
for (const word of words) {
|
|
||||||
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
||||||
if (testLine.length <= maxWidth - padding * 2) {
|
|
||||||
currentLine = testLine;
|
|
||||||
} else {
|
|
||||||
if (currentLine) {
|
|
||||||
wrappedLines.push(currentLine);
|
|
||||||
currentLine = "";
|
|
||||||
}
|
|
||||||
// wrap long words by breaking them into chunks
|
|
||||||
const maxLineLength = maxWidth - padding * 2;
|
|
||||||
let remainingWord = word;
|
|
||||||
while (remainingWord.length > maxLineLength) {
|
|
||||||
wrappedLines.push(remainingWord.substring(0, maxLineLength));
|
|
||||||
remainingWord = remainingWord.substring(maxLineLength);
|
|
||||||
}
|
|
||||||
currentLine = remainingWord;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentLine) {
|
|
||||||
wrappedLines.push(currentLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
|
|
||||||
const contentBoxWidth = maxLineLength + padding * 2;
|
|
||||||
|
|
||||||
// ensure box width is at least as wide as the title line when title exists
|
|
||||||
const titleLineLength = title ? ` ${title} `.length : 0;
|
|
||||||
const boxWidth = Math.max(contentBoxWidth, titleLineLength);
|
|
||||||
|
|
||||||
let result = "";
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
const titleLine = ` ${title} `;
|
|
||||||
const titlePadding = Math.max(0, boxWidth - titleLine.length);
|
|
||||||
result += `${indent}┌${titleLine}${"─".repeat(titlePadding)}┐\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
result += `${indent}┌${"─".repeat(boxWidth)}┐\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const line of wrappedLines) {
|
|
||||||
const paddedLine = line.padEnd(maxLineLength);
|
|
||||||
result += `${indent}│${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}│\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += `${indent}└${"─".repeat(boxWidth)}┘`;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a formatted box with text
|
|
||||||
*/
|
|
||||||
function box(
|
|
||||||
text: string,
|
|
||||||
options?: {
|
|
||||||
title?: string;
|
|
||||||
maxWidth?: number;
|
|
||||||
}
|
|
||||||
): void {
|
|
||||||
const boxContent = boxString(text, options);
|
|
||||||
core.info(boxContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrite the job summary with the given text.
|
|
||||||
*/
|
|
||||||
export function writeSummary(text: string): void {
|
|
||||||
if (!isGitHubActions) return;
|
|
||||||
core.summary.addRaw(text).write({ overwrite: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a formatted table using the table package
|
|
||||||
*/
|
|
||||||
function printTable(
|
|
||||||
rows: Array<Array<{ data: string; header?: boolean } | string>>,
|
|
||||||
options?: {
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
): void {
|
|
||||||
const { title } = options || {};
|
|
||||||
|
|
||||||
// Convert rows to string arrays for the table package
|
|
||||||
const tableData = rows.map((row) =>
|
|
||||||
row.map((cell) => {
|
|
||||||
if (typeof cell === "string") {
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
return cell.data;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatted = table(tableData);
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
core.info(`\n${title}`);
|
|
||||||
}
|
|
||||||
core.info(`\n${formatted}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a separator line
|
|
||||||
*/
|
|
||||||
function separator(length: number = 50): void {
|
|
||||||
const separatorText = "─".repeat(length);
|
|
||||||
core.info(separatorText);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main logging utility object - import this once and access all utilities
|
|
||||||
*/
|
|
||||||
export const log = {
|
|
||||||
/** Print info message */
|
|
||||||
info: (message: string): void => {
|
|
||||||
core.info(message);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Print warning message */
|
|
||||||
warning: (message: string): void => {
|
|
||||||
core.warning(message);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Print error message */
|
|
||||||
error: (message: string): void => {
|
|
||||||
core.error(message);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Print success message */
|
|
||||||
success: (message: string): void => {
|
|
||||||
core.info(`✅ ${message}`);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Print debug message (only if LOG_LEVEL=debug) */
|
|
||||||
debug: (message: string | unknown): void => {
|
|
||||||
if (isDebugEnabled()) {
|
|
||||||
core.info(`[DEBUG] ${message}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Print a formatted box with text */
|
|
||||||
box,
|
|
||||||
|
|
||||||
/** Print a formatted table using the table package */
|
|
||||||
table: printTable,
|
|
||||||
|
|
||||||
/** Print a separator line */
|
|
||||||
separator,
|
|
||||||
|
|
||||||
/** Start a collapsed group (GitHub Actions) or regular group (local) */
|
|
||||||
startGroup,
|
|
||||||
|
|
||||||
/** End a collapsed group */
|
|
||||||
endGroup,
|
|
||||||
|
|
||||||
/** Run a callback within a collapsed group */
|
|
||||||
group,
|
|
||||||
|
|
||||||
/** 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}`;
|
|
||||||
|
|
||||||
log.info(output.trimEnd());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a value as JSON, using compact format for simple values and pretty-printed for complex ones
|
|
||||||
*/
|
|
||||||
export function formatJsonValue(value: unknown): string {
|
|
||||||
const compact = JSON.stringify(value);
|
|
||||||
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value, null, 2) : compact;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a multi-line string with proper indentation for tool call output
|
|
||||||
* First line has the label, subsequent lines are indented 4 spaces
|
|
||||||
*/
|
|
||||||
export function formatIndentedField(label: string, content: string): string {
|
|
||||||
if (!content.includes("\n")) {
|
|
||||||
return ` ${label}: ${content}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = content.split("\n");
|
|
||||||
let formatted = ` ${label}: ${lines[0]}\n`;
|
|
||||||
for (let i = 1; i < lines.length; i++) {
|
|
||||||
formatted += ` ${lines[i]}\n`;
|
|
||||||
}
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a CLI executable path by checking if it's installed globally
|
* Finds a CLI executable path by checking if it's installed globally
|
||||||
|
|||||||
+283
@@ -0,0 +1,283 @@
|
|||||||
|
/**
|
||||||
|
* Logging utilities that work well in both local and GitHub Actions environments
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as core from "@actions/core";
|
||||||
|
import { table } from "table";
|
||||||
|
|
||||||
|
const isGitHubActions = !!process.env.GITHUB_ACTIONS;
|
||||||
|
|
||||||
|
const isDebugEnabled = () =>
|
||||||
|
process.env.LOG_LEVEL === "debug" ||
|
||||||
|
process.env.ACTIONS_STEP_DEBUG === "true" ||
|
||||||
|
process.env.RUNNER_DEBUG === "1" ||
|
||||||
|
core.isDebug();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format arguments into a single string for logging
|
||||||
|
*/
|
||||||
|
function formatArgs(args: unknown[]): string {
|
||||||
|
return args
|
||||||
|
.map((arg) => {
|
||||||
|
if (typeof arg === "string") return arg;
|
||||||
|
if (arg instanceof Error) return `${arg.message}\n${arg.stack}`;
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
})
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a collapsed group (GitHub Actions) or regular group (local)
|
||||||
|
*/
|
||||||
|
function startGroup(name: string): void {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.startGroup(name);
|
||||||
|
} else {
|
||||||
|
console.group(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End a collapsed group
|
||||||
|
*/
|
||||||
|
function endGroup(): void {
|
||||||
|
if (isGitHubActions) {
|
||||||
|
core.endGroup();
|
||||||
|
} else {
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a callback within a collapsed group
|
||||||
|
*/
|
||||||
|
function group(name: string, fn: () => void): void {
|
||||||
|
startGroup(name);
|
||||||
|
fn();
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted box with text (for console output)
|
||||||
|
*/
|
||||||
|
function boxString(
|
||||||
|
text: string,
|
||||||
|
options?: {
|
||||||
|
title?: string;
|
||||||
|
maxWidth?: number;
|
||||||
|
indent?: string;
|
||||||
|
padding?: number;
|
||||||
|
}
|
||||||
|
): string {
|
||||||
|
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
|
||||||
|
|
||||||
|
const lines = text.trim().split("\n");
|
||||||
|
const wrappedLines: string[] = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.length <= maxWidth - padding * 2) {
|
||||||
|
wrappedLines.push(line);
|
||||||
|
} else {
|
||||||
|
const words = line.split(" ");
|
||||||
|
let currentLine = "";
|
||||||
|
|
||||||
|
for (const word of words) {
|
||||||
|
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
||||||
|
if (testLine.length <= maxWidth - padding * 2) {
|
||||||
|
currentLine = testLine;
|
||||||
|
} else {
|
||||||
|
if (currentLine) {
|
||||||
|
wrappedLines.push(currentLine);
|
||||||
|
currentLine = "";
|
||||||
|
}
|
||||||
|
// wrap long words by breaking them into chunks
|
||||||
|
const maxLineLength = maxWidth - padding * 2;
|
||||||
|
let remainingWord = word;
|
||||||
|
while (remainingWord.length > maxLineLength) {
|
||||||
|
wrappedLines.push(remainingWord.substring(0, maxLineLength));
|
||||||
|
remainingWord = remainingWord.substring(maxLineLength);
|
||||||
|
}
|
||||||
|
currentLine = remainingWord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLine) {
|
||||||
|
wrappedLines.push(currentLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
|
||||||
|
const contentBoxWidth = maxLineLength + padding * 2;
|
||||||
|
|
||||||
|
// ensure box width is at least as wide as the title line when title exists
|
||||||
|
const titleLineLength = title ? ` ${title} `.length : 0;
|
||||||
|
const boxWidth = Math.max(contentBoxWidth, titleLineLength);
|
||||||
|
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
const titleLine = ` ${title} `;
|
||||||
|
const titlePadding = Math.max(0, boxWidth - titleLine.length);
|
||||||
|
result += `${indent}┌${titleLine}${"─".repeat(titlePadding)}┐\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
result += `${indent}┌${"─".repeat(boxWidth)}┐\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of wrappedLines) {
|
||||||
|
const paddedLine = line.padEnd(maxLineLength);
|
||||||
|
result += `${indent}│${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}│\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += `${indent}└${"─".repeat(boxWidth)}┘`;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted box with text
|
||||||
|
*/
|
||||||
|
function box(
|
||||||
|
text: string,
|
||||||
|
options?: {
|
||||||
|
title?: string;
|
||||||
|
maxWidth?: number;
|
||||||
|
}
|
||||||
|
): void {
|
||||||
|
const boxContent = boxString(text, options);
|
||||||
|
core.info(boxContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite the job summary with the given text.
|
||||||
|
*/
|
||||||
|
export function writeSummary(text: string): void {
|
||||||
|
if (!isGitHubActions) return;
|
||||||
|
core.summary.addRaw(text).write({ overwrite: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted table using the table package
|
||||||
|
*/
|
||||||
|
function printTable(
|
||||||
|
rows: Array<Array<{ data: string; header?: boolean } | string>>,
|
||||||
|
options?: {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
): void {
|
||||||
|
const { title } = options || {};
|
||||||
|
|
||||||
|
// Convert rows to string arrays for the table package
|
||||||
|
const tableData = rows.map((row) =>
|
||||||
|
row.map((cell) => {
|
||||||
|
if (typeof cell === "string") {
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
return cell.data;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatted = table(tableData);
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
core.info(`\n${title}`);
|
||||||
|
}
|
||||||
|
core.info(`\n${formatted}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a separator line
|
||||||
|
*/
|
||||||
|
function separator(length: number = 50): void {
|
||||||
|
const separatorText = "─".repeat(length);
|
||||||
|
core.info(separatorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main logging utility object - import this once and access all utilities
|
||||||
|
*/
|
||||||
|
export const log = {
|
||||||
|
/** Print info message */
|
||||||
|
info: (...args: unknown[]): void => {
|
||||||
|
core.info(formatArgs(args));
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Print warning message */
|
||||||
|
warning: (...args: unknown[]): void => {
|
||||||
|
core.warning(formatArgs(args));
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Print error message */
|
||||||
|
error: (...args: unknown[]): void => {
|
||||||
|
core.error(formatArgs(args));
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Print success message */
|
||||||
|
success: (...args: unknown[]): void => {
|
||||||
|
core.info(`✅ ${formatArgs(args)}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Print debug message (only if LOG_LEVEL=debug) */
|
||||||
|
debug: (...args: unknown[]): void => {
|
||||||
|
if (isDebugEnabled()) {
|
||||||
|
core.info(`[DEBUG] ${formatArgs(args)}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Print a formatted box with text */
|
||||||
|
box,
|
||||||
|
|
||||||
|
/** Print a formatted table using the table package */
|
||||||
|
table: printTable,
|
||||||
|
|
||||||
|
/** Print a separator line */
|
||||||
|
separator,
|
||||||
|
|
||||||
|
/** Start a collapsed group (GitHub Actions) or regular group (local) */
|
||||||
|
startGroup,
|
||||||
|
|
||||||
|
/** End a collapsed group */
|
||||||
|
endGroup,
|
||||||
|
|
||||||
|
/** Run a callback within a collapsed group */
|
||||||
|
group,
|
||||||
|
|
||||||
|
/** 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}`;
|
||||||
|
|
||||||
|
log.info(output.trimEnd());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a value as JSON, using compact format for simple values and pretty-printed for complex ones
|
||||||
|
*/
|
||||||
|
export function formatJsonValue(value: unknown): string {
|
||||||
|
const compact = JSON.stringify(value);
|
||||||
|
return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value, null, 2) : compact;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a multi-line string with proper indentation for tool call output
|
||||||
|
* First line has the label, subsequent lines are indented 4 spaces
|
||||||
|
*/
|
||||||
|
export function formatIndentedField(label: string, content: string): string {
|
||||||
|
if (!content.includes("\n")) {
|
||||||
|
return ` ${label}: ${content}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = content.split("\n");
|
||||||
|
let formatted = ` ${label}: ${lines[0]}\n`;
|
||||||
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
formatted += ` ${lines[i]}\n`;
|
||||||
|
}
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user