mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
Add support for Factory.ai droid agent.
See: https://factory.ai/product/cli
This commit is contained in:
590
package-lock.json
generated
590
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -41,21 +41,29 @@ describe('parsers/index', () => {
|
||||
expect(hasOutputParser('codex')).toBe(true);
|
||||
});
|
||||
|
||||
it('should register exactly 3 parsers', () => {
|
||||
it('should register Factory Droid parser', () => {
|
||||
expect(hasOutputParser('factory-droid')).toBe(false);
|
||||
|
||||
initializeOutputParsers();
|
||||
|
||||
expect(hasOutputParser('factory-droid')).toBe(true);
|
||||
});
|
||||
|
||||
it('should register exactly 4 parsers', () => {
|
||||
initializeOutputParsers();
|
||||
|
||||
const parsers = getAllOutputParsers();
|
||||
expect(parsers.length).toBe(3);
|
||||
expect(parsers.length).toBe(4); // Claude, OpenCode, Codex, Factory Droid
|
||||
});
|
||||
|
||||
it('should clear existing parsers before registering', () => {
|
||||
// First initialization
|
||||
initializeOutputParsers();
|
||||
expect(getAllOutputParsers().length).toBe(3);
|
||||
expect(getAllOutputParsers().length).toBe(4);
|
||||
|
||||
// Second initialization should still have exactly 3
|
||||
// Second initialization should still have exactly 4
|
||||
initializeOutputParsers();
|
||||
expect(getAllOutputParsers().length).toBe(3);
|
||||
expect(getAllOutputParsers().length).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,7 +73,7 @@ describe('parsers/index', () => {
|
||||
|
||||
ensureParsersInitialized();
|
||||
|
||||
expect(getAllOutputParsers().length).toBe(3);
|
||||
expect(getAllOutputParsers().length).toBe(4);
|
||||
});
|
||||
|
||||
it('should be idempotent after first call', () => {
|
||||
|
||||
@@ -469,6 +469,7 @@ describe('AGENT_ARTIFACTS', () => {
|
||||
'aider',
|
||||
'opencode',
|
||||
'codex',
|
||||
'factory-droid',
|
||||
'claude',
|
||||
'terminal',
|
||||
];
|
||||
@@ -622,7 +623,7 @@ describe('buildContextTransferPrompt', () => {
|
||||
});
|
||||
|
||||
it('should work for all agent type combinations', () => {
|
||||
const agents: ToolType[] = ['claude-code', 'aider', 'opencode', 'codex', 'claude', 'terminal'];
|
||||
const agents: ToolType[] = ['claude-code', 'aider', 'opencode', 'codex', 'factory-droid', 'claude', 'terminal'];
|
||||
|
||||
for (const source of agents) {
|
||||
for (const target of agents) {
|
||||
|
||||
@@ -303,6 +303,34 @@ export const AGENT_CAPABILITIES: Record<string, AgentCapabilities> = {
|
||||
supportsContextMerge: true, // Can receive merged context via prompts
|
||||
supportsContextExport: true, // Session storage supports context export
|
||||
},
|
||||
|
||||
/**
|
||||
* Factory Droid - Enterprise AI coding assistant from Factory
|
||||
* https://docs.factory.ai/cli
|
||||
*
|
||||
* Verified capabilities based on CLI testing (droid exec --help) and session file analysis.
|
||||
*/
|
||||
'factory-droid': {
|
||||
supportsResume: true, // -s, --session-id <id> (requires a prompt) - Verified
|
||||
supportsReadOnlyMode: true, // Default mode (no --auto flags) - Verified
|
||||
supportsJsonOutput: true, // -o stream-json - Verified
|
||||
supportsSessionId: true, // UUID in session filenames - Verified
|
||||
supportsImageInput: true, // -f, --file flag - Verified
|
||||
supportsImageInputOnResume: true, // -f works with -s flag - Verified
|
||||
supportsSlashCommands: false, // Factory uses different command system
|
||||
supportsSessionStorage: true, // ~/.factory/sessions/ (JSONL files) - Verified
|
||||
supportsCostTracking: false, // Token counts only in settings.json, no USD cost
|
||||
supportsUsageStats: true, // tokenUsage in settings.json - Verified
|
||||
supportsBatchMode: true, // droid exec subcommand - Verified
|
||||
requiresPromptToStart: true, // Requires prompt argument for exec
|
||||
supportsStreaming: true, // stream-json format - Verified
|
||||
supportsResultMessages: true, // Can detect end of conversation
|
||||
supportsModelSelection: true, // -m, --model flag - Verified
|
||||
supportsStreamJsonInput: true, // --input-format stream-json - Verified
|
||||
supportsThinkingDisplay: true, // Emits thinking content in messages - Verified
|
||||
supportsContextMerge: true, // Can receive merged context via prompts
|
||||
supportsContextExport: true, // Session files are exportable
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -179,6 +179,93 @@ export const AGENT_DEFINITIONS: Omit<AgentConfig, 'available' | 'path' | 'capabi
|
||||
command: 'aider',
|
||||
args: [], // Base args (placeholder - to be configured when implemented)
|
||||
},
|
||||
{
|
||||
id: 'factory-droid',
|
||||
name: 'Factory Droid',
|
||||
binaryName: 'droid',
|
||||
command: 'droid',
|
||||
args: [], // Base args for interactive mode (none)
|
||||
requiresPty: false, // Batch mode uses child process
|
||||
|
||||
// Batch mode: droid exec [options] "prompt"
|
||||
batchModePrefix: ['exec'],
|
||||
// Always skip permissions in batch mode (like Claude Code's --dangerously-skip-permissions)
|
||||
// Maestro requires full access to work properly
|
||||
batchModeArgs: ['--skip-permissions-unsafe'],
|
||||
|
||||
// JSON output for parsing
|
||||
jsonOutputArgs: ['-o', 'stream-json'],
|
||||
|
||||
// Session resume: -s <id> (requires a prompt)
|
||||
resumeArgs: (sessionId: string) => ['-s', sessionId],
|
||||
|
||||
// Read-only mode is DEFAULT in droid exec (no flag needed)
|
||||
readOnlyArgs: [],
|
||||
|
||||
// YOLO mode (same as batchModeArgs, kept for explicit yoloMode requests)
|
||||
yoloModeArgs: ['--skip-permissions-unsafe'],
|
||||
|
||||
// Model selection is handled by configOptions.model.argBuilder below
|
||||
// Don't define modelArgs here to avoid duplicate -m flags
|
||||
|
||||
// Working directory
|
||||
workingDirArgs: (dir: string) => ['--cwd', dir],
|
||||
|
||||
// File/image input
|
||||
imageArgs: (imagePath: string) => ['-f', imagePath],
|
||||
|
||||
// Prompt is positional argument (no separator needed)
|
||||
noPromptSeparator: true,
|
||||
|
||||
// Default env vars - don't set NO_COLOR as it conflicts with FORCE_COLOR
|
||||
defaultEnvVars: {},
|
||||
|
||||
// UI config options
|
||||
// Model IDs from droid CLI (exact IDs required)
|
||||
// NOTE: autonomyLevel is NOT configurable - Maestro always uses --skip-permissions-unsafe
|
||||
// which conflicts with --auto. This matches Claude Code's behavior.
|
||||
configOptions: [
|
||||
{
|
||||
key: 'model',
|
||||
type: 'select',
|
||||
label: 'Model',
|
||||
description: 'Model to use for Factory Droid',
|
||||
// Model IDs from `droid exec --help` (2026-01-22)
|
||||
options: [
|
||||
'', // Empty = use droid's default (claude-opus-4-5-20251101)
|
||||
// OpenAI models
|
||||
'gpt-5.1',
|
||||
'gpt-5.1-codex',
|
||||
'gpt-5.1-codex-max',
|
||||
'gpt-5.2',
|
||||
// Claude models
|
||||
'claude-sonnet-4-5-20250929',
|
||||
'claude-opus-4-5-20251101',
|
||||
'claude-haiku-4-5-20251001',
|
||||
// Google models
|
||||
'gemini-3-pro-preview',
|
||||
],
|
||||
default: '', // Empty = use droid's default (claude-opus-4-5-20251101)
|
||||
argBuilder: (value: string) => (value && value.trim() ? ['-m', value.trim()] : []),
|
||||
},
|
||||
{
|
||||
key: 'reasoningEffort',
|
||||
type: 'select',
|
||||
label: 'Reasoning Effort',
|
||||
description: 'How much the model should reason before responding',
|
||||
options: ['', 'low', 'medium', 'high'],
|
||||
default: '', // Empty = use droid's default reasoning
|
||||
argBuilder: (value: string) => (value && value.trim() ? ['-r', value.trim()] : []),
|
||||
},
|
||||
{
|
||||
key: 'contextWindow',
|
||||
type: 'number',
|
||||
label: 'Context Window Size',
|
||||
description: 'Maximum context window in tokens (for UI display)',
|
||||
default: 200000,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export class AgentDetector {
|
||||
@@ -544,6 +631,16 @@ export class AgentDetector {
|
||||
path.join(localAppData, 'Programs', 'Python', 'Python311', 'Scripts', 'aider.exe'),
|
||||
path.join(localAppData, 'Programs', 'Python', 'Python310', 'Scripts', 'aider.exe'),
|
||||
],
|
||||
droid: [
|
||||
// Factory Droid installation paths
|
||||
path.join(home, '.factory', 'bin', 'droid.exe'),
|
||||
path.join(localAppData, 'Factory', 'droid.exe'),
|
||||
path.join(appData, 'Factory', 'droid.exe'),
|
||||
path.join(home, '.local', 'bin', 'droid.exe'),
|
||||
// npm global installation
|
||||
path.join(appData, 'npm', 'droid.cmd'),
|
||||
path.join(localAppData, 'npm', 'droid.cmd'),
|
||||
],
|
||||
};
|
||||
|
||||
const pathsToCheck = knownPaths[binaryName] || [];
|
||||
@@ -632,6 +729,15 @@ export class AgentDetector {
|
||||
// Add paths from Node version managers (in case installed via npm)
|
||||
...versionManagerPaths.map((p) => path.join(p, 'aider')),
|
||||
],
|
||||
droid: [
|
||||
// Factory Droid installation paths
|
||||
path.join(home, '.factory', 'bin', 'droid'),
|
||||
path.join(home, '.local', 'bin', 'droid'),
|
||||
'/opt/homebrew/bin/droid',
|
||||
'/usr/local/bin/droid',
|
||||
// Add paths from Node version managers (in case installed via npm)
|
||||
...versionManagerPaths.map((p) => path.join(p, 'droid')),
|
||||
],
|
||||
};
|
||||
|
||||
const pathsToCheck = knownPaths[binaryName] || [];
|
||||
|
||||
@@ -22,7 +22,7 @@ const LOG_CONTEXT = '[AgentSessionStorage]';
|
||||
/**
|
||||
* Known agent IDs that have session storage support
|
||||
*/
|
||||
const KNOWN_AGENT_IDS: ToolType[] = ['claude-code', 'codex', 'opencode'];
|
||||
const KNOWN_AGENT_IDS: ToolType[] = ['claude-code', 'codex', 'opencode', 'factory-droid'];
|
||||
|
||||
/**
|
||||
* Session origin types - indicates how the session was created
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { ModeratorConfig, GroupChatHistoryEntry } from '../../shared/group-
|
||||
* Valid agent IDs that can be used as moderators.
|
||||
* Must match available agents from agent-detector.
|
||||
*/
|
||||
const VALID_MODERATOR_AGENT_IDS: ToolType[] = ['claude-code', 'codex', 'opencode'];
|
||||
const VALID_MODERATOR_AGENT_IDS: ToolType[] = ['claude-code', 'codex', 'opencode', 'factory-droid'];
|
||||
|
||||
/**
|
||||
* Bootstrap settings store for custom storage location.
|
||||
|
||||
@@ -327,11 +327,26 @@ export function registerAgentsHandlers(deps: AgentsHandlerDependencies): void {
|
||||
);
|
||||
|
||||
// Get all configuration for an agent
|
||||
// Merges stored config with defaults from agent's configOptions
|
||||
ipcMain.handle(
|
||||
'agents:getConfig',
|
||||
withIpcErrorLogging(handlerOpts('getConfig', CONFIG_LOG_CONTEXT), async (agentId: string) => {
|
||||
const allConfigs = agentConfigsStore.get('configs', {});
|
||||
return allConfigs[agentId] || {};
|
||||
const storedConfig = allConfigs[agentId] || {};
|
||||
|
||||
// Get defaults from agent definition's configOptions
|
||||
const agentDef = AGENT_DEFINITIONS.find((a) => a.id === agentId);
|
||||
const defaults: Record<string, unknown> = {};
|
||||
if (agentDef?.configOptions) {
|
||||
for (const option of agentDef.configOptions) {
|
||||
if (option.default !== undefined) {
|
||||
defaults[option.key] = option.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge: stored config takes precedence over defaults
|
||||
return { ...defaults, ...storedConfig };
|
||||
})
|
||||
);
|
||||
|
||||
@@ -351,6 +366,7 @@ export function registerAgentsHandlers(deps: AgentsHandlerDependencies): void {
|
||||
);
|
||||
|
||||
// Get a specific configuration value for an agent
|
||||
// Falls back to default from agent's configOptions if not stored
|
||||
ipcMain.handle(
|
||||
'agents:getConfigValue',
|
||||
withIpcErrorLogging(
|
||||
@@ -358,7 +374,16 @@ export function registerAgentsHandlers(deps: AgentsHandlerDependencies): void {
|
||||
async (agentId: string, key: string) => {
|
||||
const allConfigs = agentConfigsStore.get('configs', {});
|
||||
const agentConfig = allConfigs[agentId] || {};
|
||||
return agentConfig[key];
|
||||
|
||||
// Return stored value if present
|
||||
if (agentConfig[key] !== undefined) {
|
||||
return agentConfig[key];
|
||||
}
|
||||
|
||||
// Fall back to default from agent definition
|
||||
const agentDef = AGENT_DEFINITIONS.find((a) => a.id === agentId);
|
||||
const option = agentDef?.configOptions?.find((o) => o.key === key);
|
||||
return option?.default;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -42,6 +42,7 @@ const VALID_TOOL_TYPES: ToolType[] = [
|
||||
'terminal',
|
||||
'claude',
|
||||
'aider',
|
||||
'factory-droid',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ const VALID_TOOL_TYPES = new Set<string>([
|
||||
'opencode',
|
||||
'codex',
|
||||
'terminal',
|
||||
'factory-droid',
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -543,6 +544,148 @@ export const CODEX_ERROR_PATTERNS: AgentErrorPatterns = {
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Factory Droid Error Patterns
|
||||
// ============================================================================
|
||||
|
||||
export const FACTORY_DROID_ERROR_PATTERNS: AgentErrorPatterns = {
|
||||
auth_expired: [
|
||||
{
|
||||
pattern: /invalid.*api.*key/i,
|
||||
message: 'Invalid API key. Please check your Factory credentials.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /authentication.*failed/i,
|
||||
message: 'Authentication failed. Please verify your Factory API key.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /unauthorized/i,
|
||||
message: 'Unauthorized access. Please check your Factory API key.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /FACTORY_API_KEY/i,
|
||||
message: 'Factory API key not set. Please set FACTORY_API_KEY environment variable.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /api.*key.*expired/i,
|
||||
message: 'Your API key has expired. Please renew your Factory credentials.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
|
||||
token_exhaustion: [
|
||||
{
|
||||
pattern: /context.*exceeded/i,
|
||||
message: 'Context limit exceeded. Start a new session.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /maximum.*tokens/i,
|
||||
message: 'Maximum token limit reached. Start a new session.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /token.*limit/i,
|
||||
message: 'Token limit reached. Consider starting a fresh conversation.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /prompt.*too\s+long/i,
|
||||
message: 'Prompt is too long. Try a shorter message or start a new session.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
|
||||
rate_limited: [
|
||||
{
|
||||
pattern: /rate.*limit/i,
|
||||
message: 'Rate limit exceeded. Please wait before trying again.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /too many requests/i,
|
||||
message: 'Too many requests. Please wait before sending more messages.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /quota.*exceeded/i,
|
||||
message: 'Your API quota has been exceeded.',
|
||||
recoverable: false,
|
||||
},
|
||||
{
|
||||
pattern: /\b429\b/,
|
||||
message: 'Rate limited. Please wait and try again.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
|
||||
network_error: [
|
||||
{
|
||||
pattern: /connection\s*(failed|refused|error|reset|closed)/i,
|
||||
message: 'Connection failed. Check your internet connection.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND/i,
|
||||
message: 'Network error. Check your internet connection.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /request\s+timed?\s*out|timed?\s*out\s+waiting/i,
|
||||
message: 'Request timed out. Please try again.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /network\s+(error|failure|unavailable)/i,
|
||||
message: 'Network error occurred. Please check your connection.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
|
||||
permission_denied: [
|
||||
{
|
||||
pattern: /permission denied/i,
|
||||
message: 'Permission denied. The agent cannot access the requested resource.',
|
||||
recoverable: false,
|
||||
},
|
||||
{
|
||||
pattern: /access denied/i,
|
||||
message: 'Access denied to the requested resource.',
|
||||
recoverable: false,
|
||||
},
|
||||
{
|
||||
pattern: /autonomy.*level/i,
|
||||
message: 'Operation requires higher autonomy level. Use --auto flag.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
|
||||
agent_crashed: [
|
||||
{
|
||||
pattern: /\b(fatal|unexpected|internal|unhandled)\s+error\b/i,
|
||||
message: 'An unexpected error occurred in the agent.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
|
||||
session_not_found: [
|
||||
{
|
||||
pattern: /session.*not found/i,
|
||||
message: 'Session not found. Starting fresh conversation.',
|
||||
recoverable: true,
|
||||
},
|
||||
{
|
||||
pattern: /invalid.*session/i,
|
||||
message: 'Invalid session. Starting fresh conversation.',
|
||||
recoverable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SSH Error Patterns
|
||||
// ============================================================================
|
||||
@@ -724,6 +867,7 @@ const patternRegistry = new Map<ToolType, AgentErrorPatterns>([
|
||||
['claude-code', CLAUDE_ERROR_PATTERNS],
|
||||
['opencode', OPENCODE_ERROR_PATTERNS],
|
||||
['codex', CODEX_ERROR_PATTERNS],
|
||||
['factory-droid', FACTORY_DROID_ERROR_PATTERNS],
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,6 +53,7 @@ export {
|
||||
import { ClaudeOutputParser } from './claude-output-parser';
|
||||
import { OpenCodeOutputParser } from './opencode-output-parser';
|
||||
import { CodexOutputParser } from './codex-output-parser';
|
||||
import { FactoryDroidOutputParser } from './factory-droid-output-parser';
|
||||
import {
|
||||
registerOutputParser,
|
||||
clearParserRegistry,
|
||||
@@ -64,6 +65,7 @@ import { logger } from '../utils/logger';
|
||||
export { ClaudeOutputParser } from './claude-output-parser';
|
||||
export { OpenCodeOutputParser } from './opencode-output-parser';
|
||||
export { CodexOutputParser } from './codex-output-parser';
|
||||
export { FactoryDroidOutputParser } from './factory-droid-output-parser';
|
||||
|
||||
const LOG_CONTEXT = '[OutputParsers]';
|
||||
|
||||
@@ -79,6 +81,7 @@ export function initializeOutputParsers(): void {
|
||||
registerOutputParser(new ClaudeOutputParser());
|
||||
registerOutputParser(new OpenCodeOutputParser());
|
||||
registerOutputParser(new CodexOutputParser());
|
||||
registerOutputParser(new FactoryDroidOutputParser());
|
||||
|
||||
// Log registered parsers for debugging
|
||||
const registeredParsers = getAllOutputParsers().map((p) => p.agentId);
|
||||
|
||||
@@ -48,6 +48,7 @@ export const DEFAULT_CONTEXT_WINDOWS: Record<ToolType, number> = {
|
||||
opencode: 128000, // OpenCode (depends on model, 128k is conservative default)
|
||||
aider: 128000, // Aider (varies by model, 128k is conservative default)
|
||||
terminal: 0, // Terminal has no context window
|
||||
'factory-droid': 200000, // Factory Droid (Claude Opus 4.5 default context)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,6 +95,17 @@ export class ExitHandler {
|
||||
this.handleBatchModeExit(sessionId, managedProcess);
|
||||
}
|
||||
|
||||
// Handle stream-json mode: emit accumulated streamed text if no result was emitted
|
||||
// Some agents (like Factory Droid) don't send explicit "done" events, they just exit
|
||||
if (isStreamJsonMode && !managedProcess.resultEmitted && managedProcess.streamedText) {
|
||||
managedProcess.resultEmitted = true;
|
||||
logger.debug('[ProcessManager] Emitting streamed text at exit (no result event)', 'ProcessManager', {
|
||||
sessionId,
|
||||
streamedTextLength: managedProcess.streamedText.length,
|
||||
});
|
||||
this.bufferManager.emitDataBuffered(sessionId, managedProcess.streamedText);
|
||||
}
|
||||
|
||||
// Check for errors using the parser (if not already emitted)
|
||||
if (outputParser && !managedProcess.errorEmitted) {
|
||||
const agentError = outputParser.detectErrorFromExit(
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
export { ClaudeSessionStorage, ClaudeSessionOriginsData } from './claude-session-storage';
|
||||
export { OpenCodeSessionStorage } from './opencode-session-storage';
|
||||
export { CodexSessionStorage } from './codex-session-storage';
|
||||
export { FactoryDroidSessionStorage } from './factory-droid-session-storage';
|
||||
|
||||
import Store from 'electron-store';
|
||||
import { registerSessionStorage } from '../agent-session-storage';
|
||||
import { ClaudeSessionStorage, ClaudeSessionOriginsData } from './claude-session-storage';
|
||||
import { OpenCodeSessionStorage } from './opencode-session-storage';
|
||||
import { CodexSessionStorage } from './codex-session-storage';
|
||||
import { FactoryDroidSessionStorage } from './factory-droid-session-storage';
|
||||
|
||||
/**
|
||||
* Options for initializing session storages
|
||||
@@ -33,4 +35,5 @@ export function initializeSessionStorages(options?: InitializeSessionStoragesOpt
|
||||
registerSessionStorage(new ClaudeSessionStorage(options?.claudeSessionOriginsStore));
|
||||
registerSessionStorage(new OpenCodeSessionStorage());
|
||||
registerSessionStorage(new CodexSessionStorage());
|
||||
registerSessionStorage(new FactoryDroidSessionStorage());
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ interface EditAgentModalProps {
|
||||
}
|
||||
|
||||
// Supported agents that are fully implemented
|
||||
const SUPPORTED_AGENTS = ['claude-code', 'opencode', 'codex'];
|
||||
const SUPPORTED_AGENTS = ['claude-code', 'opencode', 'codex', 'factory-droid'];
|
||||
|
||||
export function NewInstanceModal({
|
||||
isOpen,
|
||||
|
||||
@@ -63,6 +63,13 @@ export const AGENT_TILES: AgentTile[] = [
|
||||
description: 'Open-source AI coding assistant',
|
||||
brandColor: '#F97316', // Orange
|
||||
},
|
||||
{
|
||||
id: 'factory-droid',
|
||||
name: 'Factory Droid',
|
||||
supported: true,
|
||||
description: "Factory's AI coding assistant",
|
||||
brandColor: '#8B5CF6', // Purple/violet
|
||||
},
|
||||
// Coming soon agents at the bottom
|
||||
{
|
||||
id: 'aider',
|
||||
@@ -87,9 +94,9 @@ export const AGENT_TILES: AgentTile[] = [
|
||||
},
|
||||
];
|
||||
|
||||
// Grid dimensions for keyboard navigation (3 cols for 5 items)
|
||||
// Grid dimensions for keyboard navigation (3 cols for 7 items)
|
||||
const GRID_COLS = 3;
|
||||
const GRID_ROWS = 2;
|
||||
const GRID_ROWS = 3;
|
||||
|
||||
/**
|
||||
* Get SVG logo for an agent with brand colors
|
||||
|
||||
@@ -626,6 +626,28 @@ export function AgentConfigPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{option.type === 'select' && option.options && (
|
||||
<select
|
||||
value={agentConfig[option.key] ?? option.default ?? ''}
|
||||
onChange={(e) => {
|
||||
onConfigChange(option.key, e.target.value);
|
||||
onConfigBlur();
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="w-full p-2 rounded border bg-transparent outline-none text-xs cursor-pointer"
|
||||
style={{
|
||||
borderColor: theme.colors.border,
|
||||
color: theme.colors.textMain,
|
||||
backgroundColor: theme.colors.bgMain,
|
||||
}}
|
||||
>
|
||||
{option.options.map((opt) => (
|
||||
<option key={opt} value={opt} style={{ backgroundColor: theme.colors.bgMain }}>
|
||||
{opt}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<p className="text-xs opacity-50 mt-2">{option.description}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -43,6 +43,9 @@ export const AGENT_ICONS: Record<string, string> = {
|
||||
opencode: '📟',
|
||||
aider: '🛠️',
|
||||
|
||||
// Enterprise
|
||||
'factory-droid': '🏭',
|
||||
|
||||
// Terminal/shell (internal)
|
||||
terminal: '💻',
|
||||
};
|
||||
|
||||
@@ -666,14 +666,15 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
? `${activeSession.id}-ai-${activeTabForSpawn?.id || 'default'}`
|
||||
: `${activeSession.id}-terminal`;
|
||||
|
||||
// Check if this is an AI agent in batch mode (e.g., Claude Code, OpenCode, Codex)
|
||||
// Check if this is an AI agent in batch mode (e.g., Claude Code, OpenCode, Codex, Factory Droid)
|
||||
// Batch mode agents spawn a new process per message rather than writing to stdin
|
||||
const isBatchModeAgent =
|
||||
currentMode === 'ai' &&
|
||||
(activeSession.toolType === 'claude' ||
|
||||
activeSession.toolType === 'claude-code' ||
|
||||
activeSession.toolType === 'opencode' ||
|
||||
activeSession.toolType === 'codex');
|
||||
activeSession.toolType === 'codex' ||
|
||||
activeSession.toolType === 'factory-droid');
|
||||
|
||||
if (isBatchModeAgent) {
|
||||
// Batch mode: Spawn new agent process with prompt
|
||||
|
||||
@@ -110,6 +110,18 @@ export const AGENT_ARTIFACTS: Record<ToolType, string[]> = {
|
||||
'openai codex',
|
||||
'OpenAI Codex',
|
||||
],
|
||||
'factory-droid': [
|
||||
// Brand references
|
||||
'Factory',
|
||||
'Droid',
|
||||
'Factory Droid',
|
||||
// Model references (can use multiple providers)
|
||||
'Claude',
|
||||
'GPT',
|
||||
'Gemini',
|
||||
'Opus',
|
||||
'Sonnet',
|
||||
],
|
||||
claude: [
|
||||
// This is the base Claude (not Claude Code)
|
||||
'Claude',
|
||||
@@ -150,6 +162,12 @@ export const AGENT_TARGET_NOTES: Record<ToolType, string> = {
|
||||
It uses reasoning models like o1, o3, and o4-mini.
|
||||
It can read files, edit code, and run terminal commands.
|
||||
It excels at complex reasoning and problem-solving.
|
||||
`,
|
||||
'factory-droid': `
|
||||
Factory Droid is an enterprise AI coding assistant by Factory.
|
||||
It supports multiple model providers (Claude, GPT, Gemini).
|
||||
It can read and edit files, run commands, search code, and interact with git.
|
||||
It has tiered autonomy levels for controlling operation permissions.
|
||||
`,
|
||||
claude: `
|
||||
Claude is a general-purpose AI assistant by Anthropic.
|
||||
@@ -171,6 +189,7 @@ export function getAgentDisplayName(agentType: ToolType): string {
|
||||
aider: 'Aider',
|
||||
opencode: 'OpenCode',
|
||||
codex: 'OpenAI Codex',
|
||||
'factory-droid': 'Factory Droid',
|
||||
claude: 'Claude',
|
||||
terminal: 'Terminal',
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ export const DEFAULT_CONTEXT_WINDOWS: Record<ToolType, number> = {
|
||||
codex: 200000, // OpenAI o3/o4-mini context window
|
||||
opencode: 128000, // OpenCode (depends on model, 128k is conservative default)
|
||||
aider: 128000, // Aider (varies by model, 128k is conservative default)
|
||||
'factory-droid': 200000, // Factory Droid (varies by model, defaults to Claude Opus)
|
||||
terminal: 0, // Terminal has no context window
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ export function getProviderDisplayName(toolType: ToolType): string {
|
||||
aider: 'Aider',
|
||||
opencode: 'OpenCode',
|
||||
codex: 'Codex',
|
||||
'factory-droid': 'Factory Droid',
|
||||
terminal: 'Terminal',
|
||||
};
|
||||
return displayNames[toolType] || toolType;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Shared type definitions for Maestro CLI and Electron app
|
||||
// These types are used by both the CLI tool and the renderer process
|
||||
|
||||
export type ToolType = 'claude' | 'claude-code' | 'aider' | 'opencode' | 'codex' | 'terminal';
|
||||
export type ToolType = 'claude' | 'claude-code' | 'aider' | 'opencode' | 'codex' | 'terminal' | 'factory-droid';
|
||||
|
||||
// Session group
|
||||
export interface Group {
|
||||
|
||||
Reference in New Issue
Block a user