fable-agent/src/examples/executors/antigrav-executor.ts

106 lines
3.4 KiB
TypeScript

/**
* 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");
}
}