add codex, antigrav executors; 12 total executors; update .env.example
This commit is contained in:
parent
e41b1c6855
commit
17ab7f5459
15
.env.example
15
.env.example
|
|
@ -18,6 +18,21 @@ OPENAI_API_KEY=
|
|||
PI_DEFAULT_PROVIDER=
|
||||
PI_DEFAULT_MODEL=
|
||||
|
||||
# Claude Code — model override for CCExecutor
|
||||
CLAUDE_CODE_MODEL=
|
||||
|
||||
# OpenCode — proxy and model for OpenCodeExecutor
|
||||
OPENCODE_PROXY=http://localhost:18901/v1
|
||||
OPENCODE_MODEL=
|
||||
|
||||
# Grok CLI — model for GrokExecutor
|
||||
GROK_MODEL=
|
||||
GROK_PROXY=http://localhost:18901/v1
|
||||
|
||||
# Antigrav (agy) — binary path and model for AntigravExecutor
|
||||
AGY_BIN=C:\Users\Artale\AppData\Local\agy\bin\agy.exe
|
||||
AGY_MODEL=gemini-2.5-pro
|
||||
|
||||
# ── PAI Integration ──────────────────────────────────────
|
||||
# Path to cloned PAI repository
|
||||
PAI_DIRECTORY=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Antigrav Executor — PhaseExecutor for Antigravity CLI (agy).
|
||||
*
|
||||
* Antigrav is a general-purpose agentic CLI by Google.
|
||||
* Supports sandboxed execution, plugins, and multi-model routing.
|
||||
*
|
||||
* Integration: `agy --print "<prompt>"` for non-interactive execution.
|
||||
*
|
||||
* Binary: C:\Users\Artale\AppData\Local\agy\bin\agy.exe (install via irm)
|
||||
* Docs: https://antigravity.google/cli
|
||||
*
|
||||
* Usage:
|
||||
* const ag = new AntigravExecutor({ model: "gemini-2.5-pro" });
|
||||
* const result = await feedbackLoop.run(task, ag);
|
||||
*/
|
||||
|
||||
import { exec } from "node:child_process";
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import type { LoopIteration } from "../../core/types.js";
|
||||
import type { PhaseExecutor } from "../../tier2-primitives/loops/feedback-loop.js";
|
||||
|
||||
function findAgyBin(): string {
|
||||
const candidates = [
|
||||
path.join(process.env.LOCALAPPDATA || "", "agy", "bin", "agy.exe"),
|
||||
path.join(process.env.HOME || "", "AppData", "Local", "agy", "bin", "agy.exe"),
|
||||
"agy",
|
||||
];
|
||||
for (const c of candidates) {
|
||||
try { if (fs.existsSync(c)) return c; } catch {}
|
||||
}
|
||||
return "agy";
|
||||
}
|
||||
|
||||
export interface AntigravConfig {
|
||||
/** agy binary path */
|
||||
bin?: string;
|
||||
/** Model name */
|
||||
model?: string;
|
||||
/** Use sandbox mode */
|
||||
sandbox?: boolean;
|
||||
/** Timeout per phase in ms */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class AntigravExecutor implements PhaseExecutor {
|
||||
private config: AntigravConfig;
|
||||
|
||||
constructor(config?: AntigravConfig) {
|
||||
this.config = {
|
||||
bin: process.env.AGY_BIN || findAgyBin(),
|
||||
model: process.env.AGY_MODEL || "gemini-2.5-pro",
|
||||
sandbox: false,
|
||||
timeoutMs: 120000,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
async plan(task: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nPrevious: ${previousIteration.reflection?.slice(0, 200)}` : "";
|
||||
return this.call(`You are in the PLAN phase.\nTask: ${task}${prev}`);
|
||||
}
|
||||
|
||||
async execute(plan: string): Promise<string> {
|
||||
return this.call(`EXECUTE:\n${plan}`);
|
||||
}
|
||||
|
||||
async observe(executed: string): Promise<string> {
|
||||
return this.call(`OBSERVE:\n${executed}`);
|
||||
}
|
||||
|
||||
async reflect(observation: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nExpected: ${previousIteration.refinement?.slice(0, 200)}` : "";
|
||||
return this.call(`REFLECT:\n${observation}${prev}`);
|
||||
}
|
||||
|
||||
async refine(reflection: string): Promise<string> {
|
||||
return this.call(`REFINE:\n${reflection}`);
|
||||
}
|
||||
|
||||
private async call(prompt: string): Promise<string> {
|
||||
const modelFlag = `--model "${this.config.model}"`;
|
||||
const sandboxFlag = this.config.sandbox ? "--sandbox" : "";
|
||||
const cmd = `"${this.config.bin}" --print ${modelFlag} ${sandboxFlag} "${prompt.replace(/"/g, '\\"')}" 2>/dev/null`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, { timeout: this.config.timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
|
||||
resolve(stdout.trim() || `[Antigrav error: ${err?.message?.slice(0, 200) ?? "unknown"}]`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static describeConfig(): string {
|
||||
const bin = process.env.AGY_BIN || findAgyBin();
|
||||
return [
|
||||
"Antigrav (agy) Executor",
|
||||
` Binary: ${bin}`,
|
||||
` Model: ${process.env.AGY_MODEL || "gemini-2.5-pro (set AGY_MODEL)"}`,
|
||||
" Mode: --print (non-interactive)",
|
||||
"",
|
||||
"Install: irm https://antigravity.google/cli/install.ps1 | iex",
|
||||
"Docs: https://antigravity.google/cli",
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Claude Code Executor — PhaseExecutor using the `claude` CLI.
|
||||
*
|
||||
* Claude Code is Anthropic's official agentic coding tool.
|
||||
* Ships with /goal, Outcomes, Dynamic Workflows, and vision.
|
||||
*
|
||||
* Integration: shells out to `claude -p "<prompt>"` for non-interactive execution.
|
||||
*
|
||||
* Usage:
|
||||
* const cc = new ClaudeCodeExecutor();
|
||||
* const result = await feedbackLoop.run(task, cc);
|
||||
*/
|
||||
|
||||
import { exec } from "node:child_process";
|
||||
import type { LoopIteration } from "../../core/types.js";
|
||||
import type { PhaseExecutor } from "../../tier2-primitives/loops/feedback-loop.js";
|
||||
|
||||
export interface CCConfig {
|
||||
/** Claude Code binary path */
|
||||
bin?: string;
|
||||
/** Model override (e.g. "claude-sonnet-4-6") */
|
||||
model?: string;
|
||||
/** Max turns per phase */
|
||||
maxTurns?: number;
|
||||
/** Timeout per phase in ms */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class ClaudeCodeExecutor implements PhaseExecutor {
|
||||
private config: CCConfig;
|
||||
|
||||
constructor(config?: CCConfig) {
|
||||
this.config = {
|
||||
bin: "claude",
|
||||
model: process.env.CLAUDE_CODE_MODEL,
|
||||
maxTurns: 5,
|
||||
timeoutMs: 120000,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
async plan(task: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration
|
||||
? `\nPrevious reflection: ${previousIteration.reflection}\nPrevious refinement: ${previousIteration.refinement}`
|
||||
: "";
|
||||
return this.callClaude(`You are in the PLAN phase.\nTask: ${task}${prev}\n\nProduce a specific, actionable plan with concrete steps.`);
|
||||
}
|
||||
|
||||
async execute(plan: string): Promise<string> {
|
||||
return this.callClaude(`You are in the EXECUTE phase.\nPlan:\n${plan}\n\nCarry it out step by step.`);
|
||||
}
|
||||
|
||||
async observe(executed: string): Promise<string> {
|
||||
return this.callClaude(`You are in the OBSERVE phase.\nOutput:\n${executed}\n\nExtract specific findings.`);
|
||||
}
|
||||
|
||||
async reflect(observation: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nExpected: ${previousIteration.refinement}` : "";
|
||||
return this.callClaude(`You are in the REFLECT phase.\nObservations:\n${observation}${prev}\n\nCompare to expectations. What improved, regressed, or is missing?`);
|
||||
}
|
||||
|
||||
async refine(reflection: string): Promise<string> {
|
||||
return this.callClaude(`You are in the REFINE phase.\nReflection:\n${reflection}\n\nProduce a refined approach.`);
|
||||
}
|
||||
|
||||
private async callClaude(prompt: string): Promise<string> {
|
||||
const modelFlag = this.config.model ? `-m "${this.config.model}"` : "";
|
||||
const cmd = `"${this.config.bin}" -p "${prompt.replace(/"/g, '\\"')}" ${modelFlag} 2>/dev/null`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, { timeout: this.config.timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
|
||||
resolve(stdout.trim() || `[Claude Code error: ${err?.message?.slice(0, 200) ?? "unknown"}]`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static describeConfig(): string {
|
||||
return [
|
||||
"Claude Code Executor",
|
||||
` Binary: claude`,
|
||||
` Model: ${process.env.CLAUDE_CODE_MODEL ?? "default (set CLAUDE_CODE_MODEL)"}`,
|
||||
" Mode: CLI -p (non-interactive)",
|
||||
"",
|
||||
"Requires Claude Code CLI: https://docs.anthropic.com/docs/claude-code",
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Codex Executor — PhaseExecutor using OpenAI Codex CLI.
|
||||
*
|
||||
* Codex is OpenAI's agentic coding tool (ChatGPT Plus/Pro).
|
||||
* Also accessible through Pi: `pi --provider codex --model codex`
|
||||
*
|
||||
* Integration: `codex "<prompt>"` or through Pi's Codex provider.
|
||||
*
|
||||
* Usage:
|
||||
* const codex = new CodexExecutor();
|
||||
* const result = await feedbackLoop.run(task, codex);
|
||||
*
|
||||
* // Via Pi's Codex provider:
|
||||
* const viaPi = new CodexExecutor({ usePi: true });
|
||||
*/
|
||||
|
||||
import { exec } from "node:child_process";
|
||||
import type { LoopIteration } from "../../core/types.js";
|
||||
import type { PhaseExecutor } from "../../tier2-primitives/loops/feedback-loop.js";
|
||||
|
||||
export interface CodexConfig {
|
||||
/** Codex binary path */
|
||||
bin?: string;
|
||||
/** Pipe through Pi's Codex provider instead of direct Codex CLI */
|
||||
usePi?: boolean;
|
||||
/** Timeout per phase in ms */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class CodexExecutor implements PhaseExecutor {
|
||||
private config: CodexConfig;
|
||||
|
||||
constructor(config?: CodexConfig) {
|
||||
this.config = {
|
||||
bin: "codex",
|
||||
usePi: false,
|
||||
timeoutMs: 120000,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
async plan(task: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nPrevious: ${previousIteration.reflection?.slice(0, 200)}` : "";
|
||||
return this.call(`PLAN: ${task}${prev}`);
|
||||
}
|
||||
|
||||
async execute(plan: string): Promise<string> {
|
||||
return this.call(`EXECUTE:\n${plan}`);
|
||||
}
|
||||
|
||||
async observe(executed: string): Promise<string> {
|
||||
return this.call(`OBSERVE:\n${executed}`);
|
||||
}
|
||||
|
||||
async reflect(observation: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nExpected: ${previousIteration.refinement?.slice(0, 200)}` : "";
|
||||
return this.call(`REFLECT:\n${observation}${prev}`);
|
||||
}
|
||||
|
||||
async refine(reflection: string): Promise<string> {
|
||||
return this.call(`REFINE:\n${reflection}`);
|
||||
}
|
||||
|
||||
private async call(prompt: string): Promise<string> {
|
||||
const cmd = this.config.usePi
|
||||
? `pi --provider codex --model codex --print --no-session "${prompt.replace(/"/g, '\\"')}" 2>/dev/null`
|
||||
: `"${this.config.bin}" "${prompt.replace(/"/g, '\\"')}" 2>/dev/null`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, { timeout: this.config.timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
|
||||
resolve(stdout.trim() || `[Codex error: ${err?.message?.slice(0, 200) ?? "unknown"}]`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static describeConfig(): string {
|
||||
return [
|
||||
"Codex Executor",
|
||||
" Direct: codex <prompt> (requires Codex CLI)",
|
||||
" Via Pi: pi --provider codex (requires ChatGPT Plus/Pro subscription)",
|
||||
"",
|
||||
"Codex requires ChatGPT Plus/Pro: https://codex.ai",
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Grok CLI Executor — PhaseExecutor using the `grok` CLI.
|
||||
*
|
||||
* Grok CLI is xAI's agentic coding tool (grok.exe at ~/.grok/bin/grok.exe).
|
||||
* Routes through local proxy at :18901 (configured in ~/.grok/config.toml).
|
||||
*
|
||||
* Integration: shells out to `grok -m <model> -p "<prompt>"`.
|
||||
*
|
||||
* Usage:
|
||||
* const grok = new GrokExecutor({ model: "grok-3" });
|
||||
* const result = await feedbackLoop.run(task, grok);
|
||||
*/
|
||||
|
||||
import { exec } from "node:child_process";
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import type { LoopIteration } from "../../core/types.js";
|
||||
import type { PhaseExecutor } from "../../tier2-primitives/loops/feedback-loop.js";
|
||||
|
||||
function findGrokBin(): string {
|
||||
const candidates = [
|
||||
path.join(process.env.HOME || "", ".grok", "bin", "grok.exe"),
|
||||
path.join(process.env.USERPROFILE || "", ".grok", "bin", "grok.exe"),
|
||||
"grok",
|
||||
];
|
||||
for (const c of candidates) {
|
||||
try { if (fs.existsSync(c)) return c; } catch {}
|
||||
}
|
||||
return "grok";
|
||||
}
|
||||
|
||||
export interface GrokConfig {
|
||||
/** Grok CLI binary path */
|
||||
bin?: string;
|
||||
/** Model name */
|
||||
model?: string;
|
||||
/** Proxy URL */
|
||||
proxyUrl?: string;
|
||||
/** Timeout per phase in ms */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class GrokExecutor implements PhaseExecutor {
|
||||
private config: GrokConfig;
|
||||
|
||||
constructor(config?: GrokConfig) {
|
||||
this.config = {
|
||||
bin: findGrokBin(),
|
||||
model: process.env.GROK_MODEL ?? "grok-3",
|
||||
proxyUrl: process.env.GROK_PROXY ?? "http://localhost:18901/v1",
|
||||
timeoutMs: 120000,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
async plan(task: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nPrevious: ${previousIteration.reflection?.slice(0, 200)}` : "";
|
||||
return this.callGrok(`You are in the PLAN phase.\nTask: ${task}${prev}`);
|
||||
}
|
||||
|
||||
async execute(plan: string): Promise<string> {
|
||||
return this.callGrok(`EXECUTE:\n${plan}`);
|
||||
}
|
||||
|
||||
async observe(executed: string): Promise<string> {
|
||||
return this.callGrok(`OBSERVE:\n${executed}`);
|
||||
}
|
||||
|
||||
async reflect(observation: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nExpected: ${previousIteration.refinement?.slice(0, 200)}` : "";
|
||||
return this.callGrok(`REFLECT:\n${observation}${prev}`);
|
||||
}
|
||||
|
||||
async refine(reflection: string): Promise<string> {
|
||||
return this.callGrok(`REFINE:\n${reflection}`);
|
||||
}
|
||||
|
||||
private async callGrok(prompt: string): Promise<string> {
|
||||
const modelFlag = `-m "${this.config.model}"`;
|
||||
const cmd = `"${this.config.bin}" ${modelFlag} -p "${prompt.replace(/"/g, '\\"')}" --output-format text 2>/dev/null`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, { timeout: this.config.timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
|
||||
resolve(stdout.trim() || `[Grok error: ${err?.message?.slice(0, 200) ?? "unknown"}]`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static describeConfig(): string {
|
||||
return [
|
||||
"Grok CLI Executor",
|
||||
` Binary: ${findGrokBin()}`,
|
||||
` Model: ${process.env.GROK_MODEL ?? "grok-3 (set GROK_MODEL)"}`,
|
||||
` Proxy: ${process.env.GROK_PROXY ?? "http://localhost:18901/v1"}`,
|
||||
" Mode: CLI -p (non-interactive)",
|
||||
"",
|
||||
"See ~/.grok/config.toml for proxy/model configuration.",
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* OpenCode Executor — PhaseExecutor using the `opencode` CLI.
|
||||
*
|
||||
* OpenCode is the user's primary coding agent (from AGENTS.md).
|
||||
* It's an open-source agent that routes through a local proxy at :18901.
|
||||
*
|
||||
* Integration: shells out to `opencode -p "<prompt>"` or pipes via stdin.
|
||||
*
|
||||
* Usage:
|
||||
* const oc = new OpenCodeExecutor();
|
||||
* const result = await feedbackLoop.run(task, oc);
|
||||
*/
|
||||
|
||||
import { exec } from "node:child_process";
|
||||
import type { LoopIteration } from "../../core/types.js";
|
||||
import type { PhaseExecutor } from "../../tier2-primitives/loops/feedback-loop.js";
|
||||
|
||||
export interface OpenCodeConfig {
|
||||
/** OpenCode binary path */
|
||||
bin?: string;
|
||||
/** Model override */
|
||||
model?: string;
|
||||
/** Proxy URL (default: http://localhost:18901/v1) */
|
||||
proxyUrl?: string;
|
||||
/** Timeout per phase in ms */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class OpenCodeExecutor implements PhaseExecutor {
|
||||
private config: OpenCodeConfig;
|
||||
|
||||
constructor(config?: OpenCodeConfig) {
|
||||
this.config = {
|
||||
bin: "opencode",
|
||||
proxyUrl: "http://localhost:18901/v1",
|
||||
timeoutMs: 120000,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
async plan(task: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nPrevious: ${previousIteration.reflection?.slice(0, 200)}` : "";
|
||||
return this.callOpenCode(`PLAN: ${task}${prev}`);
|
||||
}
|
||||
|
||||
async execute(plan: string): Promise<string> {
|
||||
return this.callOpenCode(`EXECUTE:\n${plan}`);
|
||||
}
|
||||
|
||||
async observe(executed: string): Promise<string> {
|
||||
return this.callOpenCode(`OBSERVE:\n${executed}`);
|
||||
}
|
||||
|
||||
async reflect(observation: string, previousIteration: LoopIteration | null): Promise<string> {
|
||||
const prev = previousIteration ? `\nExpected: ${previousIteration.refinement?.slice(0, 200)}` : "";
|
||||
return this.callOpenCode(`REFLECT:\n${observation}${prev}`);
|
||||
}
|
||||
|
||||
async refine(reflection: string): Promise<string> {
|
||||
return this.callOpenCode(`REFINE:\n${reflection}`);
|
||||
}
|
||||
|
||||
private async callOpenCode(prompt: string): Promise<string> {
|
||||
const modelFlag = this.config.model ? `-m "${this.config.model}"` : "";
|
||||
const proxyFlag = `--proxy "${this.config.proxyUrl}"`;
|
||||
const cmd = `"${this.config.bin}" -p "${prompt.replace(/"/g, '\\"')}" ${modelFlag} ${proxyFlag} 2>/dev/null`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, { timeout: this.config.timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
|
||||
resolve(stdout.trim() || `[OpenCode error: ${err?.message?.slice(0, 200) ?? "unknown"}]`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static describeConfig(): string {
|
||||
return [
|
||||
"OpenCode Executor",
|
||||
` Binary: opencode`,
|
||||
` Proxy: ${process.env.OPENCODE_PROXY ?? "http://localhost:18901/v1"}`,
|
||||
` Model: ${process.env.OPENCODE_MODEL ?? "default (set OPENCODE_MODEL)"}`,
|
||||
" Mode: CLI -p (non-interactive)",
|
||||
"",
|
||||
"See ~/.grok/AGENTS.md for proxy configuration.",
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue