automagik-genie
Version:
Self-evolving AI agent orchestration framework with Model Context Protocol support
194 lines (193 loc) • 9.64 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRunHandler = createRunHandler;
const agent_resolver_1 = require("../../lib/agent-resolver");
const forge_executor_1 = require("../../lib/forge-executor");
const forge_helpers_1 = require("../../lib/forge-helpers");
const headless_helpers_1 = require("../../lib/headless-helpers");
const executor_registry_1 = require("../../lib/executor-registry");
const executor_auth_1 = require("../../lib/executor-auth");
function createRunHandler(ctx) {
return async (parsed) => {
const [agentName, ...promptParts] = parsed.commandArgs;
if (!agentName) {
throw new Error('Usage: genie run <agent> "<prompt>"');
}
const prompt = promptParts.join(' ').trim();
const resolvedAgentName = (0, agent_resolver_1.resolveAgentIdentifier)(agentName);
const agentSpec = (0, agent_resolver_1.loadAgentSpec)(resolvedAgentName);
const agentGenie = agentSpec.meta?.genie || {};
const { executorKey, executorVariant, model, modeName } = resolveExecutionSelection(ctx.config, parsed, agentGenie);
// Detect output mode
// Default: Foreground execution with live output (wait for completion)
// --background: Start task and exit immediately (return Forge URL)
// --raw: Foreground execution, output raw text only
// --quiet: Foreground execution, suppress startup messages
const isBackground = parsed.options.background === true;
const isRawOutput = parsed.options.raw;
const isQuiet = parsed.options.quiet;
const isForeground = !isBackground; // Default is foreground
// Check if executor is authenticated
if (isExecutorAuthRequired(executorKey)) {
const isAuthenticated = await (0, executor_auth_1.checkExecutorAuth)(executorKey);
if (!isAuthenticated) {
try {
await (0, executor_auth_1.promptExecutorLogin)(executorKey);
}
catch (error) {
throw new Error(`Authentication required for ${executorKey}. Please configure it and try again.`);
}
}
}
// Ensure Forge is running (silent if quiet mode)
if (isForeground) {
await (0, headless_helpers_1.ensureForgeRunning)(isQuiet);
}
const forgeExecutor = (0, forge_executor_1.createForgeExecutor)();
// NOTE: Agent profile sync removed - Forge discovers .genie folders natively
const startTime = Date.now();
let sessionResult;
try {
sessionResult = await forgeExecutor.createTask({
agentName: resolvedAgentName,
prompt,
executorKey,
executorVariant,
executionMode: modeName,
model
});
}
catch (error) {
const reason = (0, forge_helpers_1.describeForgeError)(error);
ctx.recordRuntimeWarning(`Forge session creation failed: ${reason}`);
throw new Error(`Forge backend rejected session creation. ${forge_helpers_1.FORGE_RECOVERY_HINT}`);
}
const attemptId = sessionResult.attemptId; // Use Forge's UUID directly (issue #407 fix)
const now = new Date().toISOString();
const store = ctx.sessionService.load({ onWarning: ctx.recordRuntimeWarning });
store.sessions[attemptId] = {
agent: resolvedAgentName,
taskId: sessionResult.taskId,
projectId: sessionResult.projectId,
executor: executorKey,
executorVariant,
model: model || undefined,
status: 'running',
created: now,
lastUsed: now,
lastPrompt: prompt.slice(0, 200),
mode: modeName,
forgeUrl: sessionResult.forgeUrl,
background: isBackground
};
await ctx.sessionService.save(store);
// Foreground mode (DEFAULT): wait for completion and show output
if (isForeground) {
if (!isQuiet) {
const executorSummary = [executorKey, executorVariant].filter(Boolean).join('/');
const modelSuffix = model ? `, model=${model}` : '';
process.stdout.write(`✓ Running ${resolvedAgentName} (executor=${executorSummary}${modelSuffix})\n`);
process.stdout.write(` Session ID: ${attemptId}\n`);
process.stdout.write(` Waiting for completion...\n\n`);
}
const result = await (0, headless_helpers_1.waitForTaskCompletion)(sessionResult.attemptId, forgeExecutor);
const duration = Date.now() - startTime;
// Output result based on mode
if (isRawOutput) {
// Raw text output only (no JSON wrapper)
process.stdout.write(result.output + '\n');
}
else {
// Default: show output with metadata
if (!isQuiet) {
process.stdout.write(`\n${'='.repeat(60)}\n`);
process.stdout.write(`📊 ${resolvedAgentName} Output\n`);
process.stdout.write(`${'='.repeat(60)}\n\n`);
}
process.stdout.write(result.output + '\n');
if (!isQuiet) {
process.stdout.write(`\n${'='.repeat(60)}\n`);
process.stdout.write(`Status: ${result.status}\n`);
process.stdout.write(`Duration: ${(duration / 1000).toFixed(2)}s\n`);
process.stdout.write(`${'='.repeat(60)}\n`);
}
}
// Exit with appropriate code
process.exitCode = result.status === 'completed' ? 0 : 1;
return;
}
// Background mode: just show Forge URL and exit
const executorSummary = [executorKey, executorVariant].filter(Boolean).join('/');
const modelSuffix = model ? `, model=${model}` : '';
process.stdout.write(`✓ Started ${resolvedAgentName} in background (executor=${executorSummary}${modelSuffix})\n`);
process.stdout.write(` Session ID: ${attemptId}\n`);
process.stdout.write(` Forge URL: ${sessionResult.forgeUrl}\n`);
process.stdout.write(` View session: genie view ${attemptId}\n`);
};
}
function resolveExecutionSelection(config, parsed, agentGenie) {
let executor = (0, executor_registry_1.normalizeExecutorKeyOrDefault)(config.defaults?.executor);
let variant = (config.defaults?.executorVariant || 'DEFAULT').trim().toUpperCase();
let model = typeof config.defaults?.model === 'string' ? config.defaults.model.trim() || undefined : undefined;
let modeName = 'default';
if (typeof config.defaults?.executionMode === 'string' && config.defaults.executionMode.trim().length) {
modeName = config.defaults.executionMode.trim();
}
if (typeof agentGenie.executionMode === 'string' && agentGenie.executionMode.trim().length) {
modeName = agentGenie.executionMode.trim();
}
const agentExecutor = (0, executor_registry_1.normalizeExecutorValue)(agentGenie.executor);
if (agentExecutor) {
executor = agentExecutor;
}
const agentVariant = agentGenie.executorProfile || agentGenie.executor_variant || agentGenie.executorVariant || agentGenie.variant;
if (typeof agentVariant === 'string' && agentVariant.trim().length) {
variant = agentVariant.trim().toUpperCase();
}
if (typeof agentGenie.model === 'string' && agentGenie.model.trim().length) {
model = agentGenie.model.trim();
}
if (typeof parsed.options.executor === 'string' && parsed.options.executor.trim().length) {
executor = (0, executor_registry_1.normalizeExecutorKey)(parsed.options.executor) ?? executor;
}
if (typeof parsed.options.model === 'string' && parsed.options.model.trim().length) {
model = parsed.options.model.trim();
const matchedVariant = findVariantForModel(config, executor, model);
if (matchedVariant) {
variant = matchedVariant;
}
}
if (!variant.length)
variant = 'DEFAULT';
return { executorKey: (0, executor_registry_1.normalizeExecutorKeyOrDefault)(executor), executorVariant: variant, model, modeName };
}
function findVariantForModel(config, executorKey, model) {
const executors = config.forge?.executors;
if (!executors)
return null;
const normalizedExecutor = executorKey.trim().toUpperCase();
const executorProfiles = executors[normalizedExecutor];
if (!executorProfiles || typeof executorProfiles !== 'object')
return null;
const desiredModel = model.trim();
for (const [variantName, profileSpec] of Object.entries(executorProfiles)) {
if (!profileSpec || typeof profileSpec !== 'object')
continue;
for (const profileKey of Object.keys(profileSpec)) {
const profileConfig = profileSpec[profileKey];
if (profileConfig && typeof profileConfig === 'object' && typeof profileConfig.model === 'string') {
if (profileConfig.model.trim() === desiredModel) {
return variantName.trim().toUpperCase();
}
}
}
}
return null;
}
/**
* Check if executor requires authentication
*/
function isExecutorAuthRequired(executorKey) {
const authRequiredExecutors = ['OPENCODE', 'CLAUDE_CODE', 'CODEX', 'GEMINI'];
return authRequiredExecutors.includes(executorKey.toUpperCase());
}