automagik-genie
Version:
Self-evolving AI agent orchestration framework with Model Context Protocol support
170 lines (167 loc) • 7.29 kB
JavaScript
;
/**
* Install Flow Helpers - Genie orchestration for fresh installations
*
* Architecture:
* 1. CLI launches GENIE task with simple prompt: "Run explorer to acquire context, when it ends run the install workflow"
* 2. Genie orchestrates: explore → interview → spawn installers → completion
* 3. User monitors progress in Forge dashboard via shortened URL
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.printBox = printBox;
exports.launchMasterGenie = launchMasterGenie;
exports.runInstallFlow = runInstallFlow;
const service_config_js_1 = require("./service-config.js");
const gradient_string_1 = __importDefault(require("gradient-string"));
const child_process_1 = require("child_process");
// Import ForgeExecutor for workspace project management
const forge_executor_js_1 = require("./forge-executor.js");
const FORGE_URL = process.env.FORGE_BASE_URL || (0, service_config_js_1.getForgeConfig)().baseUrl;
// Import from compiled MCP dist (will be available after build)
let shortenUrl;
let getApiKeyFromEnv;
try {
const urlShortener = require('../../mcp/dist/lib/url-shortener.js');
shortenUrl = urlShortener.shortenUrl;
getApiKeyFromEnv = urlShortener.getApiKeyFromEnv;
}
catch {
// Fallback if MCP not built yet
shortenUrl = async (url) => ({ success: false, fullUrl: url });
getApiKeyFromEnv = () => undefined;
}
/**
* Print a gradient box to console
*/
function printBox(title, content) {
const border = '═'.repeat(60);
console.log(gradient_string_1.default.pastel(`╔${border}╗`));
console.log(gradient_string_1.default.pastel(`║ ${title.padEnd(58)} ║`));
console.log(gradient_string_1.default.pastel(`╠${border}╣`));
console.log(content);
console.log(gradient_string_1.default.pastel(`╚${border}╝`));
}
/**
* Launch Genie orchestrator for installation
*/
async function launchMasterGenie(config) {
const FORGE_URL = process.env.FORGE_BASE_URL || (0, service_config_js_1.getForgeConfig)().baseUrl;
console.log('');
printBox('🧞 GENIE AWAKENING', 'Starting installation orchestration...');
console.log('');
// Get or create workspace-specific Forge project
const forgeExecutor = (0, forge_executor_js_1.createForgeExecutor)({ forgeBaseUrl: FORGE_URL });
const projectId = await forgeExecutor.getOrCreateGenieProject();
// Get or create master agent (uses forge_agents table)
const masterResponse = await fetch(`${FORGE_URL}/api/forge/agents?project_id=${projectId}&agent_type=master`);
if (!masterResponse.ok) {
throw new Error(`Failed to query master agent: ${masterResponse.status}`);
}
const { data: agents } = await masterResponse.json();
let masterAgent = agents?.[0];
if (!masterAgent) {
console.log(gradient_string_1.default.pastel('Creating Genie agent...'));
const createResponse = await fetch(`${FORGE_URL}/api/forge/agents`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
project_id: projectId,
agent_type: 'master'
})
});
if (!createResponse.ok) {
throw new Error(`Failed to create master agent: ${createResponse.status}`);
}
const { data } = await createResponse.json();
masterAgent = data;
}
// Build simple orchestration prompt
const templates = config.templates?.join(', ') || 'code';
const prompt = `Run explorer to acquire context, when it ends run the install workflow.
Templates to install: ${templates}
See @.genie/spells/install-genie.md for detailed instructions.`;
// Get genie agent definition from registry
let genieVariant = 'GENIE'; // Fallback
let genieExecutor = config.executor?.toUpperCase() || 'CLAUDE_CODE';
try {
const { getAgentRegistry } = await import('./agent-registry.js');
const registry = await getAgentRegistry();
// Lookup genie neuron by workflow name
// Neurons are registered as "neuron/genie" without collective
const genieAgentDef = registry.getAgent('genie');
if (genieAgentDef) {
// Derive forge_profile_name: use explicit or derive from neuron name
genieVariant = genieAgentDef.forge_profile_name
|| (genieAgentDef.type === 'neuron' ? genieAgentDef.name.toUpperCase() : 'GENIE');
genieExecutor = genieAgentDef.genie?.executor || genieExecutor;
}
}
catch (error) {
// Use fallback GENIE if registry unavailable
console.warn('Failed to load genie agent definition, using fallback variant');
}
// Create attempt with genie variant from registry
console.log(gradient_string_1.default.pastel('Creating orchestration attempt...'));
const attemptResponse = await fetch(`${FORGE_URL}/api/task-attempts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
task_id: masterAgent.task_id,
executor_profile_id: {
executor: genieExecutor,
variant: genieVariant
},
base_branch: getCurrentBranch()
})
});
if (!attemptResponse.ok) {
throw new Error(`Failed to create attempt: ${attemptResponse.status}`);
}
const { data: attempt } = await attemptResponse.json();
// Send installation prompt as follow-up message
const followUpResponse = await fetch(`${FORGE_URL}/api/task-attempts/${attempt.id}/follow-up`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
if (!followUpResponse.ok) {
throw new Error(`Failed to send installation prompt: ${followUpResponse.status}`);
}
console.log('');
console.log(gradient_string_1.default.pastel('✨ Genie orchestrating installation...'));
console.log('');
const fullUrl = `${FORGE_URL}/projects/${projectId}/tasks/${masterAgent.task_id}/attempts/${attempt.id}?view=diffs`;
// Shorten URL
const { shortUrl: shortened } = await shortenUrl(fullUrl, {
apiKey: getApiKeyFromEnv()
});
return shortened || fullUrl;
}
/**
* Main install flow orchestrator
*/
async function runInstallFlow(config) {
const forgeExecutor = (0, forge_executor_js_1.createForgeExecutor)({ forgeBaseUrl: FORGE_URL });
// NOTE: Agent profile sync removed - Forge discovers .genie folders natively
// Step 1: Launch Genie orchestrator (handles explore → install workflow)
const shortUrl = await launchMasterGenie(config);
return shortUrl;
}
/**
* Get current git branch (suppresses stderr to avoid scary errors in new repos)
*/
function getCurrentBranch() {
try {
return (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'] // Suppress stderr
}).trim();
}
catch {
// Return 'main' for brand new repos (no commits yet) or non-git directories
return 'main';
}
}