merge main
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { claude } from "./claude.ts";
|
||||
import { codex } from "./codex.ts";
|
||||
import { cursor } from "./cursor.ts";
|
||||
import { jules } from "./jules.ts";
|
||||
|
||||
export const agents = {
|
||||
claude,
|
||||
codex,
|
||||
cursor,
|
||||
jules,
|
||||
} as const;
|
||||
|
||||
export type AgentInputKey = (typeof agents)[keyof typeof agents]["inputKey"];
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { parseRepoContext } from "../utils/github.ts";
|
||||
import { spawn } from "../utils/subprocess.ts";
|
||||
import { addInstructions } from "./instructions.ts";
|
||||
import { agent, installFromNpmTarball } from "./shared.ts";
|
||||
|
||||
export const jules = agent({
|
||||
name: "jules",
|
||||
inputKey: "google_api_key",
|
||||
install: async () => {
|
||||
return await installFromNpmTarball({
|
||||
packageName: "@google/jules",
|
||||
version: "latest",
|
||||
executablePath: "run.cjs",
|
||||
});
|
||||
},
|
||||
run: async ({
|
||||
prompt,
|
||||
apiKey,
|
||||
mcpServers: _mcpServers,
|
||||
githubInstallationToken: _githubInstallationToken,
|
||||
cliPath,
|
||||
}) => {
|
||||
if (!apiKey) {
|
||||
throw new Error("google_api_key is required for jules agent");
|
||||
}
|
||||
|
||||
const repoContext = parseRepoContext();
|
||||
const repoName = `${repoContext.owner}/${repoContext.name}`;
|
||||
|
||||
log.info(`Creating Jules session for ${repoName}...`);
|
||||
|
||||
// Set API key as environment variable for CLI authentication
|
||||
// Note: The CLI may require browser-based auth via 'jules login' in interactive mode
|
||||
// In CI, we rely on the API key being set as an environment variable
|
||||
const env: Record<string, string> = {
|
||||
GOOGLE_API_KEY: apiKey,
|
||||
JULES_API_KEY: apiKey,
|
||||
};
|
||||
// Copy over existing env vars, filtering out undefined values
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value !== undefined) {
|
||||
env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new remote session
|
||||
const sessionPrompt = addInstructions(prompt);
|
||||
log.info(`Starting session with prompt: ${prompt.substring(0, 100)}...`);
|
||||
|
||||
let sessionId: string | undefined;
|
||||
try {
|
||||
const createResult = await spawn({
|
||||
cmd: "node",
|
||||
args: [cliPath, "remote", "new", "--repo", repoName, "--session", sessionPrompt],
|
||||
env,
|
||||
onStdout: (chunk) => {
|
||||
log.info(chunk.trim());
|
||||
// Try to extract session ID from output
|
||||
const match = chunk.match(/session[:\s]+(\d+)/i) || chunk.match(/id[:\s]+(\d+)/i);
|
||||
if (match && !sessionId) {
|
||||
sessionId = match[1];
|
||||
log.info(`✓ Session ID: ${sessionId}`);
|
||||
}
|
||||
},
|
||||
onStderr: (chunk) => {
|
||||
log.warning(chunk.trim());
|
||||
},
|
||||
});
|
||||
|
||||
if (createResult.exitCode !== 0) {
|
||||
throw new Error(
|
||||
`Failed to create Jules session: ${createResult.stderr || createResult.stdout || "Unknown error"}`
|
||||
);
|
||||
}
|
||||
|
||||
// If session ID wasn't extracted from stdout, try to parse it
|
||||
if (!sessionId) {
|
||||
const output = createResult.stdout + createResult.stderr;
|
||||
const match = output.match(/session[:\s]+(\d+)/i) || output.match(/id[:\s]+(\d+)/i);
|
||||
if (match) {
|
||||
sessionId = match[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
log.warning("Could not extract session ID from output. Session may have been created.");
|
||||
log.info(`Output: ${createResult.stdout}`);
|
||||
} else {
|
||||
log.info(`✓ Session created: ${sessionId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
log.error(`Failed to create Jules session: ${errorMessage}`);
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
output: "",
|
||||
};
|
||||
}
|
||||
|
||||
// Monitor session progress by polling session list
|
||||
log.info("Monitoring session progress...");
|
||||
let finalOutput = "";
|
||||
const maxPollAttempts = 300; // ~50 minutes max (10 second intervals)
|
||||
let pollAttempts = 0;
|
||||
|
||||
while (pollAttempts < maxPollAttempts) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000)); // Wait 10 seconds between polls
|
||||
pollAttempts++;
|
||||
|
||||
try {
|
||||
// List sessions to check status
|
||||
const listResult = await spawn({
|
||||
cmd: "node",
|
||||
args: [cliPath, "remote", "list", "--session"],
|
||||
env,
|
||||
onStdout: (chunk) => {
|
||||
// Log session updates
|
||||
const trimmed = chunk.trim();
|
||||
if (trimmed) {
|
||||
log.info(trimmed);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (listResult.exitCode === 0) {
|
||||
const output = listResult.stdout;
|
||||
// Check if our session is complete
|
||||
// The CLI output format may vary, so we look for completion indicators
|
||||
if (sessionId && output.includes(sessionId)) {
|
||||
// Try to determine if session is complete
|
||||
// This is a heuristic - the actual output format may differ
|
||||
if (
|
||||
output.includes("completed") ||
|
||||
output.includes("done") ||
|
||||
output.includes("finished")
|
||||
) {
|
||||
log.info("Session appears to be completed");
|
||||
finalOutput = "Session completed. Pulling results...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
log.warning(`Error checking session status: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Pull results if session ID is available
|
||||
if (sessionId) {
|
||||
try {
|
||||
log.info(`Pulling results for session ${sessionId}...`);
|
||||
const pullResult = await spawn({
|
||||
cmd: "node",
|
||||
args: [cliPath, "remote", "pull", "--session", sessionId],
|
||||
env,
|
||||
onStdout: (chunk) => {
|
||||
log.info(chunk.trim());
|
||||
},
|
||||
onStderr: (chunk) => {
|
||||
log.warning(chunk.trim());
|
||||
},
|
||||
});
|
||||
|
||||
if (pullResult.exitCode === 0) {
|
||||
finalOutput = pullResult.stdout || "Results pulled successfully.";
|
||||
} else {
|
||||
log.warning(`Failed to pull results: ${pullResult.stderr || pullResult.stdout}`);
|
||||
finalOutput = finalOutput || "Session completed. Check Jules dashboard for results.";
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
log.warning(`Error pulling results: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (pollAttempts >= maxPollAttempts) {
|
||||
log.warning("Session monitoring timeout reached. Session may still be in progress.");
|
||||
finalOutput =
|
||||
finalOutput || "Session monitoring timeout. Check Jules dashboard for session status.";
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: finalOutput || "Jules session completed. Check the Jules dashboard for results.",
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import { flatMorph } from "@ark/util";
|
||||
import { agents } from "./agents/index.ts";
|
||||
import { AgentName, type Inputs, main } from "./main.ts";
|
||||
import { log } from "./utils/cli.ts";
|
||||
|
||||
@@ -20,9 +22,8 @@ async function run(): Promise<void> {
|
||||
try {
|
||||
const inputs: Required<Inputs> = {
|
||||
prompt: core.getInput("prompt", { required: true }),
|
||||
anthropic_api_key: core.getInput("anthropic_api_key"),
|
||||
openai_api_key: core.getInput("openai_api_key"),
|
||||
agent: core.getInput("agent") ? AgentName.assert(core.getInput("agent")) : undefined,
|
||||
...flatMorph(agents, (_, agent) => [agent.inputKey, core.getInput(agent.inputKey)]),
|
||||
};
|
||||
|
||||
const result = await main(inputs);
|
||||
|
||||
@@ -2,8 +2,10 @@ import { existsSync, readFileSync } from "node:fs";
|
||||
import { extname, join, resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { fromHere } from "@ark/fs";
|
||||
import { flatMorph } from "@ark/util";
|
||||
import arg from "arg";
|
||||
import { config } from "dotenv";
|
||||
import { agents } from "./agents/index.ts";
|
||||
import { type Inputs, main } from "./main.ts";
|
||||
import { log } from "./utils/cli.ts";
|
||||
import { setupTestRepo } from "./utils/setup.ts";
|
||||
@@ -22,9 +24,11 @@ export async function run(
|
||||
|
||||
const inputs: Required<Inputs> = {
|
||||
prompt,
|
||||
openai_api_key: process.env.OPENAI_API_KEY,
|
||||
anthropic_api_key: process.env.ANTHROPIC_API_KEY,
|
||||
agent: "cursor",
|
||||
agent: "codex",
|
||||
...flatMorph(agents, (_, agent) => [
|
||||
agent.inputKey,
|
||||
process.env[agent.inputKey.toUpperCase()],
|
||||
]),
|
||||
};
|
||||
|
||||
const result = await main(inputs);
|
||||
|
||||
Reference in New Issue
Block a user