@jumboo/create-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Pluggable solver drivers. A driver implements:
3
+ *
4
+ * solve({ repoDir, task, issue }) → Promise<{ summary: string }>
5
+ *
6
+ * The driver mutates the cloned working copy; the job pipeline commits whatever
7
+ * changed. `summary` becomes the top of the PR body.
8
+ *
9
+ * Drivers that ship here:
10
+ * - "echo" — no AI; writes SOLUTION.md to test the pipeline.
11
+ * - CLI agents — spawn a headless AI coding agent inside the repo, which reads
12
+ * the issue and edits files on its own. Presets: claude / codex / opencode /
13
+ * antigravity. Flags are overridable via env, so if a tool changes its CLI
14
+ * you just tweak SOLVER_ARGS — no code edit.
15
+ * - "custom" — write your own driver (see the stub at the bottom).
16
+ *
17
+ * Env overrides for the CLI presets (all optional):
18
+ * SOLVER_COMMAND the executable to run (e.g. a custom install path)
19
+ * SOLVER_ARGS space-separated args (replaces the preset's args)
20
+ * SOLVER_PROMPT_VIA "stdin" (default) or "arg" — how the prompt is passed
21
+ */
22
+ import { spawn } from "node:child_process";
23
+ import { writeFile } from "node:fs/promises";
24
+ import path from "node:path";
25
+ import { config } from "./config.js";
26
+
27
+ const SUMMARY_MAX = 4000;
28
+
29
+ function issueText(issue) {
30
+ if (issue.title || issue.body) {
31
+ return [issue.title, issue.body].filter(Boolean).join("\n\n");
32
+ }
33
+ return issue.ref || "unknown issue";
34
+ }
35
+
36
+ function buildPrompt(issue) {
37
+ return (
38
+ "You are an autonomous coding agent competing for a bounty. " +
39
+ "Fix the following GitHub issue in this repository. " +
40
+ `Make the smallest correct change. Issue: ${issueText(issue)}`
41
+ );
42
+ }
43
+
44
+ /**
45
+ * "echo" driver — no AI. Writes SOLUTION.md documenting the task so the whole
46
+ * pipeline can be exercised end-to-end. For testing only; it never fixes anything.
47
+ */
48
+ const echoDriver = {
49
+ name: "echo",
50
+ async solve({ repoDir, task, issue }) {
51
+ const content = [
52
+ "# Jumboo echo-driver solution",
53
+ "",
54
+ `- Task ID: ${task.taskId}`,
55
+ `- Repo: ${task.repoUrl}`,
56
+ `- Issue: ${issueText(issue)}`,
57
+ "",
58
+ "Produced by the `echo` solver driver to test the pipeline. It does not",
59
+ "solve the issue — pick a real solver (claude, codex, opencode, …) to compete.",
60
+ "",
61
+ ].join("\n");
62
+ await writeFile(path.join(repoDir, "SOLUTION.md"), content, "utf8");
63
+ return { summary: `Echo driver: wrote SOLUTION.md for task ${task.taskId} (pipeline test, no real fix).` };
64
+ },
65
+ };
66
+
67
+ // Placeholder for where the prompt goes in a preset's args. If present it is
68
+ // substituted; otherwise the prompt is appended (promptVia "arg") or piped on
69
+ // stdin (promptVia "stdin").
70
+ const PROMPT = "{prompt}";
71
+
72
+ /**
73
+ * Default invocation for each known AI coding-agent CLI. Every preset runs the
74
+ * agent FULLY AUTONOMOUSLY — it skips the tool's permission/approval prompts so
75
+ * it can work unattended. Only run these on a machine you control (a sandbox or
76
+ * VPS). Flags are best-effort defaults — override with SOLVER_ARGS if your
77
+ * version of a tool expects different ones.
78
+ */
79
+ export const CLI_PRESETS = {
80
+ claude: { command: "claude", args: ["-p", "--dangerously-skip-permissions"], promptVia: "stdin" },
81
+ codex: { command: "codex", args: ["exec", "--dangerously-bypass-approvals-and-sandbox"], promptVia: "arg" },
82
+ opencode: { command: "opencode", args: ["run", "--auto"], promptVia: "arg" },
83
+ // Antigravity's binary is `agy`; the prompt is the value of -p.
84
+ antigravity: { command: "agy", args: ["-p", PROMPT, "--dangerously-skip-permissions"], promptVia: "arg" },
85
+ };
86
+
87
+ /**
88
+ * Work out the final argv and whether the prompt goes on stdin:
89
+ * - PROMPT placeholder present → substitute it in place
90
+ * - promptVia "arg" → append the prompt as the last argument
91
+ * - otherwise → pipe the prompt on stdin
92
+ */
93
+ export function buildInvocation({ args, promptVia, prompt }) {
94
+ const usesPlaceholder = args.includes(PROMPT);
95
+ const finalArgs = usesPlaceholder
96
+ ? args.map((a) => (a === PROMPT ? prompt : a))
97
+ : promptVia === "arg"
98
+ ? [...args, prompt]
99
+ : [...args];
100
+ const viaStdin = !usesPlaceholder && promptVia !== "arg";
101
+ return { finalArgs, viaStdin };
102
+ }
103
+
104
+ /** Spawn a headless CLI agent in the repo and return its stdout as the summary. */
105
+ function runCliAgent({ command, args, promptVia, prompt, repoDir }) {
106
+ return new Promise((resolve, reject) => {
107
+ const { finalArgs, viaStdin } = buildInvocation({ args, promptVia, prompt });
108
+ const child = spawn(command, finalArgs, {
109
+ cwd: repoDir,
110
+ shell: process.platform === "win32", // resolve .cmd shims on Windows
111
+ windowsHide: true,
112
+ });
113
+ let out = "";
114
+ let err = "";
115
+ child.stdout.on("data", (d) => (out += d));
116
+ child.stderr.on("data", (d) => (err += d));
117
+ child.on("error", (e) => reject(new Error(`failed to spawn ${command}: ${e.message}`)));
118
+ child.on("close", (code) => {
119
+ if (code !== 0) {
120
+ return reject(new Error(`${command} exited ${code}: ${(err || out).trim().slice(0, 1000)}`));
121
+ }
122
+ resolve(out.trim().slice(0, SUMMARY_MAX) || `${command} produced no summary output.`);
123
+ });
124
+ if (viaStdin) {
125
+ child.stdin.write(prompt);
126
+ child.stdin.end();
127
+ }
128
+ });
129
+ }
130
+
131
+ /** Build a CLI-agent driver from a preset (with env overrides applied). */
132
+ function cliDriver(name, preset) {
133
+ return {
134
+ name,
135
+ async solve({ repoDir, issue }) {
136
+ const command = config.solverCommand || preset.command;
137
+ const args = config.solverArgs ?? preset.args;
138
+ const promptVia = config.solverPromptVia || preset.promptVia;
139
+ const summary = await runCliAgent({ command, args, promptVia, prompt: buildPrompt(issue), repoDir });
140
+ return { summary };
141
+ },
142
+ };
143
+ }
144
+
145
+ /**
146
+ * "custom" driver — WRITE YOUR OWN.
147
+ *
148
+ * Use this when you don't want one of the CLI agents above. Read the issue,
149
+ * make changes inside `repoDir` any way you like (call your own model/service,
150
+ * run a script, apply a patch…), then return a short summary for the PR body.
151
+ * The job pipeline commits whatever files changed.
152
+ */
153
+ const customDriver = {
154
+ name: "custom",
155
+ async solve({ repoDir, task, issue }) {
156
+ // ----------------------------------------------------------------------
157
+ // TODO: implement your solving logic here. Example shape:
158
+ //
159
+ // const prompt = buildPrompt(issue);
160
+ // ... do the work, edit files under `repoDir` ...
161
+ // return { summary: "what you changed and why" };
162
+ //
163
+ // `issue` is { title, body, ref }; `task` has { taskId, repoUrl, issueId }.
164
+ // ----------------------------------------------------------------------
165
+ throw new Error("custom solver not implemented — edit solve() in src/solver.js");
166
+ },
167
+ };
168
+
169
+ /** Resolve the configured solver. */
170
+ export function getSolver(name = config.solver) {
171
+ if (name === "echo") return echoDriver;
172
+ if (name === "custom") return customDriver;
173
+ const preset = CLI_PRESETS[name];
174
+ if (!preset) {
175
+ const known = ["echo", ...Object.keys(CLI_PRESETS), "custom"].join(", ");
176
+ throw new Error(`Unknown solver "${name}". Use one of: ${known}.`);
177
+ }
178
+ return cliDriver(name, preset);
179
+ }